mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 10:03:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			263 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			263 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package convert
 | 
						|
 | 
						|
import (
 | 
						|
	"github.com/zclconf/go-cty/cty"
 | 
						|
)
 | 
						|
 | 
						|
// conversion is an internal variant of Conversion that carries around
 | 
						|
// a cty.Path to be used in error responses.
 | 
						|
type conversion func(cty.Value, cty.Path) (cty.Value, error)
 | 
						|
 | 
						|
func getConversion(in cty.Type, out cty.Type, unsafe bool) conversion {
 | 
						|
	conv := getConversionKnown(in, out, unsafe)
 | 
						|
	if conv == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Wrap the conversion in some standard checks that we don't want to
 | 
						|
	// have to repeat in every conversion function.
 | 
						|
	var ret conversion
 | 
						|
	ret = func(in cty.Value, path cty.Path) (cty.Value, error) {
 | 
						|
		if in.IsMarked() {
 | 
						|
			// We must unmark during the conversion and then re-apply the
 | 
						|
			// same marks to the result.
 | 
						|
			in, inMarks := in.Unmark()
 | 
						|
			v, err := ret(in, path)
 | 
						|
			if v != cty.NilVal {
 | 
						|
				v = v.WithMarks(inMarks)
 | 
						|
			}
 | 
						|
			return v, err
 | 
						|
		}
 | 
						|
 | 
						|
		if out == cty.DynamicPseudoType {
 | 
						|
			// Conversion to DynamicPseudoType always just passes through verbatim.
 | 
						|
			return in, nil
 | 
						|
		}
 | 
						|
		if isKnown, isNull := in.IsKnown(), in.IsNull(); !isKnown || isNull {
 | 
						|
			// Avoid constructing unknown or null values with types which
 | 
						|
			// include optional attributes. Known or non-null object values
 | 
						|
			// will be passed to a conversion function which drops the optional
 | 
						|
			// attributes from the type. Unknown and null pass through values
 | 
						|
			// must do the same to ensure that homogeneous collections have a
 | 
						|
			// single element type.
 | 
						|
			out = out.WithoutOptionalAttributesDeep()
 | 
						|
 | 
						|
			if !isKnown {
 | 
						|
				return prepareUnknownResult(in.Range(), dynamicReplace(in.Type(), out)), nil
 | 
						|
			}
 | 
						|
 | 
						|
			if isNull {
 | 
						|
				// We'll pass through nulls, albeit type converted, and let
 | 
						|
				// the caller deal with whatever handling they want to do in
 | 
						|
				// case null values are considered valid in some applications.
 | 
						|
				return cty.NullVal(dynamicReplace(in.Type(), out)), nil
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return conv(in, path)
 | 
						|
	}
 | 
						|
 | 
						|
	return ret
 | 
						|
}
 | 
						|
 | 
						|
func getConversionKnown(in cty.Type, out cty.Type, unsafe bool) conversion {
 | 
						|
	switch {
 | 
						|
 | 
						|
	case out == cty.DynamicPseudoType:
 | 
						|
		// Conversion *to* DynamicPseudoType means that the caller wishes
 | 
						|
		// to allow any type in this position, so we'll produce a do-nothing
 | 
						|
		// conversion that just passes through the value as-is.
 | 
						|
		return dynamicPassthrough
 | 
						|
 | 
						|
	case unsafe && in == cty.DynamicPseudoType:
 | 
						|
		// Conversion *from* DynamicPseudoType means that we have a value
 | 
						|
		// whose type isn't yet known during type checking. For these we will
 | 
						|
		// assume that conversion will succeed and deal with any errors that
 | 
						|
		// result (which is why we can only do this when "unsafe" is set).
 | 
						|
		return dynamicFixup(out)
 | 
						|
 | 
						|
	case in.IsPrimitiveType() && out.IsPrimitiveType():
 | 
						|
		conv := primitiveConversionsSafe[in][out]
 | 
						|
		if conv != nil {
 | 
						|
			return conv
 | 
						|
		}
 | 
						|
		if unsafe {
 | 
						|
			return primitiveConversionsUnsafe[in][out]
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
 | 
						|
	case out.IsObjectType() && in.IsObjectType():
 | 
						|
		return conversionObjectToObject(in, out, unsafe)
 | 
						|
 | 
						|
	case out.IsTupleType() && in.IsTupleType():
 | 
						|
		return conversionTupleToTuple(in, out, unsafe)
 | 
						|
 | 
						|
	case out.IsListType() && (in.IsListType() || in.IsSetType()):
 | 
						|
		inEty := in.ElementType()
 | 
						|
		outEty := out.ElementType()
 | 
						|
		if inEty.Equals(outEty) {
 | 
						|
			// This indicates that we're converting from list to set with
 | 
						|
			// the same element type, so we don't need an element converter.
 | 
						|
			return conversionCollectionToList(outEty, nil)
 | 
						|
		}
 | 
						|
 | 
						|
		convEty := getConversion(inEty, outEty, unsafe)
 | 
						|
		if convEty == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		return conversionCollectionToList(outEty, convEty)
 | 
						|
 | 
						|
	case out.IsSetType() && (in.IsListType() || in.IsSetType()):
 | 
						|
		if in.IsListType() && !unsafe {
 | 
						|
			// Conversion from list to map is unsafe because it will lose
 | 
						|
			// information: the ordering will not be preserved, and any
 | 
						|
			// duplicate elements will be conflated.
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		inEty := in.ElementType()
 | 
						|
		outEty := out.ElementType()
 | 
						|
		convEty := getConversion(inEty, outEty, unsafe)
 | 
						|
		if inEty.Equals(outEty) {
 | 
						|
			// This indicates that we're converting from set to list with
 | 
						|
			// the same element type, so we don't need an element converter.
 | 
						|
			return conversionCollectionToSet(outEty, nil)
 | 
						|
		}
 | 
						|
 | 
						|
		if convEty == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		return conversionCollectionToSet(outEty, convEty)
 | 
						|
 | 
						|
	case out.IsMapType() && in.IsMapType():
 | 
						|
		inEty := in.ElementType()
 | 
						|
		outEty := out.ElementType()
 | 
						|
		convEty := getConversion(inEty, outEty, unsafe)
 | 
						|
		if convEty == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		return conversionCollectionToMap(outEty, convEty)
 | 
						|
 | 
						|
	case out.IsListType() && in.IsTupleType():
 | 
						|
		outEty := out.ElementType()
 | 
						|
		return conversionTupleToList(in, outEty, unsafe)
 | 
						|
 | 
						|
	case out.IsSetType() && in.IsTupleType():
 | 
						|
		outEty := out.ElementType()
 | 
						|
		return conversionTupleToSet(in, outEty, unsafe)
 | 
						|
 | 
						|
	case out.IsMapType() && in.IsObjectType():
 | 
						|
		outEty := out.ElementType()
 | 
						|
		return conversionObjectToMap(in, outEty, unsafe)
 | 
						|
 | 
						|
	case out.IsObjectType() && in.IsMapType():
 | 
						|
		if !unsafe {
 | 
						|
			// Converting a map to an object is an "unsafe" conversion,
 | 
						|
			// because we don't know if all the map keys will correspond to
 | 
						|
			// object attributes.
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		return conversionMapToObject(in, out, unsafe)
 | 
						|
 | 
						|
	case in.IsCapsuleType() || out.IsCapsuleType():
 | 
						|
		if !unsafe {
 | 
						|
			// Capsule types can only participate in "unsafe" conversions,
 | 
						|
			// because we don't know enough about their conversion behaviors
 | 
						|
			// to be sure that they will always be safe.
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		if in.Equals(out) {
 | 
						|
			// conversion to self is never allowed
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		if out.IsCapsuleType() {
 | 
						|
			if fn := out.CapsuleOps().ConversionTo; fn != nil {
 | 
						|
				return conversionToCapsule(in, out, fn)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if in.IsCapsuleType() {
 | 
						|
			if fn := in.CapsuleOps().ConversionFrom; fn != nil {
 | 
						|
				return conversionFromCapsule(in, out, fn)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// No conversion operation is available, then.
 | 
						|
		return nil
 | 
						|
 | 
						|
	default:
 | 
						|
		return nil
 | 
						|
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// retConversion wraps a conversion (internal type) so it can be returned
 | 
						|
// as a Conversion (public type).
 | 
						|
func retConversion(conv conversion) Conversion {
 | 
						|
	if conv == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	return func(in cty.Value) (cty.Value, error) {
 | 
						|
		return conv(in, cty.Path(nil))
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// prepareUnknownResult can apply value refinements to a returned unknown value
 | 
						|
// in certain cases where characteristics of the source value or type can
 | 
						|
// transfer into range constraints on the result value.
 | 
						|
func prepareUnknownResult(sourceRange cty.ValueRange, targetTy cty.Type) cty.Value {
 | 
						|
	sourceTy := sourceRange.TypeConstraint()
 | 
						|
 | 
						|
	ret := cty.UnknownVal(targetTy)
 | 
						|
	if sourceRange.DefinitelyNotNull() {
 | 
						|
		ret = ret.RefineNotNull()
 | 
						|
	}
 | 
						|
 | 
						|
	switch {
 | 
						|
	case sourceTy.IsObjectType() && targetTy.IsMapType():
 | 
						|
		// A map built from an object type always has the same number of
 | 
						|
		// elements as the source type has attributes.
 | 
						|
		return ret.Refine().CollectionLength(len(sourceTy.AttributeTypes())).NewValue()
 | 
						|
	case sourceTy.IsTupleType() && targetTy.IsListType():
 | 
						|
		// A list built from a typle type always has the same number of
 | 
						|
		// elements as the source type has elements.
 | 
						|
		return ret.Refine().CollectionLength(sourceTy.Length()).NewValue()
 | 
						|
	case sourceTy.IsTupleType() && targetTy.IsSetType():
 | 
						|
		// When building a set from a tuple type we can't exactly constrain
 | 
						|
		// the length because some elements might coalesce, but we can
 | 
						|
		// guarantee an upper limit. We can also guarantee at least one
 | 
						|
		// element if the tuple isn't empty.
 | 
						|
		switch l := sourceTy.Length(); l {
 | 
						|
		case 0, 1:
 | 
						|
			return ret.Refine().CollectionLength(l).NewValue()
 | 
						|
		default:
 | 
						|
			return ret.Refine().
 | 
						|
				CollectionLengthLowerBound(1).
 | 
						|
				CollectionLengthUpperBound(sourceTy.Length()).
 | 
						|
				NewValue()
 | 
						|
		}
 | 
						|
	case sourceTy.IsCollectionType() && targetTy.IsCollectionType():
 | 
						|
		// NOTE: We only reach this function if there is an available
 | 
						|
		// conversion between the source and target type, so we don't
 | 
						|
		// need to repeat element type compatibility checks and such here.
 | 
						|
		//
 | 
						|
		// If the source value already has a refined length then we'll
 | 
						|
		// transfer those refinements to the result, because conversion
 | 
						|
		// does not change length (aside from set element coalescing).
 | 
						|
		b := ret.Refine()
 | 
						|
		if targetTy.IsSetType() {
 | 
						|
			if sourceRange.LengthLowerBound() > 0 {
 | 
						|
				// If the source has at least one element then the result
 | 
						|
				// must always have at least one too, because value coalescing
 | 
						|
				// cannot totally empty the set.
 | 
						|
				b = b.CollectionLengthLowerBound(1)
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			b = b.CollectionLengthLowerBound(sourceRange.LengthLowerBound())
 | 
						|
		}
 | 
						|
		b = b.CollectionLengthUpperBound(sourceRange.LengthUpperBound())
 | 
						|
		return b.NewValue()
 | 
						|
	default:
 | 
						|
		return ret
 | 
						|
	}
 | 
						|
 | 
						|
}
 |