From 2b8e2fb88f99e22ada65bd03c42d2db7bd84b0e8 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 29 Jul 2025 21:13:03 -0700 Subject: [PATCH 1/3] Switch RecursionId to any --- internal/checker/relater.go | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/internal/checker/relater.go b/internal/checker/relater.go index fb3c82ae34..a33446bd1b 100644 --- a/internal/checker/relater.go +++ b/internal/checker/relater.go @@ -86,18 +86,7 @@ type ErrorOutputContainer struct { type ErrorReporter func(message *diagnostics.Message, args ...any) -type RecursionIdKind uint32 - -const ( - RecursionIdKindNode RecursionIdKind = iota - RecursionIdKindSymbol - RecursionIdKindType -) - -type RecursionId struct { - kind RecursionIdKind - id uint32 -} +type RecursionId any type Relation struct { results map[string]RelationComparisonResult @@ -836,21 +825,21 @@ func getRecursionIdentity(t *Type) RecursionId { // Deferred type references are tracked through their associated AST node. This gives us finer // granularity than using their associated target because each manifest type reference has a // unique AST node. - return RecursionId{kind: RecursionIdKindNode, id: uint32(ast.GetNodeId(t.AsTypeReference().node))} + return RecursionId(t.AsTypeReference().node) } if t.symbol != nil && !(t.objectFlags&ObjectFlagsAnonymous != 0 && t.symbol.Flags&ast.SymbolFlagsClass != 0) { // We track object types that have a symbol by that symbol (representing the origin of the type), but // exclude the static side of a class since it shares its symbol with the instance side. - return RecursionId{kind: RecursionIdKindSymbol, id: uint32(ast.GetSymbolId(t.symbol))} + return RecursionId(t.symbol) } if isTupleType(t) { - return RecursionId{kind: RecursionIdKindType, id: uint32(t.Target().id)} + return RecursionId(t.Target()) } } if t.flags&TypeFlagsTypeParameter != 0 && t.symbol != nil { // We use the symbol of the type parameter such that all "fresh" instantiations of that type parameter // have the same recursion identity. - return RecursionId{kind: RecursionIdKindSymbol, id: uint32(ast.GetSymbolId(t.symbol))} + return RecursionId(t.symbol) } if t.flags&TypeFlagsIndexedAccess != 0 { // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P1][P2][P3] it is A. @@ -858,13 +847,13 @@ func getRecursionIdentity(t *Type) RecursionId { for t.flags&TypeFlagsIndexedAccess != 0 { t = t.AsIndexedAccessType().objectType } - return RecursionId{kind: RecursionIdKindType, id: uint32(t.id)} + return RecursionId(t) } if t.flags&TypeFlagsConditional != 0 { // The root object represents the origin of the conditional type - return RecursionId{kind: RecursionIdKindNode, id: uint32(ast.GetNodeId(t.AsConditionalType().root.node.AsNode()))} + return RecursionId(t.AsConditionalType().root.node.AsNode()) } - return RecursionId{kind: RecursionIdKindType, id: uint32(t.id)} + return RecursionId(t) } func (c *Checker) getBestMatchingType(source *Type, target *Type, isRelatedTo func(source *Type, target *Type) Ternary) *Type { From 3133d3b4fb453c10d513d0324cb9fd86022fb3a4 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:35:05 -0700 Subject: [PATCH 2/3] Constrain types used as recursion IDs to prevent mistakes --- internal/checker/relater.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/internal/checker/relater.go b/internal/checker/relater.go index 37e4ec7db7..2739623741 100644 --- a/internal/checker/relater.go +++ b/internal/checker/relater.go @@ -86,7 +86,14 @@ type ErrorOutputContainer struct { type ErrorReporter func(message *diagnostics.Message, args ...any) -type RecursionId any +type RecursionId struct { + value any +} + +// This function exists to constraint the types of values that can be used as recursion IDs. +func asRecursionId[T *ast.Node | *ast.Symbol | *Type](value T) RecursionId { + return RecursionId{value: value} +} type Relation struct { results map[string]RelationComparisonResult @@ -825,21 +832,21 @@ func getRecursionIdentity(t *Type) RecursionId { // Deferred type references are tracked through their associated AST node. This gives us finer // granularity than using their associated target because each manifest type reference has a // unique AST node. - return RecursionId(t.AsTypeReference().node) + return asRecursionId(t.AsTypeReference().node) } if t.symbol != nil && !(t.objectFlags&ObjectFlagsAnonymous != 0 && t.symbol.Flags&ast.SymbolFlagsClass != 0) { // We track object types that have a symbol by that symbol (representing the origin of the type), but // exclude the static side of a class since it shares its symbol with the instance side. - return RecursionId(t.symbol) + return asRecursionId(t.symbol) } if isTupleType(t) { - return RecursionId(t.Target()) + return asRecursionId(t.Target()) } } if t.flags&TypeFlagsTypeParameter != 0 && t.symbol != nil { // We use the symbol of the type parameter such that all "fresh" instantiations of that type parameter // have the same recursion identity. - return RecursionId(t.symbol) + return asRecursionId(t.symbol) } if t.flags&TypeFlagsIndexedAccess != 0 { // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P1][P2][P3] it is A. @@ -847,13 +854,13 @@ func getRecursionIdentity(t *Type) RecursionId { for t.flags&TypeFlagsIndexedAccess != 0 { t = t.AsIndexedAccessType().objectType } - return RecursionId(t) + return asRecursionId(t) } if t.flags&TypeFlagsConditional != 0 { // The root object represents the origin of the conditional type - return RecursionId(t.AsConditionalType().root.node.AsNode()) + return asRecursionId(t.AsConditionalType().root.node.AsNode()) } - return RecursionId(t) + return asRecursionId(t) } func (c *Checker) getBestMatchingType(source *Type, target *Type, isRelatedTo func(source *Type, target *Type) Ternary) *Type { From 54f38d24617f037d365a8cd99747c54028912905 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:42:08 -0700 Subject: [PATCH 3/3] Update internal/checker/relater.go --- internal/checker/relater.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/checker/relater.go b/internal/checker/relater.go index 2739623741..fdae04e56d 100644 --- a/internal/checker/relater.go +++ b/internal/checker/relater.go @@ -90,7 +90,7 @@ type RecursionId struct { value any } -// This function exists to constraint the types of values that can be used as recursion IDs. +// This function exists to constrain the types of values that can be used as recursion IDs. func asRecursionId[T *ast.Node | *ast.Symbol | *Type](value T) RecursionId { return RecursionId{value: value} }