Skip to content

const modifier on type parameters #51865

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

Merged
merged 15 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
195 changes: 111 additions & 84 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,10 @@
"category": "Error",
"code": 1276
},
"'{0}' modifier can only appear on a type parameter of a function, method or class": {
"category": "Error",
"code": 1277
},

"'with' statements are not allowed in an async function block.": {
"category": "Error",
Expand Down
8 changes: 4 additions & 4 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2814,7 +2814,7 @@ namespace Parser {
case ParsingContext.ArrayBindingElements:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the type parameter modifiers be available for arrow functions when JSX syntax is enabled? in/out also don't work - but it's kinda surprising that even an extra comma doesn't make those parseable:

const arrow = <T, >(a: T) => {} // ok
const varianceAnnotation = <out T, >(a: T) => {} // fails to parse
const constModifier = <const T, >(a: T) => {} // fails to parse

TS playground

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yeah, that doesn't look right.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now fixed.

return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isBindingIdentifierOrPrivateIdentifierOrPattern();
case ParsingContext.TypeParameters:
return token() === SyntaxKind.InKeyword || isIdentifier();
return token() === SyntaxKind.InKeyword || token() === SyntaxKind.ConstKeyword || isIdentifier();
case ParsingContext.ArrayLiteralMembers:
switch (token()) {
case SyntaxKind.CommaToken:
Expand Down Expand Up @@ -3823,7 +3823,7 @@ namespace Parser {

function parseTypeParameter(): TypeParameterDeclaration {
const pos = getNodePos();
const modifiers = parseModifiers();
const modifiers = parseModifiers(/*permitConstAsModifier*/ true);
const name = parseIdentifier();
let constraint: TypeNode | undefined;
let expression: Expression | undefined;
Expand Down Expand Up @@ -7742,10 +7742,10 @@ namespace Parser {
*
* In such situations, 'permitInvalidConstAsModifier' should be set to true.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* In such situations, 'permitInvalidConstAsModifier' should be set to true.
* In such situations, 'permitConstAsModifier' should be set to true.

*/
function parseModifiers(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean): NodeArray<Modifier> | undefined {
function parseModifiers(permitConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean): NodeArray<Modifier> | undefined {
const pos = getNodePos();
let list, modifier, hasSeenStatic = false;
while (modifier = tryParseModifier(permitInvalidConstAsModifier, stopOnStartOfClassStaticBlock, hasSeenStatic)) {
while (modifier = tryParseModifier(permitConstAsModifier, stopOnStartOfClassStaticBlock, hasSeenStatic)) {
if (modifier.kind === SyntaxKind.StaticKeyword) hasSeenStatic = true;
list = append(list, modifier);
}
Expand Down
14 changes: 7 additions & 7 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -902,8 +902,6 @@ export interface Node extends ReadonlyTextRange {
/** @internal */ localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes)
/** @internal */ flowNode?: FlowNode; // Associated FlowNode (initialized by binding)
/** @internal */ emitNode?: EmitNode; // Associated EmitNode (initialized by transforms)
/** @internal */ contextualType?: Type; // Used to temporarily assign a contextual type during overload resolution
/** @internal */ inferenceContext?: InferenceContext; // Inference context for contextual type
}

export interface JSDocContainer {
Expand Down Expand Up @@ -5924,22 +5922,24 @@ export const enum ObjectFlags {
/** @internal */
IsGenericIndexType = 1 << 23, // Union or intersection contains generic index type
/** @internal */
IsConstTypeVariable = 1 << 24, // Union or intersection contains const type parameter
/** @internal */
IsGenericType = IsGenericObjectType | IsGenericIndexType,

// Flags that require TypeFlags.Union
/** @internal */
ContainsIntersections = 1 << 24, // Union contains intersections
ContainsIntersections = 1 << 25, // Union contains intersections
/** @internal */
IsUnknownLikeUnionComputed = 1 << 25, // IsUnknownLikeUnion flag has been computed
IsUnknownLikeUnionComputed = 1 << 26, // IsUnknownLikeUnion flag has been computed
/** @internal */
IsUnknownLikeUnion = 1 << 26, // Union of null, undefined, and empty object type
IsUnknownLikeUnion = 1 << 27, // Union of null, undefined, and empty object type
/** @internal */

// Flags that require TypeFlags.Intersection
/** @internal */
IsNeverIntersectionComputed = 1 << 24, // IsNeverLike flag has been computed
IsNeverIntersectionComputed = 1 << 25, // IsNeverLike flag has been computed
/** @internal */
IsNeverIntersection = 1 << 25, // Intersection reduces to never
IsNeverIntersection = 1 << 26, // Intersection reduces to never
}

/** @internal */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ Info 32 [00:01:13.000] response:
"1274",
"1275",
"1276",
"1277",
"1300",
"1309",
"1313",
Expand Down Expand Up @@ -1943,6 +1944,7 @@ Info 38 [00:01:19.000] response:
"1274",
"1275",
"1276",
"1277",
"1300",
"1309",
"1313",
Expand Down Expand Up @@ -3175,6 +3177,7 @@ Info 40 [00:01:21.000] response:
"1274",
"1275",
"1276",
"1277",
"1300",
"1309",
"1313",
Expand Down
68 changes: 68 additions & 0 deletions tests/baselines/reference/typeParameterConstModifiers.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiers.ts(40,14): error TS1277: 'const' modifier can only appear on a type parameter of a function, method or class
tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiers.ts(42,9): error TS1277: 'const' modifier can only appear on a type parameter of a function, method or class


==== tests/cases/conformance/types/typeParameters/typeParameterLists/typeParameterConstModifiers.ts (2 errors) ====
declare function f1<const T>(x: T): T;

const x11 = f1('a');
const x12 = f1(['a', ['b', 'c']]);
const x13 = f1({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });

declare function f2<const T, U>(x: T | undefined): T;

const x21 = f2('a');
const x22 = f2(['a', ['b', 'c']]);
const x23 = f2({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });

declare function f3<const T>(x: T): T[];

const x31 = f3("hello");
const x32 = f3("hello");

declare function f4<const T>(obj: [T, T]): T;

const x41 = f4([[1, 'x'], [2, 'y']]);
const x42 = f4([{ a: 1, b: 'x' }, { a: 2, b: 'y' }]);

declare function f5<const T>(obj: { x: T, y: T }): T;

const x51 = f5({ x: [1, 'x'], y: [2, 'y'] });
const x52 = f5({ x: { a: 1, b: 'x' }, y: { a: 2, b: 'y' } });

declare function f6<const T extends readonly unknown[]>(...args: T): T;

const x61 = f6(1, 'b', { a: 1, b: 'x' });

class C1<const T> {
constructor(x: T) {}
foo<const U>(x: U) { return x; }
}

const c71 = new C1({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
const c72 = c71.foo(['a', ['b', 'c']]);

interface I1<const T> { x: T } // Error
~~~~~
!!! error TS1277: 'const' modifier can only appear on a type parameter of a function, method or class

type T1<const T> = T; // Error
~~~~~
!!! error TS1277: 'const' modifier can only appear on a type parameter of a function, method or class

// Corrected repro from #51745

type Obj = { a: { b: { c: "123" } } };

type GetPath<T, P> =
P extends readonly [] ? T :
P extends readonly [infer A extends keyof T, ...infer Rest] ? GetPath<T[A], Rest> :
never;

function set<T, const P extends readonly string[]>(obj: T, path: P, value: GetPath<T, P>) {}

declare let obj: Obj;
declare let value: "123";

set(obj, ['a', 'b', 'c'], value);

86 changes: 86 additions & 0 deletions tests/baselines/reference/typeParameterConstModifiers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//// [typeParameterConstModifiers.ts]
declare function f1<const T>(x: T): T;

const x11 = f1('a');
const x12 = f1(['a', ['b', 'c']]);
const x13 = f1({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });

declare function f2<const T, U>(x: T | undefined): T;

const x21 = f2('a');
const x22 = f2(['a', ['b', 'c']]);
const x23 = f2({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });

declare function f3<const T>(x: T): T[];

const x31 = f3("hello");
const x32 = f3("hello");

declare function f4<const T>(obj: [T, T]): T;

const x41 = f4([[1, 'x'], [2, 'y']]);
const x42 = f4([{ a: 1, b: 'x' }, { a: 2, b: 'y' }]);

declare function f5<const T>(obj: { x: T, y: T }): T;

const x51 = f5({ x: [1, 'x'], y: [2, 'y'] });
const x52 = f5({ x: { a: 1, b: 'x' }, y: { a: 2, b: 'y' } });

declare function f6<const T extends readonly unknown[]>(...args: T): T;

const x61 = f6(1, 'b', { a: 1, b: 'x' });

class C1<const T> {
constructor(x: T) {}
foo<const U>(x: U) { return x; }
}

const c71 = new C1({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
const c72 = c71.foo(['a', ['b', 'c']]);

interface I1<const T> { x: T } // Error

type T1<const T> = T; // Error

// Corrected repro from #51745

type Obj = { a: { b: { c: "123" } } };

type GetPath<T, P> =
P extends readonly [] ? T :
P extends readonly [infer A extends keyof T, ...infer Rest] ? GetPath<T[A], Rest> :
never;

function set<T, const P extends readonly string[]>(obj: T, path: P, value: GetPath<T, P>) {}

declare let obj: Obj;
declare let value: "123";

set(obj, ['a', 'b', 'c'], value);


//// [typeParameterConstModifiers.js]
"use strict";
var x11 = f1('a');
var x12 = f1(['a', ['b', 'c']]);
var x13 = f1({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
var x21 = f2('a');
var x22 = f2(['a', ['b', 'c']]);
var x23 = f2({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
var x31 = f3("hello");
var x32 = f3("hello");
var x41 = f4([[1, 'x'], [2, 'y']]);
var x42 = f4([{ a: 1, b: 'x' }, { a: 2, b: 'y' }]);
var x51 = f5({ x: [1, 'x'], y: [2, 'y'] });
var x52 = f5({ x: { a: 1, b: 'x' }, y: { a: 2, b: 'y' } });
var x61 = f6(1, 'b', { a: 1, b: 'x' });
var C1 = /** @class */ (function () {
function C1(x) {
}
C1.prototype.foo = function (x) { return x; };
return C1;
}());
var c71 = new C1({ a: 1, b: "c", d: ["e", 2, true, { f: "g" }] });
var c72 = c71.foo(['a', ['b', 'c']]);
function set(obj, path, value) { }
set(obj, ['a', 'b', 'c'], value);
Loading