Skip to content

Commit

Permalink
schemautil: fix infinite recursion
Browse files Browse the repository at this point in the history
For mutually recursive types we need to track
which declarations we've already seen.

Thanks Juan Álvarez for the report.
  • Loading branch information
eandre committed Apr 5, 2023
1 parent aa60454 commit 0133579
Showing 1 changed file with 28 additions and 13 deletions.
41 changes: 28 additions & 13 deletions v2/internals/schema/schemautil/schemautil.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"encr.dev/pkg/paths"
"encr.dev/v2/internals/perr"
"encr.dev/v2/internals/pkginfo"
"encr.dev/v2/internals/schema"
)

Expand Down Expand Up @@ -108,17 +109,23 @@ func ResolveNamedStruct(t schema.Type, requirePointer bool) (ref *schema.TypeDec
// To be more robust in the presence of typing errors it supports partial application,
// where the number of type arguments may be different than the number of type parameters on the decl.
func ConcretizeGenericType(typ schema.Type) schema.Type {
return concretize(typ, nil)
return concretize(typ, nil, nil)
}

// ConcretizeWithTypeArgs is like ConcretizeGenericType but operates with
// a list of type arguments. It is used when the type arguments are known
// separately from the type itself, such as when using *schema.TypeDeclRef.
func ConcretizeWithTypeArgs(typ schema.Type, typeArgs []schema.Type) schema.Type {
return concretize(typ, typeArgs)
return concretize(typ, typeArgs, nil)
}

func concretize(typ schema.Type, typeArgs []schema.Type) schema.Type {
func concretize(typ schema.Type, typeArgs []schema.Type, seenDecls map[*pkginfo.PkgDeclInfo]bool) schema.Type {
// seenDecls is used to avoid infinite recursion
// for mutually recursive types.
if seenDecls == nil {
seenDecls = make(map[*pkginfo.PkgDeclInfo]bool)
}

switch typ := typ.(type) {
case schema.TypeParamRefType:
// We have a reference to a type parameter.
Expand All @@ -132,14 +139,14 @@ func concretize(typ schema.Type, typeArgs []schema.Type) schema.Type {
case schema.BuiltinType:
return typ
case schema.PointerType:
return schema.PointerType{AST: typ.AST, Elem: concretize(typ.Elem, typeArgs)}
return schema.PointerType{AST: typ.AST, Elem: concretize(typ.Elem, typeArgs, seenDecls)}
case schema.ListType:
return schema.ListType{AST: typ.AST, Elem: concretize(typ.Elem, typeArgs), Len: typ.Len}
return schema.ListType{AST: typ.AST, Elem: concretize(typ.Elem, typeArgs, seenDecls), Len: typ.Len}
case schema.MapType:
return schema.MapType{
AST: typ.AST,
Key: concretize(typ.Key, typeArgs),
Value: concretize(typ.Value, typeArgs),
Key: concretize(typ.Key, typeArgs, seenDecls),
Value: concretize(typ.Value, typeArgs, seenDecls),
}
case schema.StructType:
result := schema.StructType{
Expand All @@ -148,7 +155,7 @@ func concretize(typ schema.Type, typeArgs []schema.Type) schema.Type {
}
for i, f := range typ.Fields {
result.Fields[i] = f // copy
result.Fields[i].Type = concretize(f.Type, typeArgs)
result.Fields[i].Type = concretize(f.Type, typeArgs, seenDecls)
}
return result
case schema.NamedType:
Expand All @@ -157,24 +164,32 @@ func concretize(typ schema.Type, typeArgs []schema.Type) schema.Type {
clone.TypeArgs = slices.Clone(typ.TypeArgs)

for i, arg := range clone.TypeArgs {
clone.TypeArgs[i] = concretize(arg, typeArgs)
clone.TypeArgs[i] = concretize(arg, typeArgs, seenDecls)
}

decl := clone.Decl().Clone() // clone the type declaration
decl.Type = concretize(decl.Type, clone.TypeArgs)
// If we've already seen this declaration, don't clone it to avoid
// infinite recursion.
if seenDecls[typ.DeclInfo] {
return clone
}

// Clone and concretize the declaration.
seenDecls[typ.DeclInfo] = true
decl := clone.Decl().Clone()
decl.Type = concretize(decl.Type, clone.TypeArgs, seenDecls)
return clone.WithDecl(decl)

case schema.FuncType:
// Clone the function type. Clone the slices so we don't overwrite the original.
clone := typ // copy
clone.Params = slices.Clone(typ.Params)
clone.Results = slices.Clone(typ.Results)

for i, p := range clone.Params {
clone.Params[i].Type = concretize(p.Type, typeArgs)
clone.Params[i].Type = concretize(p.Type, typeArgs, seenDecls)
}
for i, p := range clone.Results {
clone.Results[i].Type = concretize(p.Type, typeArgs)
clone.Results[i].Type = concretize(p.Type, typeArgs, seenDecls)
}
return clone
case schema.InterfaceType:
Expand Down

0 comments on commit 0133579

Please sign in to comment.