Skip to content

Commit 19dadef

Browse files
authored
Restore idempotency to resolveObjectTypeMembers (#64372)
1 parent 21b3aeb commit 19dadef

15 files changed

Lines changed: 141 additions & 33 deletions

‎packages/typescript/src/enums/objectFlags.enum.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ export enum ObjectFlags {
3636
IsClassInstanceClone = 1 << 26,
3737
IdenticalBaseTypeCalculated = 1 << 27,
3838
IdenticalBaseTypeExists = 1 << 28,
39-
UnresolvedMembers = 1 << 29,
40-
FromTypeNode = 1 << 30,
39+
FromTypeNode = 1 << 29,
4140
IsGenericTypeComputed = 1 << 22,
4241
IsGenericObjectType = 1 << 23,
4342
IsGenericIndexType = 1 << 24,

‎packages/typescript/src/enums/objectFlags.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ export var ObjectFlags: any;
3636
ObjectFlags[ObjectFlags["IsClassInstanceClone"] = 67108864] = "IsClassInstanceClone";
3737
ObjectFlags[ObjectFlags["IdenticalBaseTypeCalculated"] = 134217728] = "IdenticalBaseTypeCalculated";
3838
ObjectFlags[ObjectFlags["IdenticalBaseTypeExists"] = 268435456] = "IdenticalBaseTypeExists";
39-
ObjectFlags[ObjectFlags["UnresolvedMembers"] = 536870912] = "UnresolvedMembers";
40-
ObjectFlags[ObjectFlags["FromTypeNode"] = 1073741824] = "FromTypeNode";
39+
ObjectFlags[ObjectFlags["FromTypeNode"] = 536870912] = "FromTypeNode";
4140
ObjectFlags[ObjectFlags["IsGenericTypeComputed"] = 4194304] = "IsGenericTypeComputed";
4241
ObjectFlags[ObjectFlags["IsGenericObjectType"] = 8388608] = "IsGenericObjectType";
4342
ObjectFlags[ObjectFlags["IsGenericIndexType"] = 16777216] = "IsGenericIndexType";

‎tsc/internal/api/enum_values_generated.go‎

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tsc/internal/checker/checker.go‎

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,9 @@ type IterationTypesKey struct {
234234
// PropertiesTypesKey
235235

236236
type PropertiesTypesKey struct {
237-
typeId TypeId
238-
include TypeFlags
239-
includeOrigin bool
240-
unresolvedMembers bool
237+
typeId TypeId
238+
include TypeFlags
239+
includeOrigin bool
241240
}
242241

243242
// NonExistentPropertyKey
@@ -596,7 +595,7 @@ type Checker struct {
596595
SignatureCount uint32
597596
TotalInstantiationCount uint32
598597
instantiationCount uint32
599-
instantiationDepth uint32
598+
instantiationStack []*Type
600599
conditionalConstraintDepth uint32
601600
inlineLevel int
602601
serializationLevel int
@@ -4724,15 +4723,15 @@ basePropertyCheck:
47244723
c.error(errorNode, diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, memberInfo.typeName, missedProperty, memberInfo.baseTypeName)
47254724
}
47264725
case len(memberInfo.missedProperties) > 5:
4727-
missedProperties := strings.Join(core.Map(memberInfo.missedProperties[:4], func(prop string) string { return "'" + prop + "'" }), ", ")
4726+
missedProperties := quotedAndCommaSeparated(memberInfo.missedProperties[:4])
47284727
remainingMissedProperties := len(memberInfo.missedProperties) - 4
47294728
if ast.IsClassExpression(errorNode) {
47304729
c.error(errorNode, diagnostics.Non_abstract_class_expression_is_missing_implementations_for_the_following_members_of_0_Colon_1_and_2_more, memberInfo.baseTypeName, missedProperties, remainingMissedProperties)
47314730
} else {
47324731
c.error(errorNode, diagnostics.Non_abstract_class_0_is_missing_implementations_for_the_following_members_of_1_Colon_2_and_3_more, memberInfo.typeName, memberInfo.baseTypeName, missedProperties, remainingMissedProperties)
47334732
}
47344733
default:
4735-
missedProperties := strings.Join(core.Map(memberInfo.missedProperties, func(prop string) string { return "'" + prop + "'" }), ", ")
4734+
missedProperties := quotedAndCommaSeparated(memberInfo.missedProperties)
47364735
if ast.IsClassExpression(errorNode) {
47374736
c.error(errorNode, diagnostics.Non_abstract_class_expression_is_missing_implementations_for_the_following_members_of_0_Colon_1, memberInfo.baseTypeName, missedProperties)
47384737
} else {
@@ -19470,9 +19469,7 @@ func (c *Checker) resolveObjectTypeMembers(t *Type, source *Type, typeParameters
1947019469
if !instantiated {
1947119470
members = maps.Clone(members)
1947219471
}
19473-
c.setStructuredTypeMembers(t, members, callSignatures, constructSignatures, indexInfos)
1947419472
thisArgument := core.LastOrNil(typeArguments)
19475-
t.objectFlags |= ObjectFlagsUnresolvedMembers
1947619473
for _, baseType := range baseTypes {
1947719474
instantiatedBaseType := baseType
1947819475
if thisArgument != nil {
@@ -19491,7 +19488,6 @@ func (c *Checker) resolveObjectTypeMembers(t *Type, source *Type, typeParameters
1949119488
return findIndexInfo(indexInfos, info.keyType) == nil
1949219489
}))
1949319490
}
19494-
t.objectFlags &^= ObjectFlagsUnresolvedMembers
1949519491
}
1949619492
c.setStructuredTypeMembers(t, members, callSignatures, constructSignatures, indexInfos)
1949719493
}
@@ -22503,14 +22499,22 @@ func (c *Checker) instantiateTypeWithAlias(t *Type, m *TypeMapper, alias *TypeAl
2250322499
if t == nil || m == nil || !(c.couldContainTypeVariables(t) || (t.alias != nil && len(t.alias.typeArguments) > 0 && core.Some(t.alias.typeArguments, c.couldContainTypeVariables))) {
2250422500
return t
2250522501
}
22506-
if c.instantiationDepth == 100 || c.instantiationCount >= 5_000_000 {
22502+
if len(c.instantiationStack) == 100 || c.instantiationCount >= 5_000_000 {
2250722503
// We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement
2250822504
// or expression. There is a very high likelihood we're dealing with a combination of infinite generic types
2250922505
// that perpetually generate new type identities, so we stop the recursion here by yielding the error type.
2251022506
if tr := c.tracer; tr != nil {
22511-
tr.Instant(tracing.PhaseCheckTypes, "instantiateType_DepthLimit", map[string]any{"typeId": t.id, "instantiationDepth": c.instantiationDepth, "instantiationCount": c.instantiationCount})
22507+
tr.Instant(tracing.PhaseCheckTypes, "instantiateType_DepthLimit", map[string]any{"typeId": t.id, "instantiationDepth": len(c.instantiationStack), "instantiationCount": c.instantiationCount})
22508+
}
22509+
circularTypeNames := c.getCircularTypeNames()
22510+
switch {
22511+
case len(circularTypeNames) == 1:
22512+
c.error(c.currentNode, diagnostics.Instantiations_of_type_0_appear_infinitely_circular, circularTypeNames[0])
22513+
case len(circularTypeNames) > 1:
22514+
c.error(c.currentNode, diagnostics.Instantiations_of_the_following_types_appear_infinitely_circular_Colon_0, quotedAndCommaSeparated(circularTypeNames))
22515+
default:
22516+
c.error(c.currentNode, diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite)
2251222517
}
22513-
c.error(c.currentNode, diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite)
2251422518
return c.errorType
2251522519
}
2251622520
index := c.findActiveMapper(m)
@@ -22527,17 +22531,38 @@ func (c *Checker) instantiateTypeWithAlias(t *Type, m *TypeMapper, alias *TypeAl
2252722531
}
2252822532
c.TotalInstantiationCount++
2252922533
c.instantiationCount++
22530-
c.instantiationDepth++
22534+
c.instantiationStack = append(c.instantiationStack, t)
2253122535
result := c.instantiateTypeWorker(t, m, alias)
2253222536
if index == -1 {
2253322537
c.popActiveMapper()
2253422538
} else {
2253522539
cache[key] = result
2253622540
}
22537-
c.instantiationDepth--
22541+
c.instantiationStack[len(c.instantiationStack)-1] = nil
22542+
c.instantiationStack = c.instantiationStack[:len(c.instantiationStack)-1]
2253822543
return result
2253922544
}
2254022545

22546+
func (c *Checker) getCircularTypeNames() []string {
22547+
typeCounts := make(map[*Type]int)
22548+
var circularTypeNames []string
22549+
for _, t := range c.instantiationStack {
22550+
typeCounts[t] = typeCounts[t] + 1
22551+
if typeCounts[t] == 3 {
22552+
symbol := t.symbol
22553+
if t.alias != nil {
22554+
symbol = t.alias.symbol
22555+
}
22556+
if symbol != nil && len(symbol.Name) != 0 && symbol.Name[0] != '\xFE' {
22557+
if name := c.SymbolToString(symbol); !slices.Contains(circularTypeNames, name) {
22558+
circularTypeNames = append(circularTypeNames, name)
22559+
}
22560+
}
22561+
}
22562+
}
22563+
return circularTypeNames
22564+
}
22565+
2254122566
func (c *Checker) pushActiveMapper(mapper *TypeMapper) {
2254222567
c.activeMappers = append(c.activeMappers, mapper)
2254322568

@@ -27155,7 +27180,7 @@ func (c *Checker) getExtractStringType(t *Type) *Type {
2715527180
}
2715627181

2715727182
func (c *Checker) getLiteralTypeFromProperties(t *Type, include TypeFlags, includeOrigin bool) *Type {
27158-
key := PropertiesTypesKey{typeId: t.id, include: include, includeOrigin: includeOrigin, unresolvedMembers: t.objectFlags&ObjectFlagsUnresolvedMembers != 0}
27183+
key := PropertiesTypesKey{typeId: t.id, include: include, includeOrigin: includeOrigin}
2715927184
if cached, ok := c.propertiesTypes[key]; ok {
2716027185
return cached
2716127186
}

‎tsc/internal/checker/types.go‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -641,8 +641,7 @@ const (
641641
// Flags that require TypeFlags.Object and ObjectFlags.Reference
642642
ObjectFlagsIdenticalBaseTypeCalculated = 1 << 27 // has had `getSingleBaseForNonAugmentingSubtype` invoked on it already
643643
ObjectFlagsIdenticalBaseTypeExists = 1 << 28 // has a defined cachedEquivalentBaseType member
644-
ObjectFlagsUnresolvedMembers = 1 << 29 // Member resolution in process
645-
ObjectFlagsFromTypeNode = 1 << 30 // Originates in resolution of AST type node
644+
ObjectFlagsFromTypeNode = 1 << 29 // Originates in resolution of AST type node
646645
// Flags that require TypeFlags.UnionOrIntersection or TypeFlags.Substitution
647646
ObjectFlagsIsGenericTypeComputed = 1 << 22 // IsGenericObjectType flag has been computed
648647
ObjectFlagsIsGenericObjectType = 1 << 23 // Union or intersection contains generic object type

‎tsc/internal/checker/utilities.go‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1919,3 +1919,7 @@ func GetSetAccessorValueParameter(accessor *ast.Node) *ast.Node {
19191919
}
19201920
return nil
19211921
}
1922+
1923+
func quotedAndCommaSeparated(items []string) string {
1924+
return strings.Join(core.Map(items, func(item string) string { return "'" + item + "'" }), ", ")
1925+
}

‎tsc/internal/diagnostics/diagnosticMessages.generated.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,8 @@
11921192
"Visit_https_Colon_Slash_Slashaka_ms_Slashts6_for_migration_information_5111": "Visit https://aka.ms/ts6 for migration information.",
11931193
"tsconfig_json_is_present_but_will_not_be_loaded_if_files_are_specified_on_commandline_Use_ignoreConf_5112": "tsconfig.json is present but will not be loaded if files are specified on commandline. Use '--ignoreConfig' to skip this error.",
11941194
"_0_is_only_assignable_to_the_non_distributed_1_but_1_has_been_distributed_here_5113": "'{0}' is only assignable to the non-distributed '{1}', but '{1}' has been distributed here.",
1195+
"Instantiations_of_type_0_appear_infinitely_circular_5114": "Instantiations of type '{0}' appear infinitely circular.",
1196+
"Instantiations_of_the_following_types_appear_infinitely_circular_Colon_0_5115": "Instantiations of the following types appear infinitely circular: {0}.",
11951197
"Generates_a_sourcemap_for_each_corresponding_d_ts_file_6000": "Generates a sourcemap for each corresponding '.d.ts' file.",
11961198
"Concatenate_and_emit_output_to_single_file_6001": "Concatenate and emit output to single file.",
11971199
"Generates_corresponding_d_ts_file_6002": "Generates corresponding '.d.ts' file.",

‎tsc/internal/diagnostics/diagnosticMessages.json‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4784,6 +4784,14 @@
47844784
"category": "Error",
47854785
"code": 5113
47864786
},
4787+
"Instantiations of type '{0}' appear infinitely circular.": {
4788+
"category": "Error",
4789+
"code": 5114
4790+
},
4791+
"Instantiations of the following types appear infinitely circular: {0}.": {
4792+
"category": "Error",
4793+
"code": 5115
4794+
},
47874795

47884796
"Generates a sourcemap for each corresponding '.d.ts' file.": {
47894797
"category": "Message",

‎tsc/internal/diagnostics/diagnostics_generated.go‎

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package fourslash_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/microsoft/TypeScript/tsc/internal/fourslash"
7+
"github.com/microsoft/TypeScript/tsc/internal/testutil"
8+
)
9+
10+
func TestNoGhostErrors(t *testing.T) {
11+
t.Parallel()
12+
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
13+
const content = `
14+
interface ZodType<T> {
15+
optional: "true" | "false";
16+
output: T;
17+
}
18+
19+
interface ZodString extends ZodType<string> {
20+
optional: "false";
21+
}
22+
23+
type ZodShape = Record<string, any>;
24+
type Prettify<T> = { [K in keyof T]: T[K] } & {};
25+
type InferObjectType<Shape extends ZodShape> = Prettify<
26+
{
27+
[k in keyof Shape as Shape[k] extends { optional: "true" }
28+
? k
29+
: never]?: Shape[k]["output"];
30+
} & {
31+
[k in keyof Shape as Shape[k] extends { optional: "true" }
32+
? never
33+
: k]: Shape[k]["output"];
34+
}
35+
>;
36+
interface ZodObject<T extends ZodShape> extends ZodType<InferObjectType<T>> {
37+
optional: "false";
38+
}
39+
40+
interface ZodOptional<T extends ZodType<any>>
41+
extends ZodType<T["output"] | undefined> {
42+
optional: "true";
43+
}
44+
45+
declare function object<T extends ZodShape>(shape: T): ZodObject<T>;
46+
declare function string(): ZodString;
47+
declare function optional<T extends ZodType<any>>(schema: T): ZodOptional<T>;
48+
49+
const Category = object({
50+
name: string(),
51+
get parent/*1*/() {
52+
return optional(Category);
53+
},
54+
});
55+
56+
export const output = Category.output;`
57+
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
58+
defer done()
59+
f.VerifyQuickInfoAt(t, "1", "(accessor) parent: ZodOptional<ZodObject<{\n name: ZodString;\n readonly parent: ZodOptional<ZodObject<...>>;\n}>>", "")
60+
f.VerifyDiagnostics(t, nil)
61+
}

0 commit comments

Comments
 (0)