Skip to content
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

type-level function application #17961

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
080b391
add type-level function application
KiaraGrouwstra Aug 22, 2017
312b69e
type calls: add failing test
KiaraGrouwstra Aug 22, 2017
c98fe79
disambiguate type argument lists with empty <>
KiaraGrouwstra Aug 23, 2017
636b4ac
fix function name
KiaraGrouwstra Aug 23, 2017
a76ba38
show issues: overload selection (#17471), composition
KiaraGrouwstra Aug 24, 2017
187b6b5
minimum chaining repro + checks, errors
KiaraGrouwstra Aug 25, 2017
ee110a3
allow errors on artificial nodes, empty type args
KiaraGrouwstra Aug 25, 2017
7a0ec45
Merge branch 'master' into 6606-type-level-function-application
KiaraGrouwstra Aug 26, 2017
fa39af3
switch parent linking to built-in fn.. how to import though?
KiaraGrouwstra Aug 26, 2017
6591541
fix some tests
KiaraGrouwstra Sep 3, 2017
4b2e8a1
give type calls own type, fixes eager eval
KiaraGrouwstra Sep 6, 2017
2f2e800
fix other tests
KiaraGrouwstra Sep 8, 2017
c54eea7
drop type arg support, works fine but not worth it
KiaraGrouwstra Sep 9, 2017
0e4c359
resolve type call, generalize functions to fix synthetic node workaround
KiaraGrouwstra Sep 9, 2017
c70ea67
factor out function
KiaraGrouwstra Sep 9, 2017
f730a07
drop index type... not needed here unlike for gcnew's `!`?
KiaraGrouwstra Sep 9, 2017
9bbae94
add tests
KiaraGrouwstra Sep 9, 2017
28aece0
tests: reduce/map WIP
KiaraGrouwstra Sep 9, 2017
4d14c9f
add use-case: type substraction / assertion (`!`)
KiaraGrouwstra Sep 9, 2017
a8fc55a
use-case: `promised` (WIP, breaks on unions)
KiaraGrouwstra Sep 9, 2017
ccc55d1
add tests
KiaraGrouwstra Sep 11, 2017
a7882c2
add error tests in own file
KiaraGrouwstra Sep 11, 2017
e67a82b
simplify out unused bits
KiaraGrouwstra Sep 11, 2017
5decbbc
directly pass arg types for type calls, fixes compose
KiaraGrouwstra Sep 11, 2017
d602415
union-proof type calls, fixes `awaited` use-case from #17077
KiaraGrouwstra Sep 11, 2017
e131686
fix heterogeneous map test (add type param)
KiaraGrouwstra Sep 11, 2017
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
1 change: 1 addition & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3366,6 +3366,7 @@ namespace ts {
case SyntaxKind.ThisType:
case SyntaxKind.TypeOperator:
case SyntaxKind.IndexedAccessType:
case SyntaxKind.TypeCall:
case SyntaxKind.MappedType:
case SyntaxKind.LiteralType:
case SyntaxKind.NamespaceExportDeclaration:
Expand Down
237 changes: 191 additions & 46 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions src/compiler/declarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,8 @@ namespace ts {
return emitTypeOperator(<TypeOperatorNode>type);
case SyntaxKind.IndexedAccessType:
return emitIndexedAccessType(<IndexedAccessTypeNode>type);
case SyntaxKind.TypeCall:
return emitTypeCall(<TypeCallTypeNode>type);
case SyntaxKind.MappedType:
return emitMappedType(<MappedTypeNode>type);
case SyntaxKind.FunctionType:
Expand Down Expand Up @@ -553,6 +555,13 @@ namespace ts {
write("]");
}

function emitTypeCall(node: TypeCallTypeNode) {
emitType(node.function);
write("(");
emitCommaList(node.arguments, emitType);
write(")");
}

function emitMappedType(node: MappedTypeNode) {
const prevEnclosingDeclaration = enclosingDeclaration;
enclosingDeclaration = node;
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,8 @@ namespace ts {
return emitMappedType(<MappedTypeNode>node);
case SyntaxKind.LiteralType:
return emitLiteralType(<LiteralTypeNode>node);
case SyntaxKind.TypeCall:
return emitTypeCall(<TypeCallTypeNode>node);

// Binding patterns
case SyntaxKind.ObjectBindingPattern:
Expand Down Expand Up @@ -1240,6 +1242,11 @@ namespace ts {
write("]");
}

function emitTypeCall(node: TypeCallTypeNode) {
emit(node.function);
emitList(node, node.arguments, ListFormat.CallExpressionArguments);
}

function emitCallExpression(node: CallExpression) {
emitExpression(node.expression);
emitTypeArguments(node, node.typeArguments);
Expand Down
14 changes: 14 additions & 0 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,20 @@ namespace ts {
: node;
}

export function createTypeCall(fn: TypeNode, argumentsArray: ReadonlyArray<TypeNode>) {
const node = <TypeCallTypeNode>createSynthesizedNode(SyntaxKind.TypeCall);
node.function = parenthesizeElementTypeMember(fn);
node.arguments = parenthesizeElementTypeMembers(createNodeArray(argumentsArray));
return node;
}

export function updateTypeCall(node: TypeCallTypeNode, fn: TypeNode, argumentsArray: ReadonlyArray<TypeNode>) {
return node.function !== fn
|| node.arguments !== argumentsArray
? updateNode(createTypeCall(fn, argumentsArray), node)
: node;
}

export function createCall(expression: Expression, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentsArray: ReadonlyArray<Expression>) {
const node = <CallExpression>createSynthesizedNode(SyntaxKind.CallExpression);
node.expression = parenthesizeForAccess(expression);
Expand Down
60 changes: 55 additions & 5 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ namespace ts {
case SyntaxKind.ElementAccessExpression:
return visitNode(cbNode, (<ElementAccessExpression>node).expression) ||
visitNode(cbNode, (<ElementAccessExpression>node).argumentExpression);
case SyntaxKind.TypeCall:
return visitNode(cbNode, (<TypeCallTypeNode>node).function) ||
visitNodes(cbNode, cbNodes, (<TypeCallTypeNode>node).arguments);
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
return visitNode(cbNode, (<CallExpression>node).expression) ||
Expand Down Expand Up @@ -2739,8 +2742,8 @@ namespace ts {
return token() === SyntaxKind.CloseParenToken || isStartOfParameter() || isStartOfType();
}

function parseJSDocPostfixTypeOrHigher(): TypeNode {
const type = parseNonArrayType();
function parseJSDocPostfixTypeOrHigher(typeNode?: TypeNode): TypeNode {
const type = typeNode || parseNonArrayType();
const kind = getKind(token());
if (!kind) return type;
nextToken();
Expand All @@ -2762,8 +2765,8 @@ namespace ts {
}
}

function parseArrayTypeOrHigher(): TypeNode {
let type = parseJSDocPostfixTypeOrHigher();
function parseArrayTypeOrHigher(typeNode?: TypeNode): TypeNode {
let type = parseJSDocPostfixTypeOrHigher(typeNode);
while (!scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) {
if (isStartOfType()) {
const node = <IndexedAccessTypeNode>createNode(SyntaxKind.IndexedAccessType, type.pos);
Expand Down Expand Up @@ -2795,7 +2798,7 @@ namespace ts {
case SyntaxKind.KeyOfKeyword:
return parseTypeOperator(SyntaxKind.KeyOfKeyword);
}
return parseArrayTypeOrHigher();
return parseTypeCallRest();
}

function parseUnionOrIntersectionType(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, parseConstituentType: () => TypeNode, operator: SyntaxKind.BarToken | SyntaxKind.AmpersandToken): TypeNode {
Expand Down Expand Up @@ -4241,6 +4244,53 @@ namespace ts {
}
}

// type equivalent of parseCallExpressionRest
function parseTypeCallRest(type?: TypeNode): TypeNode {
while (true) {
type = parseArrayTypeOrHigher(type);
// crap, the type may have parsed a semicolon...
if (!~[SyntaxKind.ThisType, SyntaxKind.ArrayType, SyntaxKind.TupleType, SyntaxKind.TypeOperator, SyntaxKind.LiteralType, SyntaxKind.NullKeyword, SyntaxKind.TrueKeyword, SyntaxKind.FalseKeyword, SyntaxKind.AnyKeyword, SyntaxKind.NumberKeyword, SyntaxKind.ObjectKeyword, SyntaxKind.BooleanKeyword, SyntaxKind.StringKeyword, SyntaxKind.SymbolKeyword, SyntaxKind.ThisKeyword, SyntaxKind.VoidKeyword, SyntaxKind.UndefinedKeyword, SyntaxKind.NullKeyword, SyntaxKind.NeverKeyword].indexOf(type.kind)) {
if (token() === SyntaxKind.OpenParenToken) {
const call = tryParse(() => parseTypeCall(type));
if (call) {
type = call;
continue;
}
}
}
return type;
}
}

function parseTypeCall(type: TypeNode) {
// if we're in what looks like a function declaration, scram
const arg = lookAhead(parseFirstParam);
if (arg && arg.type) {
return undefined;
}

if (token() !== SyntaxKind.OpenParenToken) {
return;
}
const args = parseTypeArgumentList();
const typeCall = <TypeCallTypeNode>createNode(SyntaxKind.TypeCall, type.pos);
typeCall.function = type;
typeCall.arguments = args;
return finishNode(typeCall);
}

function parseFirstParam() {
parseExpected(SyntaxKind.OpenParenToken);
return parseParameter();
}

function parseTypeArgumentList() {
parseExpected(SyntaxKind.OpenParenToken);
const result = parseDelimitedList(ParsingContext.TypeArguments, parseType);
parseExpected(SyntaxKind.CloseParenToken);
return result;
}

function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression {
while (true) {
expression = parseMemberExpressionRest(expression);
Expand Down
10 changes: 9 additions & 1 deletion src/compiler/symbolWalker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ namespace ts {
if (type.flags & TypeFlags.IndexedAccess) {
visitIndexedAccessType(type as IndexedAccessType);
}
if (type.flags & TypeFlags.TypeCall) {
visitTypeCallType(type as TypeCallType);
}
}

function visitTypeList(types: Type[]): void {
Expand Down Expand Up @@ -111,6 +114,11 @@ namespace ts {
visitType(type.constraint);
}

function visitTypeCallType(type: TypeCallType): void {
visitType(type.function);
visitTypeList(type.arguments);
}

function visitMappedType(type: MappedType): void {
visitType(type.typeParameter);
visitType(type.constraintType);
Expand Down Expand Up @@ -188,4 +196,4 @@ namespace ts {
}
}
}
}
}
2 changes: 2 additions & 0 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ namespace ts {
case SyntaxKind.IndexedAccessType:
case SyntaxKind.MappedType:
case SyntaxKind.LiteralType:
case SyntaxKind.TypeCall:
// TypeScript type nodes are elided.

case SyntaxKind.IndexSignature:
Expand Down Expand Up @@ -1833,6 +1834,7 @@ namespace ts {
case SyntaxKind.TypeQuery:
case SyntaxKind.TypeOperator:
case SyntaxKind.IndexedAccessType:
case SyntaxKind.TypeCall:
case SyntaxKind.MappedType:
case SyntaxKind.TypeLiteral:
case SyntaxKind.AnyKeyword:
Expand Down
39 changes: 29 additions & 10 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ namespace ts {
IndexedAccessType,
MappedType,
LiteralType,
TypeCall,
// Binding patterns
ObjectBindingPattern,
ArrayBindingPattern,
Expand Down Expand Up @@ -398,7 +399,7 @@ namespace ts {
FirstFutureReservedWord = ImplementsKeyword,
LastFutureReservedWord = YieldKeyword,
FirstTypeNode = TypePredicate,
LastTypeNode = LiteralType,
LastTypeNode = TypeCall,
FirstPunctuation = OpenBraceToken,
LastPunctuation = CaretEqualsToken,
FirstToken = Unknown,
Expand Down Expand Up @@ -617,6 +618,8 @@ namespace ts {

export type DeclarationName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName | BindingPattern;

export type Arguments = ReadonlyArray<Expression> | ReadonlyArray<TypeNode>;

export interface Declaration extends Node {
_declarationBrand: any;
}
Expand Down Expand Up @@ -1495,6 +1498,12 @@ namespace ts {
arguments: NodeArray<Expression>;
}

export interface TypeCallTypeNode extends TypeNode {
kind: SyntaxKind.TypeCall;
function: TypeNode;
arguments: NodeArray<TypeNode>;
}

// see: https://tc39.github.io/ecma262/#prod-SuperCall
export interface SuperCall extends CallExpression {
expression: SuperExpression;
Expand Down Expand Up @@ -1526,6 +1535,8 @@ namespace ts {

export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement;

export type CallLike = CallLikeExpression | TypeCallTypeNode;

export interface AsExpression extends Expression {
kind: SyntaxKind.AsExpression;
expression: Expression;
Expand Down Expand Up @@ -3141,17 +3152,18 @@ namespace ts {
Intersection = 1 << 17, // Intersection (T & U)
Index = 1 << 18, // keyof T
IndexedAccess = 1 << 19, // T[K]
TypeCall = 1 << 20, // F(T)
/* @internal */
FreshLiteral = 1 << 20, // Fresh literal type
FreshLiteral = 1 << 21, // Fresh literal type
/* @internal */
ContainsWideningType = 1 << 21, // Type is or contains undefined or null widening type
ContainsWideningType = 1 << 22, // Type is or contains undefined or null widening type
/* @internal */
ContainsObjectLiteral = 1 << 22, // Type is or contains object literal type
ContainsObjectLiteral = 1 << 23, // Type is or contains object literal type
/* @internal */
ContainsAnyFunctionType = 1 << 23, // Type is or contains the anyFunctionType
NonPrimitive = 1 << 24, // intrinsic object type
ContainsAnyFunctionType = 1 << 24, // Type is or contains the anyFunctionType
NonPrimitive = 1 << 25, // intrinsic object type
/* @internal */
JsxAttributes = 1 << 25, // Jsx attributes type
JsxAttributes = 1 << 26, // Jsx attributes type

/* @internal */
Nullable = Undefined | Null,
Expand All @@ -3170,12 +3182,12 @@ namespace ts {
EnumLike = Enum | EnumLiteral,
UnionOrIntersection = Union | Intersection,
StructuredType = Object | Union | Intersection,
StructuredOrTypeVariable = StructuredType | TypeParameter | Index | IndexedAccess,
TypeVariable = TypeParameter | IndexedAccess,
StructuredOrTypeVariable = StructuredType | TypeParameter | Index | IndexedAccess | TypeCall,
TypeVariable = TypeParameter | IndexedAccess | TypeCall,

// 'Narrowable' types are types where narrowing actually narrows.
// This *should* be every type other than null, undefined, void, and never
Narrowable = Any | StructuredType | TypeParameter | Index | IndexedAccess | StringLike | NumberLike | BooleanLike | ESSymbol | NonPrimitive,
Narrowable = Any | StructuredType | TypeParameter | Index | IndexedAccess | TypeCall | StringLike | NumberLike | BooleanLike | ESSymbol | NonPrimitive,
NotUnionOrUnit = Any | ESSymbol | Object | NonPrimitive,
/* @internal */
RequiresWidening = ContainsWideningType | ContainsObjectLiteral,
Expand Down Expand Up @@ -3400,6 +3412,13 @@ namespace ts {
constraint?: Type;
}

// F(T) types (TypeFlags.TypeCall)
export interface TypeCallType extends TypeVariable {
function: Type;
arguments: Type[];
node: TypeCallTypeNode;
}

// keyof T types (TypeFlags.Index)
export interface IndexType extends Type {
type: TypeVariable | UnionOrIntersectionType;
Expand Down
21 changes: 20 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,9 @@ namespace ts {
if (nodeIsMissing(node)) {
return "";
}
if (!sourceFile) {
sourceFile = getSourceFileOfNode(node);
}

const text = sourceFile.text;
return text.substring(includeTrivia ? node.pos : skipTrivia(text, node.pos), node.end);
Expand Down Expand Up @@ -529,7 +532,12 @@ namespace ts {

export function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): Diagnostic {
const sourceFile = getSourceFileOfNode(node);
return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2);
if (!sourceFile || node.pos < 0) {
return createDiagnosticForNode(node.parent, message, arg0, arg1, arg2);
}
else {
return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2);
}
}

export function createDiagnosticForNodeInSourceFile(sourceFile: SourceFile, node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): Diagnostic {
Expand All @@ -539,6 +547,7 @@ namespace ts {

export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain): Diagnostic {
const sourceFile = getSourceFileOfNode(node);
if (!sourceFile) return createDiagnosticForNodeFromMessageChain(node.parent, messageChain);
const span = getErrorSpanForNode(sourceFile, node);
return {
file: sourceFile,
Expand Down Expand Up @@ -572,7 +581,13 @@ namespace ts {
}

export function getErrorSpanForNode(sourceFile: SourceFile, node: Node): TextSpan {
if (node && node.parent !== node && (!sourceFile || node.pos < 0)) {
return getErrorSpanForNode(sourceFile, node.parent);
}
let errorNode = node;
if (node.pos < 0) {
return getErrorSpanForNode(sourceFile, node.parent);
}
switch (node.kind) {
case SyntaxKind.SourceFile:
const pos = skipTrivia(sourceFile.text, 0, /*stopAfterLineBreak*/ false);
Expand Down Expand Up @@ -4117,6 +4132,10 @@ namespace ts {
return node.kind === SyntaxKind.IndexedAccessType;
}

export function isTypeCallTypeNode(node: Node): node is TypeCallTypeNode {
return node.kind === SyntaxKind.TypeCall;
}

export function isMappedTypeNode(node: Node): node is MappedTypeNode {
return node.kind === SyntaxKind.MappedType;
}
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,11 @@ namespace ts {
visitNode((<ElementAccessExpression>node).expression, visitor, isExpression),
visitNode((<ElementAccessExpression>node).argumentExpression, visitor, isExpression));

case SyntaxKind.TypeCall:
return updateTypeCall(<TypeCallTypeNode>node,
visitNode((<TypeCallTypeNode>node).function, visitor, isTypeNode),
nodesVisitor((<TypeCallTypeNode>node).arguments, visitor, isTypeNode));

case SyntaxKind.CallExpression:
return updateCall(<CallExpression>node,
visitNode((<CallExpression>node).expression, visitor, isExpression),
Expand Down Expand Up @@ -1391,6 +1396,10 @@ namespace ts {
result = reduceNode((<SpreadAssignment>node).expression, cbNode, result);
break;

case SyntaxKind.TypeCall:
result = reduceNode((<TypeCallTypeNode>node).function, cbNode, result);
break;

// Enum
case SyntaxKind.EnumMember:
result = reduceNode((<EnumMember>node).name, cbNode, result);
Expand Down
Loading