[codegen/pcl] Allocate fewer type instances. (#7548)

When converting a `schema.InputType` to a `model.Type`, calculate the
resolved form of the type in the schema type system rather than the
model type system. The results are semantically identical, but the
number of type objects that are allocated is much smaller b/c
`model.NewOutputType` no longer allocates.

This deserves a little more explanation.

In order to prevent nested outputs and/or promises, `model.NewOutputType`
calculates the resolved form of its argument prior to allocating a new
`OutputType` value. Calculating the resolved form of the argument is a
no-op if the argument is already fully resolved. Therefore, passing in a
fully-resolved schema type prevents `model.NewOutputType` from
calulating the resolved form, and `model.NewOutputType` will only
allocate the `OutputType` itself instead of the `OutputType` and the
resolved form of any eventuals present in its argument.

This has a _very important_ knock-on benefit: the schema -> model type
translator ensures that given a `schema.Type` instance `T` it will
always return the same `model.Type` instance `U`. This termendously
speeds up type equality checks for complex types, as they will now be
referentially identical.

This change alone gives a significant speedup in azure-native code
generation.
This commit is contained in:
Pat Gavlin 2021-07-16 07:49:24 -07:00 committed by GitHub
parent c6062ea1d5
commit 80d35758d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 19 additions and 23 deletions

View file

@ -47,7 +47,7 @@ type binder struct {
options bindOptions
referencedPackages map[string]*schema.Package
typeSchemas map[model.Type]schema.Type
schemaTypes map[schema.Type]model.Type
tokens syntax.TokenMap
nodes []Node
@ -117,7 +117,7 @@ func BindProgram(files []*syntax.File, opts ...BindOption) (*Program, hcl.Diagno
options: options,
tokens: syntax.NewTokenMapForFiles(files),
referencedPackages: map[string]*schema.Package{},
typeSchemas: map[model.Type]schema.Type{},
schemaTypes: map[schema.Type]model.Type{},
root: model.NewRootScope(syntax.None),
}

View file

@ -146,34 +146,29 @@ func (b *binder) loadReferencedPackageSchemas(n Node) error {
// schemaTypeToType converts a schema.Type to a model Type.
func (b *binder) schemaTypeToType(src schema.Type) (result model.Type) {
return b.schemaTypeToTypeImpl(src, map[schema.Type]model.Type{})
}
func (b *binder) schemaTypeToTypeImpl(src schema.Type, seen map[schema.Type]model.Type) (result model.Type) {
defer func() {
b.typeSchemas[result] = src
}()
if already, ok := seen[src]; ok {
return already
}
switch src := src.(type) {
case *schema.ArrayType:
return model.NewListType(b.schemaTypeToTypeImpl(src.ElementType, seen))
return model.NewListType(b.schemaTypeToType(src.ElementType))
case *schema.MapType:
return model.NewMapType(b.schemaTypeToTypeImpl(src.ElementType, seen))
return model.NewMapType(b.schemaTypeToType(src.ElementType))
case *schema.EnumType:
// TODO(codegen): make this a union of constant types.
return b.schemaTypeToType(src.ElementType)
case *schema.ObjectType:
if t, ok := b.schemaTypes[src]; ok {
return t
}
properties := map[string]model.Type{}
objType := model.NewObjectType(properties, src)
seen[src] = objType
b.schemaTypes[src] = objType
for _, prop := range src.Properties {
typ := prop.Type
if b.options.allowMissingProperties {
typ = &schema.OptionalType{ElementType: typ}
}
t := b.schemaTypeToTypeImpl(typ, seen)
t := b.schemaTypeToType(typ)
if prop.ConstValue != nil {
var value cty.Value
switch v := prop.ConstValue.(type) {
@ -200,20 +195,21 @@ func (b *binder) schemaTypeToTypeImpl(src schema.Type, seen map[schema.Type]mode
}
if src.UnderlyingType != nil {
underlyingType := b.schemaTypeToTypeImpl(src.UnderlyingType, seen)
underlyingType := b.schemaTypeToType(src.UnderlyingType)
return model.NewUnionType(t, underlyingType)
}
return t
case *schema.InputType:
elementType := b.schemaTypeToTypeImpl(src.ElementType, seen)
return model.NewUnionTypeAnnotated([]model.Type{elementType, model.NewOutputType(elementType)}, src)
elementType := b.schemaTypeToType(src.ElementType)
resolvedElementType := b.schemaTypeToType(codegen.ResolvedType(src.ElementType))
return model.NewUnionTypeAnnotated([]model.Type{elementType, model.NewOutputType(resolvedElementType)}, src)
case *schema.OptionalType:
elementType := b.schemaTypeToTypeImpl(src.ElementType, seen)
elementType := b.schemaTypeToType(src.ElementType)
return model.NewOptionalType(elementType)
case *schema.UnionType:
types := make([]model.Type, len(src.ElementTypes))
for i, src := range src.ElementTypes {
types[i] = b.schemaTypeToTypeImpl(src, seen)
types[i] = b.schemaTypeToType(src)
}
if src.Discriminator != "" {
return model.NewUnionTypeAnnotated(types, src)