Skip to content

Uniform Generics for Conditional Type Inference and Type Parameter Narrowing [Experiment] #30284

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

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9389679
Initial commit
jack-williams Dec 26, 2018
e8026c6
Add more uniform cases
jack-williams Dec 26, 2018
382e235
Undo indenting
jack-williams Dec 26, 2018
7422eff
Improve error
jack-williams Dec 26, 2018
57d184a
Narrow conditional types using uniform types
jack-williams Dec 29, 2018
485f5d0
WIP of equality case
jack-williams Jan 3, 2019
870bd2b
Merge branch 'master' into uniform-types
jack-williams Jan 30, 2019
e17d3c1
Merge branch 'master' into uniform-types
jack-williams Mar 8, 2019
9ffceee
Fix alias case
jack-williams Mar 8, 2019
de2a9ea
Remove equality constraint
jack-williams Mar 8, 2019
0c5fbc9
Infer conditional types
jack-williams Mar 8, 2019
4b178dd
Better error locations
jack-williams Mar 8, 2019
3404b0c
Cond -> If
jack-williams Mar 8, 2019
6998ccf
Merge branch 'master' into uniform-types
jack-williams Mar 8, 2019
3eb1a0f
Merge branch 'master' into uniform-types
jack-williams Mar 9, 2019
dab9944
Accept baselines
jack-williams Mar 9, 2019
cb08acb
Merge branch 'master' into uniform-types
jack-williams Mar 9, 2019
45b29db
Fix emitter
jack-williams Mar 9, 2019
e093a7d
Add If Completion
jack-williams Mar 9, 2019
2226e62
Fix lint
jack-williams Mar 9, 2019
c055d73
Better propagation
jack-williams Mar 9, 2019
e9e7673
Remove duplicate code
jack-williams Mar 9, 2019
e6a4b07
Add naive equality case
jack-williams Mar 10, 2019
2417138
Lint
jack-williams Mar 10, 2019
1c08f41
Accept baselines
jack-williams Mar 10, 2019
0b23e9f
Hack conditional type resolution
jack-williams Mar 10, 2019
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
178 changes: 173 additions & 5 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2593,7 +2593,10 @@
"category": "Error",
"code": 2751
},

"Type '{0}' does not satisfy uniformity constraint of type '{1}'. Values of type '{0}' do not behave identically under typeof": {
"category": "Error",
"code": 2752
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
"code": 4000
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1637,6 +1637,12 @@ namespace ts {

function emitTypeParameter(node: TypeParameterDeclaration) {
emit(node.name);
if (node.uniformityConstraint && node.uniformityConstraint & UniformityFlags.TypeOf) {
writePunctuation("!");
}
else if (node.uniformityConstraint && node.uniformityConstraint & UniformityFlags.Equality) {
writePunctuation("~");
}
if (node.constraint) {
writeSpace();
writeKeyword("extends");
Expand Down
8 changes: 5 additions & 3 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,19 +300,21 @@ namespace ts {

// Signature elements

export function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode) {
export function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode, uniformityConstraint?: UniformityFlags) {
const node = createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration;
node.name = asName(name);
node.constraint = constraint;
node.default = defaultType;
node.uniformityConstraint = uniformityConstraint;
return node;
}

export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined) {
export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined, uniformityConstraint?: UniformityFlags) {
return node.name !== name
|| node.constraint !== constraint
|| node.default !== defaultType
? updateNode(createTypeParameterDeclaration(name, constraint, defaultType), node)
|| node.uniformityConstraint !== uniformityConstraint
? updateNode(createTypeParameterDeclaration(name, constraint, defaultType, uniformityConstraint), node)
: node;
}

Expand Down
5 changes: 4 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace ts {
None = 0,
Yield = 1 << 0,
Await = 1 << 1,
Type = 1 << 2,
Type = 1 << 2,
IgnoreMissingOpenBrace = 1 << 4,
JSDoc = 1 << 5,
}
Expand Down Expand Up @@ -2415,6 +2415,9 @@ namespace ts {
function parseTypeParameter(): TypeParameterDeclaration {
const node = <TypeParameterDeclaration>createNode(SyntaxKind.TypeParameter);
node.name = parseIdentifier();
node.uniformityConstraint = 0;
node.uniformityConstraint |= parseOptional(SyntaxKind.ExclamationToken) ? UniformityFlags.TypeOf : 0;
node.uniformityConstraint |= parseOptional(SyntaxKind.TildeToken) ? UniformityFlags.Equality : 0;
if (parseOptional(SyntaxKind.ExtendsKeyword)) {
// It's not uncommon for people to write improper constraints to a generic. If the
// user writes a constraint that is an expression and not an actual type, then parse
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,7 @@ namespace ts {
}
case SyntaxKind.TypeParameter: {
if (isPrivateMethodTypeParameter(input) && (input.default || input.constraint)) {
return cleanup(updateTypeParameterDeclaration(input, input.name, /*constraint*/ undefined, /*defaultType*/ undefined));
return cleanup(updateTypeParameterDeclaration(input, input.name, /*constraint*/ undefined, /*defaultType*/ undefined, /*uniformityConstraint*/ undefined));
}
return cleanup(visitEachChild(input, visitDeclarationSubtree, context));
}
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,8 @@ namespace ts {

// For error recovery purposes.
expression?: Expression;

uniformityConstraint?: UniformityFlags;
}

export interface SignatureDeclarationBase extends NamedDeclaration, JSDocContainer {
Expand Down Expand Up @@ -4041,6 +4043,11 @@ namespace ts {
export interface EnumType extends Type {
}

export const enum UniformityFlags {
TypeOf = 1 << 0,
Equality = 1 << 1,
}

export const enum ObjectFlags {
Class = 1 << 0, // Class
Interface = 1 << 1, // Interface
Expand Down Expand Up @@ -4272,6 +4279,8 @@ namespace ts {
isThisType?: boolean;
/* @internal */
resolvedDefaultType?: Type;
/* @internal */
uniformityConstraint?: UniformityFlags;
}

// Indexed access types (TypeFlags.IndexedAccess)
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ namespace ts {
return updateTypeParameterDeclaration(<TypeParameterDeclaration>node,
visitNode((<TypeParameterDeclaration>node).name, visitor, isIdentifier),
visitNode((<TypeParameterDeclaration>node).constraint, visitor, isTypeNode),
visitNode((<TypeParameterDeclaration>node).default, visitor, isTypeNode));
visitNode((<TypeParameterDeclaration>node).default, visitor, isTypeNode),
(<TypeParameterDeclaration>node).uniformityConstraint);

case SyntaxKind.Parameter:
return updateParameter(<ParameterDeclaration>node,
Expand Down
1 change: 1 addition & 0 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4504,6 +4504,7 @@ namespace FourSlashInterface {
typeEntry("Required"),
typeEntry("Readonly"),
typeEntry("Pick"),
typeEntry("If"),
typeEntry("Record"),
typeEntry("Exclude"),
typeEntry("Extract"),
Expand Down
2 changes: 2 additions & 0 deletions src/lib/es5.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,8 @@ type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

type If<C, E, T, F> = [C] extends [E] ? T : F;

/**
* Construct a type with a set of properties K of type T
*/
Expand Down
9 changes: 7 additions & 2 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ declare namespace ts {
constraint?: TypeNode;
default?: TypeNode;
expression?: Expression;
uniformityConstraint?: UniformityFlags;
}
interface SignatureDeclarationBase extends NamedDeclaration, JSDocContainer {
kind: SignatureDeclaration["kind"];
Expand Down Expand Up @@ -2277,6 +2278,10 @@ declare namespace ts {
}
interface EnumType extends Type {
}
enum UniformityFlags {
TypeOf = 1,
Equality = 2
}
enum ObjectFlags {
Class = 1,
Interface = 2,
Expand Down Expand Up @@ -3757,8 +3762,8 @@ declare namespace ts {
function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier): QualifiedName;
function createComputedPropertyName(expression: Expression): ComputedPropertyName;
function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression): ComputedPropertyName;
function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode): TypeParameterDeclaration;
function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined): TypeParameterDeclaration;
function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode, uniformityConstraint?: UniformityFlags): TypeParameterDeclaration;
function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined, uniformityConstraint?: UniformityFlags): TypeParameterDeclaration;
function createParameter(decorators: ReadonlyArray<Decorator> | undefined, modifiers: ReadonlyArray<Modifier> | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): ParameterDeclaration;
function updateParameter(node: ParameterDeclaration, decorators: ReadonlyArray<Decorator> | undefined, modifiers: ReadonlyArray<Modifier> | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): ParameterDeclaration;
function createDecorator(expression: Expression): Decorator;
Expand Down
9 changes: 7 additions & 2 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ declare namespace ts {
constraint?: TypeNode;
default?: TypeNode;
expression?: Expression;
uniformityConstraint?: UniformityFlags;
}
interface SignatureDeclarationBase extends NamedDeclaration, JSDocContainer {
kind: SignatureDeclaration["kind"];
Expand Down Expand Up @@ -2277,6 +2278,10 @@ declare namespace ts {
}
interface EnumType extends Type {
}
enum UniformityFlags {
TypeOf = 1,
Equality = 2
}
enum ObjectFlags {
Class = 1,
Interface = 2,
Expand Down Expand Up @@ -3757,8 +3762,8 @@ declare namespace ts {
function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier): QualifiedName;
function createComputedPropertyName(expression: Expression): ComputedPropertyName;
function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression): ComputedPropertyName;
function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode): TypeParameterDeclaration;
function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined): TypeParameterDeclaration;
function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode, uniformityConstraint?: UniformityFlags): TypeParameterDeclaration;
function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined, uniformityConstraint?: UniformityFlags): TypeParameterDeclaration;
function createParameter(decorators: ReadonlyArray<Decorator> | undefined, modifiers: ReadonlyArray<Modifier> | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): ParameterDeclaration;
function updateParameter(node: ParameterDeclaration, decorators: ReadonlyArray<Decorator> | undefined, modifiers: ReadonlyArray<Modifier> | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): ParameterDeclaration;
function createDecorator(expression: Expression): Decorator;
Expand Down
14 changes: 7 additions & 7 deletions tests/baselines/reference/conditionalTypes1.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,10 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS
type T38<T> = [T] extends [{ a: string }] ? [T] extends [{ b: number }] ? T35<T> : never : never;

type Extends<T, U> = T extends U ? true : false;
type If<C extends boolean, T, F> = C extends true ? T : F;
type Not<C extends boolean> = If<C, false, true>;
type And<A extends boolean, B extends boolean> = If<A, B, false>;
type Or<A extends boolean, B extends boolean> = If<A, true, B>;
type IfC<C extends boolean, T, F> = C extends true ? T : F;
type Not<C extends boolean> = IfC<C, false, true>;
type And<A extends boolean, B extends boolean> = IfC<A, B, false>;
type Or<A extends boolean, B extends boolean> = IfC<A, true, B>;

type IsString<T> = Extends<T, string>;

Expand Down Expand Up @@ -420,9 +420,9 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS

function f50() {
type Eq<T, U> = T extends U ? U extends T ? true : false : false;
type If<S, T, U> = S extends false ? U : T;
type Omit<T extends object> = { [P in keyof T]: If<Eq<T[P], never>, never, P>; }[keyof T];
type Omit2<T extends object, U = never> = { [P in keyof T]: If<Eq<T[P], U>, never, P>; }[keyof T];
type IfC<S, T, U> = S extends false ? U : T;
type Omit<T extends object> = { [P in keyof T]: IfC<Eq<T[P], never>, never, P>; }[keyof T];
type Omit2<T extends object, U = never> = { [P in keyof T]: IfC<Eq<T[P], U>, never, P>; }[keyof T];
type A = Omit<{ a: void; b: never; }>; // 'a'
type B = Omit2<{ a: void; b: never; }>; // 'a'
}
Expand Down
22 changes: 11 additions & 11 deletions tests/baselines/reference/conditionalTypes1.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,10 @@ type T37<T> = T extends { b: number } ? T extends { a: string } ? T35<T> : never
type T38<T> = [T] extends [{ a: string }] ? [T] extends [{ b: number }] ? T35<T> : never : never;

type Extends<T, U> = T extends U ? true : false;
type If<C extends boolean, T, F> = C extends true ? T : F;
type Not<C extends boolean> = If<C, false, true>;
type And<A extends boolean, B extends boolean> = If<A, B, false>;
type Or<A extends boolean, B extends boolean> = If<A, true, B>;
type IfC<C extends boolean, T, F> = C extends true ? T : F;
type Not<C extends boolean> = IfC<C, false, true>;
type And<A extends boolean, B extends boolean> = IfC<A, B, false>;
type Or<A extends boolean, B extends boolean> = IfC<A, true, B>;

type IsString<T> = Extends<T, string>;

Expand Down Expand Up @@ -292,9 +292,9 @@ const f45 = <U>(value: T95<U>): T94<U> => value; // Error

function f50() {
type Eq<T, U> = T extends U ? U extends T ? true : false : false;
type If<S, T, U> = S extends false ? U : T;
type Omit<T extends object> = { [P in keyof T]: If<Eq<T[P], never>, never, P>; }[keyof T];
type Omit2<T extends object, U = never> = { [P in keyof T]: If<Eq<T[P], U>, never, P>; }[keyof T];
type IfC<S, T, U> = S extends false ? U : T;
type Omit<T extends object> = { [P in keyof T]: IfC<Eq<T[P], never>, never, P>; }[keyof T];
type Omit2<T extends object, U = never> = { [P in keyof T]: IfC<Eq<T[P], U>, never, P>; }[keyof T];
type A = Omit<{ a: void; b: never; }>; // 'a'
type B = Omit2<{ a: void; b: never; }>; // 'a'
}
Expand Down Expand Up @@ -591,10 +591,10 @@ declare type T38<T> = [T] extends [{
b: number;
}] ? T35<T> : never : never;
declare type Extends<T, U> = T extends U ? true : false;
declare type If<C extends boolean, T, F> = C extends true ? T : F;
declare type Not<C extends boolean> = If<C, false, true>;
declare type And<A extends boolean, B extends boolean> = If<A, B, false>;
declare type Or<A extends boolean, B extends boolean> = If<A, true, B>;
declare type IfC<C extends boolean, T, F> = C extends true ? T : F;
declare type Not<C extends boolean> = IfC<C, false, true>;
declare type And<A extends boolean, B extends boolean> = IfC<A, B, false>;
declare type Or<A extends boolean, B extends boolean> = IfC<A, true, B>;
declare type IsString<T> = Extends<T, string>;
declare type Q1 = IsString<number>;
declare type Q2 = IsString<"abc">;
Expand Down
Loading