Skip to content

Rest type #13470

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 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
182 changes: 165 additions & 17 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions src/compiler/declarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,15 @@ namespace ts {
writeLine();
}

function emitRestType(node: RestTypeNode) {
write("rest(");
emitType(node.source);
write(",");
emitType(node.remove);
write(")");
writeLine();
}

function emitVariableDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration) {
// If we are emitting property it isn't moduleElement and hence we already know it needs to be emitted
// so there is no check needed to see if declaration is visible
Expand Down Expand Up @@ -1762,6 +1771,8 @@ namespace ts {
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return emitAccessorDeclaration(<AccessorDeclaration>node);
case SyntaxKind.RestType:
return emitRestType(node as RestTypeNode);
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
return emitPropertyDeclaration(<PropertyDeclaration>node);
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2043,6 +2043,14 @@
"category": "Error",
"code": 2704
},
"The right side of a rest type must be a string or string literal union.": {
"category": "Error",
"code": 2705
},
"Object rest element implicitly has type 'any' because the destructuring contains a computed property.": {
"category": "Error",
"code": 2706
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
39 changes: 29 additions & 10 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ namespace ts {
case SyntaxKind.UnionType:
case SyntaxKind.IntersectionType:
return visitNodes(cbNodes, (<UnionOrIntersectionTypeNode>node).types);
case SyntaxKind.RestType:
return visitNode(cbNode, (node as RestTypeNode).source) ||
visitNode(cbNode, (node as RestTypeNode).remove);
case SyntaxKind.ParenthesizedType:
case SyntaxKind.TypeOperator:
return visitNode(cbNode, (<ParenthesizedTypeNode | TypeOperatorNode>node).type);
Expand Down Expand Up @@ -1300,8 +1303,6 @@ namespace ts {
return token() === SyntaxKind.OpenBracketToken || isLiteralPropertyName();
case ParsingContext.ObjectLiteralMembers:
return token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.AsteriskToken || token() === SyntaxKind.DotDotDotToken || isLiteralPropertyName();
case ParsingContext.RestProperties:
return isLiteralPropertyName();
case ParsingContext.ObjectBindingElements:
return token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.DotDotDotToken || isLiteralPropertyName();
case ParsingContext.HeritageClauseElement:
Expand Down Expand Up @@ -1430,7 +1431,6 @@ namespace ts {
case ParsingContext.ArrayBindingElements:
return token() === SyntaxKind.CloseBracketToken;
case ParsingContext.Parameters:
case ParsingContext.RestProperties:
// Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery
return token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.CloseBracketToken /*|| token === SyntaxKind.OpenBraceToken*/;
case ParsingContext.TypeArguments:
Expand Down Expand Up @@ -1616,9 +1616,6 @@ namespace ts {
case ParsingContext.Parameters:
return isReusableParameter(node);

case ParsingContext.RestProperties:
return false;

// Any other lists we do not care about reusing nodes in. But feel free to add if
// you can do so safely. Danger areas involve nodes that may involve speculative
// parsing. If speculative parsing is involved with the node, then the range the
Expand Down Expand Up @@ -1816,7 +1813,6 @@ namespace ts {
case ParsingContext.BlockStatements: return Diagnostics.Declaration_or_statement_expected;
case ParsingContext.SwitchClauses: return Diagnostics.case_or_default_expected;
case ParsingContext.SwitchClauseStatements: return Diagnostics.Statement_expected;
case ParsingContext.RestProperties: // fallthrough
case ParsingContext.TypeMembers: return Diagnostics.Property_or_signature_expected;
case ParsingContext.ClassMembers: return Diagnostics.Unexpected_token_A_constructor_method_accessor_or_property_was_expected;
case ParsingContext.EnumMembers: return Diagnostics.Enum_member_expected;
Expand Down Expand Up @@ -2624,7 +2620,7 @@ namespace ts {
case SyntaxKind.KeyOfKeyword:
return parseTypeOperator(SyntaxKind.KeyOfKeyword);
}
return parseArrayTypeOrHigher();
return parseRestTypeOrHigher();
}

function parseUnionOrIntersectionType(kind: SyntaxKind, parseConstituentType: () => TypeNode, operator: SyntaxKind): TypeNode {
Expand All @@ -2643,7 +2639,26 @@ namespace ts {
return type;
}

function parseIntersectionTypeOrHigher(): TypeNode {
function parseRestTypeOrHigher() {
switch (token()) {
case SyntaxKind.RestKeyword:
return parseRestType();
}
return parseArrayTypeOrHigher();
}

function parseRestType(): TypeNode {
const node = createNode(SyntaxKind.RestType) as RestTypeNode;
parseExpected(SyntaxKind.RestKeyword);
parseExpected(SyntaxKind.OpenParenToken);
node.source = parseType();
parseExpected(SyntaxKind.CommaToken);
node.remove = parseType();
parseExpected(SyntaxKind.CloseParenToken);
return finishNode(node);
}

function parseIntersectionTypeOrHigher(): TypeNode {
return parseUnionOrIntersectionType(SyntaxKind.IntersectionType, parseTypeOperatorOrHigher, SyntaxKind.AmpersandToken);
}

Expand Down Expand Up @@ -4952,6 +4967,11 @@ namespace ts {
function parseObjectBindingElement(): BindingElement {
const node = <BindingElement>createNode(SyntaxKind.BindingElement);
node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
if (node.dotDotDotToken) {
node.name = parseIdentifierOrPattern();
return finishNode(node);
}

const tokenIsIdentifier = isIdentifier();
const propertyName = parsePropertyName();
if (tokenIsIdentifier && token() !== SyntaxKind.ColonToken) {
Expand Down Expand Up @@ -5896,7 +5916,6 @@ namespace ts {
JsxChildren, // Things between opening and closing JSX tags
ArrayLiteralMembers, // Members in array literal
Parameters, // Parameters in parameter list
RestProperties, // Property names in a rest type list
TypeParameters, // Type parameters in type parameter list
TypeArguments, // Type arguments in type argument list
TupleElementTypes, // Element types in tuple element type list
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ namespace ts {
"readonly": SyntaxKind.ReadonlyKeyword,
"require": SyntaxKind.RequireKeyword,
"global": SyntaxKind.GlobalKeyword,
"rest": SyntaxKind.RestKeyword,
"return": SyntaxKind.ReturnKeyword,
"set": SyntaxKind.SetKeyword,
"static": SyntaxKind.StaticKeyword,
Expand Down
14 changes: 14 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@
NeverKeyword,
ReadonlyKeyword,
RequireKeyword,
RestKeyword,
NumberKeyword,
ObjectKeyword,
SetKeyword,
Expand Down Expand Up @@ -236,6 +237,7 @@
UnionType,
IntersectionType,
ParenthesizedType,
RestType,
ThisType,
TypeOperator,
IndexedAccessType,
Expand Down Expand Up @@ -905,6 +907,12 @@
kind: SyntaxKind.IntersectionType;
}

export interface RestTypeNode extends TypeNode {
kind: SyntaxKind.RestType;
source: TypeNode;
remove: TypeNode;
}

export interface ParenthesizedTypeNode extends TypeNode {
kind: SyntaxKind.ParenthesizedType;
type: TypeNode;
Expand Down Expand Up @@ -2815,6 +2823,7 @@
/* @internal */
ContainsAnyFunctionType = 1 << 23, // Type is or contains object literal type
NonPrimitive = 1 << 24, // intrinsic object type
Rest = 1 << 25, // rest(T, U)

/* @internal */
Nullable = Undefined | Null,
Expand Down Expand Up @@ -2969,6 +2978,11 @@

export type StructuredType = ObjectType | UnionType | IntersectionType;

export interface RestType extends Type {
source: Type;
remove: Type; // should be a string, string literal union or never
}

/* @internal */
// An instantiated anonymous type has a target and a mapper
export interface AnonymousType extends ObjectType {
Expand Down
34 changes: 34 additions & 0 deletions tests/baselines/reference/differenceType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//// [differenceType.ts]
type A = { a };
type Ab = { a; b };
let nothing: rest(A, 'a');
let none: rest(Ab, 'a' | 'b');
let under: rest(Ab, 'a');
let empty: rest(Ab, 'a' | 'b');
let nope: rest({}, string);
let nope2: rest(Ab, string);

type Abcd = { a; b; c; d }

function f<T extends Abcd>(t: T) {
let tsubu: rest(T, 'b' | 'd');
return tsubu;
}

const explicit = f<Abcd>({ a: 1, b: 2, c: 3, d: 4 })
const inferred = f({ a: 1, b: 2, c: 3, d: 5 })


//// [differenceType.js]
var nothing;
var none;
var under;
var empty;
var nope;
var nope2;
function f(t) {
var tsubu;
return tsubu;
}
var explicit = f({ a: 1, b: 2, c: 3, d: 4 });
var inferred = f({ a: 1, b: 2, c: 3, d: 5 });
72 changes: 72 additions & 0 deletions tests/baselines/reference/differenceType.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
=== tests/cases/conformance/types/rest/differenceType.ts ===
type A = { a };
>A : Symbol(A, Decl(differenceType.ts, 0, 0))
>a : Symbol(a, Decl(differenceType.ts, 0, 10))

type Ab = { a; b };
>Ab : Symbol(Ab, Decl(differenceType.ts, 0, 15))
>a : Symbol(a, Decl(differenceType.ts, 1, 11))
>b : Symbol(b, Decl(differenceType.ts, 1, 14))

let nothing: rest(A, 'a');
>nothing : Symbol(nothing, Decl(differenceType.ts, 2, 3))
>A : Symbol(A, Decl(differenceType.ts, 0, 0))

let none: rest(Ab, 'a' | 'b');
>none : Symbol(none, Decl(differenceType.ts, 3, 3))
>Ab : Symbol(Ab, Decl(differenceType.ts, 0, 15))

let under: rest(Ab, 'a');
>under : Symbol(under, Decl(differenceType.ts, 4, 3))
>Ab : Symbol(Ab, Decl(differenceType.ts, 0, 15))

let empty: rest(Ab, 'a' | 'b');
>empty : Symbol(empty, Decl(differenceType.ts, 5, 3))
>Ab : Symbol(Ab, Decl(differenceType.ts, 0, 15))

let nope: rest({}, string);
>nope : Symbol(nope, Decl(differenceType.ts, 6, 3))

let nope2: rest(Ab, string);
>nope2 : Symbol(nope2, Decl(differenceType.ts, 7, 3))
>Ab : Symbol(Ab, Decl(differenceType.ts, 0, 15))

type Abcd = { a; b; c; d }
>Abcd : Symbol(Abcd, Decl(differenceType.ts, 7, 28))
>a : Symbol(a, Decl(differenceType.ts, 9, 13))
>b : Symbol(b, Decl(differenceType.ts, 9, 16))
>c : Symbol(c, Decl(differenceType.ts, 9, 19))
>d : Symbol(d, Decl(differenceType.ts, 9, 22))

function f<T extends Abcd>(t: T) {
>f : Symbol(f, Decl(differenceType.ts, 9, 26))
>T : Symbol(T, Decl(differenceType.ts, 11, 11))
>Abcd : Symbol(Abcd, Decl(differenceType.ts, 7, 28))
>t : Symbol(t, Decl(differenceType.ts, 11, 27))
>T : Symbol(T, Decl(differenceType.ts, 11, 11))

let tsubu: rest(T, 'b' | 'd');
>tsubu : Symbol(tsubu, Decl(differenceType.ts, 12, 7))
>T : Symbol(T, Decl(differenceType.ts, 11, 11))

return tsubu;
>tsubu : Symbol(tsubu, Decl(differenceType.ts, 12, 7))
}

const explicit = f<Abcd>({ a: 1, b: 2, c: 3, d: 4 })
>explicit : Symbol(explicit, Decl(differenceType.ts, 16, 5))
>f : Symbol(f, Decl(differenceType.ts, 9, 26))
>Abcd : Symbol(Abcd, Decl(differenceType.ts, 7, 28))
>a : Symbol(a, Decl(differenceType.ts, 16, 26))
>b : Symbol(b, Decl(differenceType.ts, 16, 32))
>c : Symbol(c, Decl(differenceType.ts, 16, 38))
>d : Symbol(d, Decl(differenceType.ts, 16, 44))

const inferred = f({ a: 1, b: 2, c: 3, d: 5 })
>inferred : Symbol(inferred, Decl(differenceType.ts, 17, 5))
>f : Symbol(f, Decl(differenceType.ts, 9, 26))
>a : Symbol(a, Decl(differenceType.ts, 17, 20))
>b : Symbol(b, Decl(differenceType.ts, 17, 26))
>c : Symbol(c, Decl(differenceType.ts, 17, 32))
>d : Symbol(d, Decl(differenceType.ts, 17, 38))

84 changes: 84 additions & 0 deletions tests/baselines/reference/differenceType.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
=== tests/cases/conformance/types/rest/differenceType.ts ===
type A = { a };
>A : A
>a : any

type Ab = { a; b };
>Ab : Ab
>a : any
>b : any

let nothing: rest(A, 'a');
>nothing : {}
>A : A

let none: rest(Ab, 'a' | 'b');
>none : {}
>Ab : Ab

let under: rest(Ab, 'a');
>under : { b: any; }
>Ab : Ab

let empty: rest(Ab, 'a' | 'b');
>empty : {}
>Ab : Ab

let nope: rest({}, string);
>nope : {}

let nope2: rest(Ab, string);
>nope2 : {}
>Ab : Ab

type Abcd = { a; b; c; d }
>Abcd : Abcd
>a : any
>b : any
>c : any
>d : any

function f<T extends Abcd>(t: T) {
>f : <T extends Abcd>(t: T) => rest(T, "b" | "d")
>T : T
>Abcd : Abcd
>t : T
>T : T

let tsubu: rest(T, 'b' | 'd');
>tsubu : rest(T, "b" | "d")
>T : T

return tsubu;
>tsubu : rest(T, "b" | "d")
}

const explicit = f<Abcd>({ a: 1, b: 2, c: 3, d: 4 })
>explicit : { a: any; c: any; }
>f<Abcd>({ a: 1, b: 2, c: 3, d: 4 }) : { a: any; c: any; }
>f : <T extends Abcd>(t: T) => rest(T, "b" | "d")
>Abcd : Abcd
>{ a: 1, b: 2, c: 3, d: 4 } : { a: number; b: number; c: number; d: number; }
>a : number
>1 : 1
>b : number
>2 : 2
>c : number
>3 : 3
>d : number
>4 : 4

const inferred = f({ a: 1, b: 2, c: 3, d: 5 })
>inferred : { a: number; c: number; }
>f({ a: 1, b: 2, c: 3, d: 5 }) : { a: number; c: number; }
>f : <T extends Abcd>(t: T) => rest(T, "b" | "d")
>{ a: 1, b: 2, c: 3, d: 5 } : { a: number; b: number; c: number; d: number; }
>a : number
>1 : 1
>b : number
>2 : 2
>c : number
>3 : 3
>d : number
>5 : 5

Loading