From 80d35758d5845f49e5153932d8b126768246aa0b Mon Sep 17 00:00:00 2001 From: Pat Gavlin Date: Fri, 16 Jul 2021 07:49:24 -0700 Subject: [PATCH] [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. --- pkg/codegen/hcl2/binder.go | 4 ++-- pkg/codegen/hcl2/binder_schema.go | 38 ++++++++++++++----------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/pkg/codegen/hcl2/binder.go b/pkg/codegen/hcl2/binder.go index 7c6132f89..3998517de 100644 --- a/pkg/codegen/hcl2/binder.go +++ b/pkg/codegen/hcl2/binder.go @@ -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), } diff --git a/pkg/codegen/hcl2/binder_schema.go b/pkg/codegen/hcl2/binder_schema.go index 1f679f491..3418d5c11 100644 --- a/pkg/codegen/hcl2/binder_schema.go +++ b/pkg/codegen/hcl2/binder_schema.go @@ -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)