Skip to content

Commit a4fd87e

Browse files
authored
Merge pull request #24347 from weswigham/constraint-fix
Check for keyof constraint type instead of syntactic check (#24098)
2 parents 3546503 + b60ad05 commit a4fd87e

File tree

5 files changed

+195
-15
lines changed

5 files changed

+195
-15
lines changed

src/compiler/checker.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20588,16 +20588,7 @@ namespace ts {
2058820588
return widened;
2058920589
}
2059020590

20591-
function isTypeParameterWithKeyofConstraint(type: Type) {
20592-
if (type.flags & TypeFlags.TypeParameter) {
20593-
const constraintDeclaration = getConstraintDeclaration(<TypeParameter>type);
20594-
return constraintDeclaration && constraintDeclaration.kind === SyntaxKind.TypeOperator &&
20595-
(<TypeOperatorNode>constraintDeclaration).operator === SyntaxKind.KeyOfKeyword;
20596-
}
20597-
return false;
20598-
}
20599-
20600-
function isLiteralOfContextualType(candidateType: Type, contextualType: Type): boolean {
20591+
function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean {
2060120592
if (contextualType) {
2060220593
if (contextualType.flags & TypeFlags.UnionOrIntersection) {
2060320594
const types = (<UnionType>contextualType).types;
@@ -20608,11 +20599,9 @@ namespace ts {
2060820599
// this a literal context for literals of that primitive type. For example, given a
2060920600
// type parameter 'T extends string', infer string literal types for T.
2061020601
const constraint = getBaseConstraintOfType(contextualType) || emptyObjectType;
20611-
return isTypeParameterWithKeyofConstraint(contextualType) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.UniqueESSymbol) ||
20612-
constraint.flags & TypeFlags.String && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
20613-
constraint.flags & TypeFlags.Number && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) ||
20614-
constraint.flags & TypeFlags.Boolean && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) ||
20615-
constraint.flags & TypeFlags.ESSymbol && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) ||
20602+
return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
20603+
maybeTypeOfKind(constraint, TypeFlags.Number) && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) ||
20604+
maybeTypeOfKind(constraint, TypeFlags.ESSymbol) && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) ||
2061620605
isLiteralOfContextualType(candidateType, constraint);
2061720606
}
2061820607
// If the contextual type is a literal of a particular primitive type, we consider this a
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//// [jsxInferenceProducesLiteralAsExpected.tsx]
2+
import React = require("react");
3+
type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
4+
class TestObject {
5+
a: string = '';
6+
b: number = 1;
7+
c: () => void = () => { };
8+
}
9+
interface TestProps<T> {
10+
model: T;
11+
foo: FunctionPropertyNames<T>;
12+
}
13+
function Test<T>(props: TestProps<T>) { return <></>; }
14+
const model = new TestObject();
15+
16+
const el1 = <Test model={model} foo="c" />;
17+
const el2 = <Test<TestObject> model={model} foo="c" />;
18+
19+
//// [jsxInferenceProducesLiteralAsExpected.js]
20+
"use strict";
21+
exports.__esModule = true;
22+
var React = require("react");
23+
var TestObject = /** @class */ (function () {
24+
function TestObject() {
25+
this.a = '';
26+
this.b = 1;
27+
this.c = function () { };
28+
}
29+
return TestObject;
30+
}());
31+
function Test(props) { return React.createElement(React.Fragment, null); }
32+
var model = new TestObject();
33+
var el1 = React.createElement(Test, { model: model, foo: "c" });
34+
var el2 = React.createElement(Test, { model: model, foo: "c" });
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
=== tests/cases/compiler/jsxInferenceProducesLiteralAsExpected.tsx ===
2+
import React = require("react");
3+
>React : Symbol(React, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 0, 0))
4+
5+
type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
6+
>FunctionPropertyNames : Symbol(FunctionPropertyNames, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 0, 32))
7+
>T : Symbol(T, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 1, 27))
8+
>K : Symbol(K, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 1, 35))
9+
>T : Symbol(T, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 1, 27))
10+
>T : Symbol(T, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 1, 27))
11+
>K : Symbol(K, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 1, 35))
12+
>Function : Symbol(Function, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
13+
>K : Symbol(K, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 1, 35))
14+
>T : Symbol(T, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 1, 27))
15+
16+
class TestObject {
17+
>TestObject : Symbol(TestObject, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 1, 95))
18+
19+
a: string = '';
20+
>a : Symbol(TestObject.a, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 2, 18))
21+
22+
b: number = 1;
23+
>b : Symbol(TestObject.b, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 3, 19))
24+
25+
c: () => void = () => { };
26+
>c : Symbol(TestObject.c, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 4, 18))
27+
}
28+
interface TestProps<T> {
29+
>TestProps : Symbol(TestProps, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 6, 1))
30+
>T : Symbol(T, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 7, 20))
31+
32+
model: T;
33+
>model : Symbol(TestProps.model, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 7, 24))
34+
>T : Symbol(T, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 7, 20))
35+
36+
foo: FunctionPropertyNames<T>;
37+
>foo : Symbol(TestProps.foo, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 8, 13))
38+
>FunctionPropertyNames : Symbol(FunctionPropertyNames, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 0, 32))
39+
>T : Symbol(T, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 7, 20))
40+
}
41+
function Test<T>(props: TestProps<T>) { return <></>; }
42+
>Test : Symbol(Test, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 10, 1))
43+
>T : Symbol(T, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 11, 14))
44+
>props : Symbol(props, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 11, 17))
45+
>TestProps : Symbol(TestProps, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 6, 1))
46+
>T : Symbol(T, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 11, 14))
47+
48+
const model = new TestObject();
49+
>model : Symbol(model, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 12, 5))
50+
>TestObject : Symbol(TestObject, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 1, 95))
51+
52+
const el1 = <Test model={model} foo="c" />;
53+
>el1 : Symbol(el1, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 14, 5))
54+
>Test : Symbol(Test, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 10, 1))
55+
>model : Symbol(model, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 14, 17))
56+
>model : Symbol(model, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 12, 5))
57+
>foo : Symbol(foo, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 14, 31))
58+
59+
const el2 = <Test<TestObject> model={model} foo="c" />;
60+
>el2 : Symbol(el2, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 15, 5))
61+
>Test : Symbol(Test, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 10, 1))
62+
>TestObject : Symbol(TestObject, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 1, 95))
63+
>model : Symbol(model, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 15, 29))
64+
>model : Symbol(model, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 12, 5))
65+
>foo : Symbol(foo, Decl(jsxInferenceProducesLiteralAsExpected.tsx, 15, 43))
66+
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
=== tests/cases/compiler/jsxInferenceProducesLiteralAsExpected.tsx ===
2+
import React = require("react");
3+
>React : typeof React
4+
5+
type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
6+
>FunctionPropertyNames : { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]
7+
>T : T
8+
>K : K
9+
>T : T
10+
>T : T
11+
>K : K
12+
>Function : Function
13+
>K : K
14+
>T : T
15+
16+
class TestObject {
17+
>TestObject : TestObject
18+
19+
a: string = '';
20+
>a : string
21+
>'' : ""
22+
23+
b: number = 1;
24+
>b : number
25+
>1 : 1
26+
27+
c: () => void = () => { };
28+
>c : () => void
29+
>() => { } : () => void
30+
}
31+
interface TestProps<T> {
32+
>TestProps : TestProps<T>
33+
>T : T
34+
35+
model: T;
36+
>model : T
37+
>T : T
38+
39+
foo: FunctionPropertyNames<T>;
40+
>foo : { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]
41+
>FunctionPropertyNames : { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]
42+
>T : T
43+
}
44+
function Test<T>(props: TestProps<T>) { return <></>; }
45+
>Test : <T>(props: TestProps<T>) => JSX.Element
46+
>T : T
47+
>props : TestProps<T>
48+
>TestProps : TestProps<T>
49+
>T : T
50+
><></> : JSX.Element
51+
52+
const model = new TestObject();
53+
>model : TestObject
54+
>new TestObject() : TestObject
55+
>TestObject : typeof TestObject
56+
57+
const el1 = <Test model={model} foo="c" />;
58+
>el1 : JSX.Element
59+
><Test model={model} foo="c" /> : JSX.Element
60+
>Test : <T>(props: TestProps<T>) => JSX.Element
61+
>model : TestObject
62+
>model : TestObject
63+
>foo : "c"
64+
65+
const el2 = <Test<TestObject> model={model} foo="c" />;
66+
>el2 : JSX.Element
67+
><Test<TestObject> model={model} foo="c" /> : JSX.Element
68+
>Test : <T>(props: TestProps<T>) => JSX.Element
69+
>TestObject : TestObject
70+
>model : TestObject
71+
>model : TestObject
72+
>foo : "c"
73+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @jsx: react
2+
// @libFiles: lib.d.ts,react.d.ts
3+
import React = require("react");
4+
type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
5+
class TestObject {
6+
a: string = '';
7+
b: number = 1;
8+
c: () => void = () => { };
9+
}
10+
interface TestProps<T> {
11+
model: T;
12+
foo: FunctionPropertyNames<T>;
13+
}
14+
function Test<T>(props: TestProps<T>) { return <></>; }
15+
const model = new TestObject();
16+
17+
const el1 = <Test model={model} foo="c" />;
18+
const el2 = <Test<TestObject> model={model} foo="c" />;

0 commit comments

Comments
 (0)