diff --git a/internal/repl/session.go b/internal/repl/session.go index de567f81be..cac405f685 100644 --- a/internal/repl/session.go +++ b/internal/repl/session.go @@ -104,16 +104,13 @@ Control-D. return strings.TrimSpace(text), nil } -// Modified copy of TypeString from go-cty: +// typeString returns a string representation of a given type that is +// reminiscent of the OpenTofu type constraint syntax that might be used +// to declare the type as part of an input variable declaration. +// +// This is a modified copy of TypeString from go-cty-debug, adapted to +// produce HCL-like type expressions instead of Go expressions: // https://github.com/zclconf/go-cty-debug/blob/master/ctydebug/type_string.go -// -// TypeString returns a string representation of a given type that is -// reminiscent of Go syntax calling into the cty package but is mainly -// intended for easy human inspection of values in tests, debug output, etc. -// -// The resulting string will include newlines and indentation in order to -// increase the readability of complex structures. It always ends with a -// newline, so you can print this result directly to your output. func typeString(ty cty.Type) string { var b strings.Builder writeType(ty, &b, 0) @@ -123,81 +120,13 @@ func typeString(ty cty.Type) string { func writeType(ty cty.Type, b *strings.Builder, indent int) { switch { case ty == cty.NilType: - b.WriteString("nil") - return + b.WriteString("nil") // not actually a useful type to print, but handled for robustness case ty.IsObjectType(): - atys := ty.AttributeTypes() - if len(atys) == 0 { - b.WriteString("object({})") - return - } - attrNames := make([]string, 0, len(atys)) - for name := range atys { - attrNames = append(attrNames, name) - } - sort.Strings(attrNames) - b.WriteString("object({\n") - indent++ - for _, name := range attrNames { - aty := atys[name] - b.WriteString(indentSpaces(indent)) - fmt.Fprintf(b, "%s: ", name) - writeType(aty, b, indent) - b.WriteString(",\n") - } - indent-- - b.WriteString(indentSpaces(indent)) - b.WriteString("})") + writeObjectType(ty, b, indent) case ty.IsTupleType(): - etys := ty.TupleElementTypes() - if len(etys) == 0 { - b.WriteString("tuple([])") - return - } - b.WriteString("tuple([\n") - indent++ - for _, ety := range etys { - b.WriteString(indentSpaces(indent)) - writeType(ety, b, indent) - b.WriteString(",\n") - } - indent-- - b.WriteString(indentSpaces(indent)) - b.WriteString("])") + writeTupleType(ty, b, indent) case ty.IsCollectionType(): - ety := ty.ElementType() - switch { - case ty.IsListType(): - b.WriteString("list(") - case ty.IsMapType(): - b.WriteString("map(") - case ty.IsSetType(): - b.WriteString("set(") - default: - // At the time of writing there are no other collection types, - // but we'll be robust here and just pass through the GoString - // of anything we don't recognize. - b.WriteString(ty.FriendlyName()) - return - } - // Because object and tuple types render split over multiple - // lines, a collection type container around them can end up - // being hard to see when scanning, so we'll generate some extra - // indentation to make a collection of structural type more visually - // distinct from the structural type alone. - complexElem := ety.IsObjectType() || ety.IsTupleType() - if complexElem { - indent++ - b.WriteString("\n") - b.WriteString(indentSpaces(indent)) - } - writeType(ty.ElementType(), b, indent) - if complexElem { - indent-- - b.WriteString(",\n") - b.WriteString(indentSpaces(indent)) - } - b.WriteString(")") + writeCollectionType(ty, b, indent) default: // For any other type we'll just use its GoString and assume it'll // follow the usual GoString conventions. @@ -205,6 +134,85 @@ func writeType(ty cty.Type, b *strings.Builder, indent int) { } } +func writeObjectType(ty cty.Type, b *strings.Builder, indent int) { + atys := ty.AttributeTypes() + if len(atys) == 0 { + b.WriteString("object({})") + return + } + attrNames := make([]string, 0, len(atys)) + for name := range atys { + attrNames = append(attrNames, name) + } + sort.Strings(attrNames) + b.WriteString("object({\n") + indent++ + for _, name := range attrNames { + aty := atys[name] + b.WriteString(indentSpaces(indent)) + fmt.Fprintf(b, "%s: ", name) + writeType(aty, b, indent) + b.WriteString(",\n") + } + indent-- + b.WriteString(indentSpaces(indent)) + b.WriteString("})") +} + +func writeTupleType(ty cty.Type, b *strings.Builder, indent int) { + etys := ty.TupleElementTypes() + if len(etys) == 0 { + b.WriteString("tuple([])") + return + } + b.WriteString("tuple([\n") + indent++ + for _, ety := range etys { + b.WriteString(indentSpaces(indent)) + writeType(ety, b, indent) + b.WriteString(",\n") + } + indent-- + b.WriteString(indentSpaces(indent)) + b.WriteString("])") +} + +func writeCollectionType(ty cty.Type, b *strings.Builder, indent int) { + ety := ty.ElementType() + switch { + case ty.IsListType(): + b.WriteString("list(") + case ty.IsMapType(): + b.WriteString("map(") + case ty.IsSetType(): + b.WriteString("set(") + default: + // At the time of writing there are no other collection types, + // but we'll be robust here and just pass through the GoString + // of anything we don't recognize. + b.WriteString(ty.FriendlyName()) + return + } + // Because object and tuple types render split over multiple + // lines, a collection type container around them can end up + // being hard to see when scanning, so we'll generate some extra + // indentation to make a collection of structural type more visually + // distinct from the structural type alone. + complexElem := ety.IsObjectType() || ety.IsTupleType() + if complexElem { + indent++ + b.WriteString("\n") + b.WriteString(indentSpaces(indent)) + } + writeType(ty.ElementType(), b, indent) + if complexElem { + indent-- + b.WriteString(",\n") + b.WriteString(indentSpaces(indent)) + } + b.WriteString(")") +} + func indentSpaces(level int) string { return strings.Repeat(" ", level) }