package typeexpr import ( "bytes" "fmt" "sort" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2" "github.com/zclconf/go-cty/cty" ) // Type attempts to process the given expression as a type expression and, if // successful, returns the resulting type. If unsuccessful, error diagnostics // are returned. func Type(expr hcl.Expression) (cty.Type, hcl.Diagnostics) { return getType(expr, false) } // TypeConstraint attempts to parse the given expression as a type constraint // and, if successful, returns the resulting type. If unsuccessful, error // diagnostics are returned. // // A type constraint has the same structure as a type, but it additionally // allows the keyword "any" to represent cty.DynamicPseudoType, which is often // used as a wildcard in type checking and type conversion operations. func TypeConstraint(expr hcl.Expression) (cty.Type, hcl.Diagnostics) { return getType(expr, true) } // TypeString returns a string rendering of the given type as it would be // expected to appear in the HCL native syntax. // // This is primarily intended for showing types to the user in an application // that uses typexpr, where the user can be assumed to be familiar with the // type expression syntax. In applications that do not use typeexpr these // results may be confusing to the user and so type.FriendlyName may be // preferable, even though it's less precise. // // TypeString produces reasonable results only for types like what would be // produced by the Type and TypeConstraint functions. In particular, it cannot // support capsule types. func TypeString(ty cty.Type) string { // Easy cases first switch ty { case cty.String: return "string" case cty.Bool: return "bool" case cty.Number: return "number" case cty.DynamicPseudoType: return "any" } if ty.IsCapsuleType() { panic("TypeString does not support capsule types") } if ty.IsCollectionType() { ety := ty.ElementType() etyString := TypeString(ety) switch { case ty.IsListType(): return fmt.Sprintf("list(%s)", etyString) case ty.IsSetType(): return fmt.Sprintf("set(%s)", etyString) case ty.IsMapType(): return fmt.Sprintf("map(%s)", etyString) default: // Should never happen because the above is exhaustive panic("unsupported collection type") } } if ty.IsObjectType() { var buf bytes.Buffer buf.WriteString("object({") atys := ty.AttributeTypes() names := make([]string, 0, len(atys)) for name := range atys { names = append(names, name) } sort.Strings(names) first := true for _, name := range names { aty := atys[name] if !first { buf.WriteByte(',') } if !hclsyntax.ValidIdentifier(name) { // Should never happen for any type produced by this package, // but we'll do something reasonable here just so we don't // produce garbage if someone gives us a hand-assembled object // type that has weird attribute names. // Using Go-style quoting here isn't perfect, since it doesn't // exactly match HCL syntax, but it's fine for an edge-case. buf.WriteString(fmt.Sprintf("%q", name)) } else { buf.WriteString(name) } buf.WriteByte('=') buf.WriteString(TypeString(aty)) first = false } buf.WriteString("})") return buf.String() } if ty.IsTupleType() { var buf bytes.Buffer buf.WriteString("tuple([") etys := ty.TupleElementTypes() first := true for _, ety := range etys { if !first { buf.WriteByte(',') } buf.WriteString(TypeString(ety)) first = false } buf.WriteString("])") return buf.String() } // Should never happen because we covered all cases above. panic(fmt.Errorf("unsupported type %#v", ty)) }