Skip to content

Commit 0fccf68

Browse files
authored
Do not reuse input expression nodes to generate type nodes for contextually typed const array literal expressions (#3246)
1 parent 78488d6 commit 0fccf68

5 files changed

Lines changed: 175 additions & 1 deletion

File tree

internal/pseudochecker/lookup.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,9 @@ func (ch *PseudoChecker) typeFromArrayLiteral(node *ast.ArrayLiteralExpression)
444444
if !ch.canGetTypeFromArrayLiteral(node) {
445445
return NewPseudoTypeInferred(node.AsNode())
446446
}
447+
if IsInConstContext(node.AsNode()) && isContextuallyTyped(node.AsNode()) {
448+
return NewPseudoTypeInferred(node.AsNode()) // expr in an as const cast with a contextual type has variable readonly state, bail
449+
}
447450
// we are in a const context producing a tuple type, there are no spread elements
448451
results := make([]*PseudoType, 0, len(node.Elements.Nodes))
449452
for _, e := range node.Elements.Nodes {
@@ -672,7 +675,10 @@ func isContextuallyTyped(node *ast.Node) bool {
672675
if ast.IsCallExpression(n) {
673676
return true
674677
}
675-
if (ast.IsVariableParameterOrProperty(n) || ast.IsAssertionExpression(n)) && n.Type() != nil {
678+
if ast.IsSatisfiesExpression(n) {
679+
return true
680+
}
681+
if (ast.IsVariableParameterOrProperty(n) || ast.IsAssertionExpression(n)) && n.Type() != nil && !ast.IsConstAssertion(n) {
676682
return true
677683
}
678684
return ast.IsJsxElement(n) || ast.IsJsxExpression(n)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//// [tests/cases/compiler/declarationEmitAsConstSatisfiesNonReadonlyResult.ts] ////
2+
3+
//// [declarationEmitAsConstSatisfiesNonReadonlyResult.ts]
4+
export const obj = {
5+
array: [
6+
{ n: 1 },
7+
{ n: 2 }
8+
]
9+
} as const satisfies {array?: Readonly<{n: unknown}>[]}
10+
11+
declare function foo<const T extends {array?: Readonly<{n: unknown}>[]}>(arg: T): T;
12+
13+
export const call = foo({
14+
array: [
15+
{ n: 1 },
16+
{ n: 2 }
17+
]
18+
})
19+
20+
//// [declarationEmitAsConstSatisfiesNonReadonlyResult.js]
21+
export const obj = {
22+
array: [
23+
{ n: 1 },
24+
{ n: 2 }
25+
]
26+
};
27+
export const call = foo({
28+
array: [
29+
{ n: 1 },
30+
{ n: 2 }
31+
]
32+
});
33+
34+
35+
//// [declarationEmitAsConstSatisfiesNonReadonlyResult.d.ts]
36+
export declare const obj: {
37+
readonly array: [{
38+
readonly n: 1;
39+
}, {
40+
readonly n: 2;
41+
}];
42+
};
43+
export declare const call: {
44+
readonly array: [{
45+
readonly n: 1;
46+
}, {
47+
readonly n: 2;
48+
}];
49+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//// [tests/cases/compiler/declarationEmitAsConstSatisfiesNonReadonlyResult.ts] ////
2+
3+
=== declarationEmitAsConstSatisfiesNonReadonlyResult.ts ===
4+
export const obj = {
5+
>obj : Symbol(obj, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 0, 12))
6+
7+
array: [
8+
>array : Symbol(array, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 0, 20))
9+
10+
{ n: 1 },
11+
>n : Symbol(n, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 2, 9))
12+
13+
{ n: 2 }
14+
>n : Symbol(n, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 3, 9))
15+
16+
]
17+
} as const satisfies {array?: Readonly<{n: unknown}>[]}
18+
>const : Symbol(const)
19+
>array : Symbol(array, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 5, 22))
20+
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
21+
>n : Symbol(n, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 5, 40))
22+
23+
declare function foo<const T extends {array?: Readonly<{n: unknown}>[]}>(arg: T): T;
24+
>foo : Symbol(foo, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 5, 55))
25+
>T : Symbol(T, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 7, 21))
26+
>array : Symbol(array, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 7, 38))
27+
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
28+
>n : Symbol(n, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 7, 56))
29+
>arg : Symbol(arg, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 7, 73))
30+
>T : Symbol(T, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 7, 21))
31+
>T : Symbol(T, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 7, 21))
32+
33+
export const call = foo({
34+
>call : Symbol(call, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 9, 12))
35+
>foo : Symbol(foo, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 5, 55))
36+
37+
array: [
38+
>array : Symbol(array, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 9, 25))
39+
40+
{ n: 1 },
41+
>n : Symbol(n, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 11, 9))
42+
43+
{ n: 2 }
44+
>n : Symbol(n, Decl(declarationEmitAsConstSatisfiesNonReadonlyResult.ts, 12, 9))
45+
46+
]
47+
})
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//// [tests/cases/compiler/declarationEmitAsConstSatisfiesNonReadonlyResult.ts] ////
2+
3+
=== declarationEmitAsConstSatisfiesNonReadonlyResult.ts ===
4+
export const obj = {
5+
>obj : { readonly array: [{ readonly n: 1; }, { readonly n: 2; }]; }
6+
>{ array: [ { n: 1 }, { n: 2 } ]} as const satisfies {array?: Readonly<{n: unknown}>[]} : { readonly array: [{ readonly n: 1; }, { readonly n: 2; }]; }
7+
>{ array: [ { n: 1 }, { n: 2 } ]} as const : { readonly array: [{ readonly n: 1; }, { readonly n: 2; }]; }
8+
>{ array: [ { n: 1 }, { n: 2 } ]} : { readonly array: [{ readonly n: 1; }, { readonly n: 2; }]; }
9+
10+
array: [
11+
>array : [{ readonly n: 1; }, { readonly n: 2; }]
12+
>[ { n: 1 }, { n: 2 } ] : [{ readonly n: 1; }, { readonly n: 2; }]
13+
14+
{ n: 1 },
15+
>{ n: 1 } : { readonly n: 1; }
16+
>n : 1
17+
>1 : 1
18+
19+
{ n: 2 }
20+
>{ n: 2 } : { readonly n: 2; }
21+
>n : 2
22+
>2 : 2
23+
24+
]
25+
} as const satisfies {array?: Readonly<{n: unknown}>[]}
26+
>array : Readonly<{ n: unknown; }>[] | undefined
27+
>n : unknown
28+
29+
declare function foo<const T extends {array?: Readonly<{n: unknown}>[]}>(arg: T): T;
30+
>foo : <const T extends { array?: Readonly<{ n: unknown; }>[]; }>(arg: T) => T
31+
>array : Readonly<{ n: unknown; }>[] | undefined
32+
>n : unknown
33+
>arg : T
34+
35+
export const call = foo({
36+
>call : { readonly array: [{ readonly n: 1; }, { readonly n: 2; }]; }
37+
>foo({ array: [ { n: 1 }, { n: 2 } ]}) : { readonly array: [{ readonly n: 1; }, { readonly n: 2; }]; }
38+
>foo : <const T extends { array?: Readonly<{ n: unknown; }>[]; }>(arg: T) => T
39+
>{ array: [ { n: 1 }, { n: 2 } ]} : { array: [{ n: 1; }, { n: 2; }]; }
40+
41+
array: [
42+
>array : [{ n: 1; }, { n: 2; }]
43+
>[ { n: 1 }, { n: 2 } ] : [{ n: 1; }, { n: 2; }]
44+
45+
{ n: 1 },
46+
>{ n: 1 } : { n: 1; }
47+
>n : 1
48+
>1 : 1
49+
50+
{ n: 2 }
51+
>{ n: 2 } : { n: 2; }
52+
>n : 2
53+
>2 : 2
54+
55+
]
56+
})
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @declaration: true
2+
export const obj = {
3+
array: [
4+
{ n: 1 },
5+
{ n: 2 }
6+
]
7+
} as const satisfies {array?: Readonly<{n: unknown}>[]}
8+
9+
declare function foo<const T extends {array?: Readonly<{n: unknown}>[]}>(arg: T): T;
10+
11+
export const call = foo({
12+
array: [
13+
{ n: 1 },
14+
{ n: 2 }
15+
]
16+
})

0 commit comments

Comments
 (0)