Skip to content

Commit fede84e

Browse files
authored
const modifier on type parameters (#51865)
* `const` modifier on type parameters + revised contextual type logic * Accept new baselines * Fix modifier checking * Add tests * Cache isConstTypeVariable check * Revert "Cache isConstTypeVariable check" This reverts commit f8fd1fd29f7975fcc3aeac8675c2cb107da33065. * Fewer isConstTypeParameterContext checks * Pay attention to cached `undefined` contextual type * Allow `const` modifier in more places + properly print back * Also permit `const` in method signature type parameters * Fix parsing of `const` modifier in array expression type parameters * Accept new baselines * Remove unused properties from NodeLinks * Rename `permitInvalidConstAsModifier` to `permitConstAsModifier`
1 parent 2484390 commit fede84e

10 files changed

+948
-113
lines changed

src/compiler/checker.ts

+111-101
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,10 @@
907907
"category": "Error",
908908
"code": 1276
909909
},
910+
"'{0}' modifier can only appear on a type parameter of a function, method or class": {
911+
"category": "Error",
912+
"code": 1277
913+
},
910914

911915
"'with' statements are not allowed in an async function block.": {
912916
"category": "Error",

src/compiler/parser.ts

+10-9
Original file line numberDiff line numberDiff line change
@@ -2816,7 +2816,7 @@ namespace Parser {
28162816
case ParsingContext.ArrayBindingElements:
28172817
return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isBindingIdentifierOrPrivateIdentifierOrPattern();
28182818
case ParsingContext.TypeParameters:
2819-
return token() === SyntaxKind.InKeyword || isIdentifier();
2819+
return token() === SyntaxKind.InKeyword || token() === SyntaxKind.ConstKeyword || isIdentifier();
28202820
case ParsingContext.ArrayLiteralMembers:
28212821
switch (token()) {
28222822
case SyntaxKind.CommaToken:
@@ -3825,7 +3825,7 @@ namespace Parser {
38253825

38263826
function parseTypeParameter(): TypeParameterDeclaration {
38273827
const pos = getNodePos();
3828-
const modifiers = parseModifiers();
3828+
const modifiers = parseModifiers(/*permitConstAsModifier*/ true);
38293829
const name = parseIdentifier();
38303830
let constraint: TypeNode | undefined;
38313831
let expression: Expression | undefined;
@@ -5205,13 +5205,14 @@ namespace Parser {
52055205

52065206
// If we have "<" not followed by an identifier,
52075207
// then this definitely is not an arrow function.
5208-
if (!isIdentifier()) {
5208+
if (!isIdentifier() && token() !== SyntaxKind.ConstKeyword) {
52095209
return Tristate.False;
52105210
}
52115211

52125212
// JSX overrides
52135213
if (languageVariant === LanguageVariant.JSX) {
52145214
const isArrowFunctionInJsx = lookAhead(() => {
5215+
parseOptional(SyntaxKind.ConstKeyword);
52155216
const third = nextToken();
52165217
if (third === SyntaxKind.ExtendsKeyword) {
52175218
const fourth = nextToken();
@@ -7703,11 +7704,11 @@ namespace Parser {
77037704
return list && createNodeArray(list, pos);
77047705
}
77057706

7706-
function tryParseModifier(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean, hasSeenStaticModifier?: boolean): Modifier | undefined {
7707+
function tryParseModifier(permitConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean, hasSeenStaticModifier?: boolean): Modifier | undefined {
77077708
const pos = getNodePos();
77087709
const kind = token();
77097710

7710-
if (token() === SyntaxKind.ConstKeyword && permitInvalidConstAsModifier) {
7711+
if (token() === SyntaxKind.ConstKeyword && permitConstAsModifier) {
77117712
// We need to ensure that any subsequent modifiers appear on the same line
77127713
// so that when 'const' is a standalone declaration, we don't issue an error.
77137714
if (!tryParse(nextTokenIsOnSameLineAndCanFollowModifier)) {
@@ -7742,12 +7743,12 @@ namespace Parser {
77427743
* In those situations, if we are entirely sure that 'const' is not valid on its own (such as when ASI takes effect
77437744
* and turns it into a standalone declaration), then it is better to parse it and report an error later.
77447745
*
7745-
* In such situations, 'permitInvalidConstAsModifier' should be set to true.
7746+
* In such situations, 'permitConstAsModifier' should be set to true.
77467747
*/
7747-
function parseModifiers(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean): NodeArray<Modifier> | undefined {
7748+
function parseModifiers(permitConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean): NodeArray<Modifier> | undefined {
77487749
const pos = getNodePos();
77497750
let list, modifier, hasSeenStatic = false;
7750-
while (modifier = tryParseModifier(permitInvalidConstAsModifier, stopOnStartOfClassStaticBlock, hasSeenStatic)) {
7751+
while (modifier = tryParseModifier(permitConstAsModifier, stopOnStartOfClassStaticBlock, hasSeenStatic)) {
77517752
if (modifier.kind === SyntaxKind.StaticKeyword) hasSeenStatic = true;
77527753
list = append(list, modifier);
77537754
}
@@ -7774,7 +7775,7 @@ namespace Parser {
77747775

77757776
const hasJSDoc = hasPrecedingJSDocComment();
77767777
const decorators = parseDecorators();
7777-
const modifiers = parseModifiers(/*permitInvalidConstAsModifier*/ true, /*stopOnStartOfClassStaticBlock*/ true);
7778+
const modifiers = parseModifiers(/*permitConstAsModifier*/ true, /*stopOnStartOfClassStaticBlock*/ true);
77787779
if (token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) {
77797780
return parseClassStaticBlockDeclaration(pos, hasJSDoc, decorators, modifiers);
77807781
}

src/compiler/types.ts

-3
Original file line numberDiff line numberDiff line change
@@ -5985,9 +5985,6 @@ export interface NodeLinks {
59855985
skipDirectInference?: true; // Flag set by the API `getContextualType` call on a node when `Completions` is passed to force the checker to skip making inferences to a node's type
59865986
declarationRequiresScopeChange?: boolean; // Set by `useOuterVariableScopeInParameter` in checker when downlevel emit would change the name resolution scope inside of a parameter.
59875987
serializedTypes?: Map<string, SerializedTypeEntry>; // Collection of types serialized at this location
5988-
5989-
contextualType?: Type; // Used to temporarily assign a contextual type during overload resolution
5990-
inferenceContext?: InferenceContext; // Inference context for contextual type
59915988
}
59925989

59935990
/** @internal */

tests/baselines/reference/tsserver/plugins/getSupportedCodeFixes-can-be-proxied.js

+3
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,7 @@ Info 32 [00:01:13.000] response:
623623
"1274",
624624
"1275",
625625
"1276",
626+
"1277",
626627
"1300",
627628
"1309",
628629
"1313",
@@ -1952,6 +1953,7 @@ Info 38 [00:01:19.000] response:
19521953
"1274",
19531954
"1275",
19541955
"1276",
1956+
"1277",
19551957
"1300",
19561958
"1309",
19571959
"1313",
@@ -3193,6 +3195,7 @@ Info 40 [00:01:21.000] response:
31933195
"1274",
31943196
"1275",
31953197
"1276",
3198+
"1277",
31963199
"1300",
31973200
"1309",
31983201
"1313",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiers.ts(43,14): error TS1277: 'const' modifier can only appear on a type parameter of a function, method or class
2+
tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiers.ts(49,9): error TS1277: 'const' modifier can only appear on a type parameter of a function, method or class
3+
4+
5+
==== tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiers.ts (2 errors) ====
6+
declare function f1<const T>(x: T): T;
7+
8+
const x11 = f1('a');
9+
const x12 = f1(['a', ['b', 'c']]);
10+
const x13 = f1({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
11+
12+
declare function f2<const T, U>(x: T | undefined): T;
13+
14+
const x21 = f2('a');
15+
const x22 = f2(['a', ['b', 'c']]);
16+
const x23 = f2({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
17+
18+
declare function f3<const T>(x: T): T[];
19+
20+
const x31 = f3("hello");
21+
const x32 = f3("hello");
22+
23+
declare function f4<const T>(obj: [T, T]): T;
24+
25+
const x41 = f4([[1, 'x'], [2, 'y']]);
26+
const x42 = f4([{ a: 1, b: 'x' }, { a: 2, b: 'y' }]);
27+
28+
declare function f5<const T>(obj: { x: T, y: T }): T;
29+
30+
const x51 = f5({ x: [1, 'x'], y: [2, 'y'] });
31+
const x52 = f5({ x: { a: 1, b: 'x' }, y: { a: 2, b: 'y' } });
32+
33+
declare function f6<const T extends readonly unknown[]>(...args: T): T;
34+
35+
const x61 = f6(1, 'b', { a: 1, b: 'x' });
36+
37+
class C1<const T> {
38+
constructor(x: T) {}
39+
foo<const U>(x: U) { return x; }
40+
}
41+
42+
const c71 = new C1({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
43+
const c72 = c71.foo(['a', ['b', 'c']]);
44+
45+
const fx1 = <const T>(x: T) => x;
46+
const fx2 = <const T,>(x: T) => x;
47+
48+
interface I1<const T> { x: T } // Error
49+
~~~~~
50+
!!! error TS1277: 'const' modifier can only appear on a type parameter of a function, method or class
51+
52+
interface I2 {
53+
f<const T>(x: T): T;
54+
}
55+
56+
type T1<const T> = T; // Error
57+
~~~~~
58+
!!! error TS1277: 'const' modifier can only appear on a type parameter of a function, method or class
59+
60+
type T2 = <const T>(x: T) => T;
61+
type T3 = { <const T>(x: T): T };
62+
type T4 = new <const T>(x: T) => T;
63+
type T5 = { new <const T>(x: T): T };
64+
65+
// Corrected repro from #51745
66+
67+
type Obj = { a: { b: { c: "123" } } };
68+
69+
type GetPath<T, P> =
70+
P extends readonly [] ? T :
71+
P extends readonly [infer A extends keyof T, ...infer Rest] ? GetPath<T[A], Rest> :
72+
never;
73+
74+
function set<T, const P extends readonly string[]>(obj: T, path: P, value: GetPath<T, P>) {}
75+
76+
declare let obj: Obj;
77+
declare let value: "123";
78+
79+
set(obj, ['a', 'b', 'c'], value);
80+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//// [typeParameterConstModifiers.ts]
2+
declare function f1<const T>(x: T): T;
3+
4+
const x11 = f1('a');
5+
const x12 = f1(['a', ['b', 'c']]);
6+
const x13 = f1({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
7+
8+
declare function f2<const T, U>(x: T | undefined): T;
9+
10+
const x21 = f2('a');
11+
const x22 = f2(['a', ['b', 'c']]);
12+
const x23 = f2({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
13+
14+
declare function f3<const T>(x: T): T[];
15+
16+
const x31 = f3("hello");
17+
const x32 = f3("hello");
18+
19+
declare function f4<const T>(obj: [T, T]): T;
20+
21+
const x41 = f4([[1, 'x'], [2, 'y']]);
22+
const x42 = f4([{ a: 1, b: 'x' }, { a: 2, b: 'y' }]);
23+
24+
declare function f5<const T>(obj: { x: T, y: T }): T;
25+
26+
const x51 = f5({ x: [1, 'x'], y: [2, 'y'] });
27+
const x52 = f5({ x: { a: 1, b: 'x' }, y: { a: 2, b: 'y' } });
28+
29+
declare function f6<const T extends readonly unknown[]>(...args: T): T;
30+
31+
const x61 = f6(1, 'b', { a: 1, b: 'x' });
32+
33+
class C1<const T> {
34+
constructor(x: T) {}
35+
foo<const U>(x: U) { return x; }
36+
}
37+
38+
const c71 = new C1({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
39+
const c72 = c71.foo(['a', ['b', 'c']]);
40+
41+
const fx1 = <const T>(x: T) => x;
42+
const fx2 = <const T,>(x: T) => x;
43+
44+
interface I1<const T> { x: T } // Error
45+
46+
interface I2 {
47+
f<const T>(x: T): T;
48+
}
49+
50+
type T1<const T> = T; // Error
51+
52+
type T2 = <const T>(x: T) => T;
53+
type T3 = { <const T>(x: T): T };
54+
type T4 = new <const T>(x: T) => T;
55+
type T5 = { new <const T>(x: T): T };
56+
57+
// Corrected repro from #51745
58+
59+
type Obj = { a: { b: { c: "123" } } };
60+
61+
type GetPath<T, P> =
62+
P extends readonly [] ? T :
63+
P extends readonly [infer A extends keyof T, ...infer Rest] ? GetPath<T[A], Rest> :
64+
never;
65+
66+
function set<T, const P extends readonly string[]>(obj: T, path: P, value: GetPath<T, P>) {}
67+
68+
declare let obj: Obj;
69+
declare let value: "123";
70+
71+
set(obj, ['a', 'b', 'c'], value);
72+
73+
74+
//// [typeParameterConstModifiers.js]
75+
"use strict";
76+
var x11 = f1('a');
77+
var x12 = f1(['a', ['b', 'c']]);
78+
var x13 = f1({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
79+
var x21 = f2('a');
80+
var x22 = f2(['a', ['b', 'c']]);
81+
var x23 = f2({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
82+
var x31 = f3("hello");
83+
var x32 = f3("hello");
84+
var x41 = f4([[1, 'x'], [2, 'y']]);
85+
var x42 = f4([{ a: 1, b: 'x' }, { a: 2, b: 'y' }]);
86+
var x51 = f5({ x: [1, 'x'], y: [2, 'y'] });
87+
var x52 = f5({ x: { a: 1, b: 'x' }, y: { a: 2, b: 'y' } });
88+
var x61 = f6(1, 'b', { a: 1, b: 'x' });
89+
var C1 = /** @class */ (function () {
90+
function C1(x) {
91+
}
92+
C1.prototype.foo = function (x) { return x; };
93+
return C1;
94+
}());
95+
var c71 = new C1({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
96+
var c72 = c71.foo(['a', ['b', 'c']]);
97+
var fx1 = function (x) { return x; };
98+
var fx2 = function (x) { return x; };
99+
function set(obj, path, value) { }
100+
set(obj, ['a', 'b', 'c'], value);

0 commit comments

Comments
 (0)