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

WIP: type call spreads #18007

Closed
Closed
1 change: 1 addition & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3358,6 +3358,7 @@ namespace ts {
case SyntaxKind.TypeLiteral:
case SyntaxKind.ArrayType:
case SyntaxKind.TupleType:
case SyntaxKind.TypeSpread:
case SyntaxKind.UnionType:
case SyntaxKind.IntersectionType:
case SyntaxKind.ParenthesizedType:
Expand Down
133 changes: 126 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/// <reference path="moduleNameResolver.ts"/>
/// <reference path="binder.ts"/>
/// <reference path="symbolWalker.ts" />
/// <reference types="node" />

declare var console: Console;

/* @internal */
namespace ts {
Expand Down Expand Up @@ -239,6 +242,7 @@ namespace ts {
const intersectionTypes = createMap<IntersectionType>();
const literalTypes = createMap<LiteralType>();
const indexedAccessTypes = createMap<IndexedAccessType>();
const spreadTypes = createMap<TypeSpreadType>();
const evolvingArrayTypes: EvolvingArrayType[] = [];

const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String);
Expand Down Expand Up @@ -2542,6 +2546,10 @@ namespace ts {
const indexTypeNode = typeToTypeNodeHelper((<IndexedAccessType>type).indexType, context);
return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode);
}
if (type.flags & TypeFlags.TypeSpread) {
const typeNode = typeToTypeNodeHelper((<TypeSpreadType>type).type, context);
return createTypeSpread(typeNode);
}

Debug.fail("Should be unreachable.");

Expand Down Expand Up @@ -3305,6 +3313,10 @@ namespace ts {
writeType((<IndexedAccessType>type).indexType, TypeFormatFlags.None);
writePunctuation(writer, SyntaxKind.CloseBracketToken);
}
else if (type.flags & TypeFlags.TypeSpread) {
writePunctuation(writer, SyntaxKind.DotDotDotToken);
writeType((<TypeSpreadType>type).type, TypeFormatFlags.None);
}
else {
// Should never get here
// { ... }
Expand Down Expand Up @@ -5392,6 +5404,7 @@ namespace ts {
}

function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: TypeParameter[], typeArguments: Type[]) {
if (allowSyntheticDefaultImports) console.log("resolveObjectTypeMembers", typeToString(type));
let mapper: TypeMapper;
let members: SymbolTable;
let callSignatures: Signature[];
Expand Down Expand Up @@ -6799,7 +6812,7 @@ namespace ts {
const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(typeArgs, typeParameters, minTypeArgumentCount, node));
return createTypeReference(<GenericType>type, typeArguments);
}
if (node.typeArguments) {
if (node.typeArguments && node.typeArguments.length) {
error(node, Diagnostics.Type_0_is_not_generic, typeToString(type));
return unknownType;
}
Expand Down Expand Up @@ -6841,7 +6854,7 @@ namespace ts {
}
return getTypeAliasInstantiation(symbol, typeArguments);
}
if (node.typeArguments) {
if (node.typeArguments && node.typeArguments.length) {
error(node, Diagnostics.Type_0_is_not_generic, symbolToString(symbol));
return unknownType;
}
Expand All @@ -6852,7 +6865,7 @@ namespace ts {
* Get type from reference to named type that cannot be generic (enum or type parameter)
*/
function getTypeFromNonGenericTypeReference(node: TypeReferenceType, symbol: Symbol): Type {
if (node.typeArguments) {
if (node.typeArguments && node.typeArguments.length) {
error(node, Diagnostics.Type_0_is_not_generic, symbolToString(symbol));
return unknownType;
}
Expand Down Expand Up @@ -7218,11 +7231,69 @@ namespace ts {
function getTypeFromTupleTypeNode(node: TupleTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = createTupleType(map(node.elementTypes, getTypeFromTypeNode));
links.resolvedType = createTupleType(flatMap(node.elementTypes, getTypeFromTupleElement));
}
return links.resolvedType;
}

function getTypeSpreadTypes(tuple: Type): Type[] {
if (isGenericTupleType(tuple)) {
// Defer the operation by creating a spread type.
const id = "" + tuple.id;
let type = spreadTypes.get(id);
if (!type) {
spreadTypes.set(id, type = createTypeSpreadType(tuple));
}
return [type];
}
else {
// const type = getApparentType(nodeType);
if (allowSyntheticDefaultImports) {
console.log("type", typeToString(tuple));
console.log("isTupleLikeType(type)", isTupleLikeType(tuple));
}
if (isTupleLikeType(tuple)) {
// return map(getPropertiesOfType(tuple), getTypeOfSymbol);
return getTupleTypeElementTypes(tuple);
}
else {
// error(typeNode, Diagnostics.Tuple_type_spreads_may_only_be_created_from_tuple_types);
console.log("not a tuple, don't resolve?");
return [];
}
}
}

function isGenericTupleType(type: Type): boolean {
return type.flags & TypeFlags.TypeVariable ? true :
type.flags & TypeFlags.UnionOrIntersection ? forEach((<UnionOrIntersectionType>type).types, isGenericTupleType) :
false;
}

function getTupleTypeElementTypes(type: Type): Type[] {
Debug.assert(isTupleLikeType(type));
const types = [];
let idx = 0;
let symbol: Symbol;
while (symbol = getPropertyOfObjectType(type, idx++ + "" as __String)) {
types.push(getTypeOfSymbol(symbol));
}
return types;
}

function getTypeFromTupleElement(node: TypeNode | TypeSpreadTypeNode): Type | Type[] {
if (node.kind === SyntaxKind.TypeSpread) {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getTypeFromTypeNode((node as TypeSpreadTypeNode).type);
}
return getTypeSpreadTypes(links.resolvedType);
}
else {
return getTypeFromTypeNode(node as TypeNode);
}
}

interface TypeSet extends Array<Type> {
containsAny?: boolean;
containsUndefined?: boolean;
Expand Down Expand Up @@ -7553,13 +7624,38 @@ namespace ts {
return links.resolvedType;
}

function getTypeFromTypeCallNode(node: TypeCallTypeNode): Type {
const fn = typeNodeToExpression(node.type);
const args = map(node.arguments, (type: TypeNode) => {
if (type.kind === SyntaxKind.TypeSpread) {
return createSpread(typeNodeToExpression((type as TypeSpreadTypeNode).type));
} else {
return typeNodeToExpression(type);
}
});
const callExpr = createCall(fn, node.typeArguments, args);
return checkExpression(callExpr);
}

// null! as type
function typeNodeToExpression(type: TypeNode): Expression {
return createAsExpression(createNonNullExpression(createNull()), type);
}

function createIndexedAccessType(objectType: Type, indexType: Type) {
const type = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
type.objectType = objectType;
type.indexType = indexType;
return type;
}

function createTypeSpreadType(tuple: Type) {
console.log("createTypeSpreadType");
const type = <TypeSpreadType>createType(TypeFlags.TypeSpread);
type.type = tuple;
return type;
}

function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode, cacheSymbol: boolean) {
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? <ElementAccessExpression>accessNode : undefined;
const propName = indexType.flags & TypeFlags.StringOrNumberLiteral ?
Expand Down Expand Up @@ -8010,6 +8106,8 @@ namespace ts {
return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node);
case SyntaxKind.TypeOperator:
return getTypeFromTypeOperatorNode(<TypeOperatorNode>node);
case SyntaxKind.TypeCall:
return getTypeFromTypeCallNode(<TypeCallTypeNode>node);
case SyntaxKind.IndexedAccessType:
return getTypeFromIndexedAccessTypeNode(<IndexedAccessTypeNode>node);
case SyntaxKind.MappedType:
Expand Down Expand Up @@ -8375,6 +8473,9 @@ namespace ts {
if (type.flags & TypeFlags.IndexedAccess) {
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
}
// if (type.flags & TypeFlags.TypeSpread) {
// return getTypeSpreadTypes(instantiateType((<TypeSpreadType>type).type, mapper));
// }
return type;
}

Expand Down Expand Up @@ -13247,7 +13348,7 @@ namespace ts {
return node.contextualType;
}
const parent = node.parent;
switch (parent.kind) {
switch (parent && parent.kind) {
case SyntaxKind.VariableDeclaration:
case SyntaxKind.Parameter:
case SyntaxKind.PropertyDeclaration:
Expand Down Expand Up @@ -18782,7 +18883,7 @@ namespace ts {
}
const type = getTypeFromTypeReference(node);
if (type !== unknownType) {
if (node.typeArguments) {
if (node.typeArguments && node.typeArguments.length) {
// Do type argument local checks only if referenced type is successfully resolved
forEach(node.typeArguments, checkSourceElement);
if (produceDiagnostics) {
Expand Down Expand Up @@ -18835,6 +18936,22 @@ namespace ts {
forEach(node.elementTypes, checkSourceElement);
}

function checkTypeSpreadTypeNode(node: TypeSpreadTypeNode) {
checkSourceElement(node.type);
// checkTypeSpreadType(<TypeSpreadType> getTypeFromTypeNode(node.type), node);
const type = getApparentType(getTypeFromTypeNode(node.type));
if (!isArrayLikeType(type)) { // isTupleLikeType
grammarErrorOnNode(node, Diagnostics.Tuple_type_spreads_may_only_be_created_from_tuple_types);
}
}

// function checkTypeSpreadType(spread: TypeSpreadType, node: TypeSpreadTypeNode) {
// const type = getApparentType(spread.type);
// if (!isArrayLikeType(type)) { // isTupleLikeType
// grammarErrorOnNode(node, Diagnostics.Tuple_type_spreads_may_only_be_created_from_tuple_types);
// }
// }

function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) {
forEach(node.types, checkSourceElement);
}
Expand Down Expand Up @@ -22370,6 +22487,8 @@ namespace ts {
return checkArrayType(<ArrayTypeNode>node);
case SyntaxKind.TupleType:
return checkTupleType(<TupleTypeNode>node);
case SyntaxKind.TypeSpread:
return checkTypeSpreadTypeNode(<TypeSpreadTypeNode>node);
case SyntaxKind.UnionType:
case SyntaxKind.IntersectionType:
return checkUnionOrIntersectionType(<UnionOrIntersectionTypeNode>node);
Expand Down Expand Up @@ -24338,7 +24457,7 @@ namespace ts {

function checkGrammarTypeArguments(node: Node, typeArguments: NodeArray<TypeNode>): boolean {
return checkGrammarForDisallowedTrailingComma(typeArguments) ||
checkGrammarForAtLeastOneTypeArgument(node, typeArguments);
false && checkGrammarForAtLeastOneTypeArgument(node, typeArguments);
}

function checkGrammarForOmittedArgument(args: NodeArray<Expression>): boolean {
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2208,6 +2208,10 @@
"category": "Error",
"code": 2713
},
"Tuple type spreads may only be created from tuple types.": {
"category": "Error",
"code": 2714
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
17 changes: 17 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,8 @@ namespace ts {
return emitShorthandPropertyAssignment(<ShorthandPropertyAssignment>node);
case SyntaxKind.SpreadAssignment:
return emitSpreadAssignment(node as SpreadAssignment);
case SyntaxKind.TypeSpread:
return emitTypeSpread(node as TypeSpreadTypeNode);

// Enum
case SyntaxKind.EnumMember:
Expand Down Expand Up @@ -753,6 +755,8 @@ namespace ts {
return emitPropertyAccessExpression(<PropertyAccessExpression>node);
case SyntaxKind.ElementAccessExpression:
return emitElementAccessExpression(<ElementAccessExpression>node);
case SyntaxKind.TypeCall:
return emitTypeCall(<TypeCallTypeNode>node);
case SyntaxKind.CallExpression:
return emitCallExpression(<CallExpression>node);
case SyntaxKind.NewExpression:
Expand Down Expand Up @@ -1240,6 +1244,12 @@ namespace ts {
write("]");
}

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

function emitCallExpression(node: CallExpression) {
emitExpression(node.expression);
emitTypeArguments(node, node.typeArguments);
Expand Down Expand Up @@ -2193,6 +2203,13 @@ namespace ts {
}
}

function emitTypeSpread(node: TypeSpreadTypeNode) {
if (node.type) {
write("...");
emit(node.type);
}
}

//
// Enum
//
Expand Down
29 changes: 29 additions & 0 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,22 @@ namespace ts {
: node;
}

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

export function updateTypeCall(node: TypeCallTypeNode, type: TypeNode, typeArguments: ReadonlyArray<TypeNode> | undefined, argumentsArray: ReadonlyArray<TypeNode>) {
return node.type !== type
|| node.typeArguments !== typeArguments
|| node.arguments !== argumentsArray
? updateNode(createTypeCall(type, typeArguments, 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 Expand Up @@ -2185,6 +2201,19 @@ namespace ts {
: node;
}

export function createTypeSpread(type: TypeNode) {
console.log("createTypeSpread");
const node = <TypeSpreadTypeNode>createSynthesizedNode(SyntaxKind.TypeSpread);
node.type = type !== undefined ? parenthesizeElementTypeMember(type) : undefined;
return node;
}

export function updateTypeSpread(node: TypeSpreadTypeNode, type: TypeNode) {
return node.type !== type
? updateNode(createTypeSpread(type), node)
: node;
}

// Enum

export function createEnumMember(name: string | PropertyName, initializer?: Expression) {
Expand Down
Loading