mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
0bbbb9c64b
This builds on an experimental feature in the underlying cty library which allows marking specific attribtues of an object type constraint as optional, which in turn modifies how the cty conversion package handles missing attributes in a source value: it will silently substitute a null value of the appropriate type rather than returning an error. In order to implement the experiment this commit temporarily forks the HCL typeexpr extension package into a local internal/typeexpr package, where I've extended the type constraint syntax to allow annotating object type attributes as being optional using the HCL function call syntax. If the experiment is successful -- both at the Terraform layer and in the underlying cty library -- we'll likely send these modifications to upstream HCL so that other HCL-based languages can potentially benefit from this new capability. Because it's experimental, the optional attribute modifier is allowed only with an explicit opt-in to the module_variable_optional_attrs experiment.
130 lines
3.6 KiB
Go
130 lines
3.6 KiB
Go
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))
|
|
}
|