Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

schemautil: fix infinite recursion #659

Merged
merged 1 commit into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions v2/codegen/cuegen/testdata/generic_named_types_svc.cue
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ package svc
}
#Config

#List_string: [...string]

// Generic option which can be disbaled
#DisablableOption_uint64: {
Option: uint64
Disabled: bool // True if this is disabled
}

// Generic option which can be disbaled
#DisablableOption_uint16: {
Option: uint16
Expand All @@ -50,12 +58,4 @@ package svc
// A nice generic map
#Map_string_string: {
[string]: string
}

#List_string: [...string]

// Generic option which can be disbaled
#DisablableOption_uint64: {
Option: uint64
Disabled: bool // True if this is disabled
}
43 changes: 30 additions & 13 deletions v2/internals/schema/schemautil/schemautil.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,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[TypeHash]*schema.TypeDecl) schema.Type {
// seenDecls is used to avoid infinite recursion
// for mutually recursive types.
if seenDecls == nil {
seenDecls = make(map[TypeHash]*schema.TypeDecl)
}

switch typ := typ.(type) {
case schema.TypeParamRefType:
// We have a reference to a type parameter.
Expand All @@ -132,14 +138,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 +154,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 +163,34 @@ 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)
}

// If we've already seen this declaration, don't clone it to avoid
// infinite recursion.
hash := Hash(typ)
if decl, ok := seenDecls[hash]; ok {
return clone.WithDecl(decl)
}

decl := clone.Decl().Clone() // clone the type declaration
decl.Type = concretize(decl.Type, clone.TypeArgs)
decl := clone.Decl().Clone()

// Clone and concretize the declaration.
seenDecls[hash] = decl
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 Expand Up @@ -275,6 +291,7 @@ func hashType(buf *bytes.Buffer, t schema.Type) {
case schema.NamedType:
buf.WriteString("named:")
buf.WriteString(t.DeclInfo.File.Pkg.ImportPath.String())
buf.WriteString(".")
buf.WriteString(t.DeclInfo.Name)
if len(t.TypeArgs) > 0 {
buf.WriteString("[")
Expand Down