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

Add tuple support, fix deep printback, simple test showing current shortcoming #33058

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
108 changes: 68 additions & 40 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3540,7 +3540,6 @@ namespace ts {
}

function createNodeBuilder() {
let depth = 0;
return {
typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)),
Expand Down Expand Up @@ -3804,10 +3803,7 @@ namespace ts {
function createAnonymousTypeNode(type: ObjectType): TypeNode {
const typeId = "" + type.id;
const symbol = type.symbol;
let id: string;
if (symbol) {
const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class;
id = (isConstructorObject ? "+" : "") + getSymbolId(symbol);
if (isJSConstructor(symbol.valueDeclaration)) {
// Instance and static types share the same symbol; only add 'typeof' for the static side.
const isInstanceType = type === getInferredClassType(symbol) ? SymbolFlags.Type : SymbolFlags.Value;
Expand All @@ -3831,25 +3827,7 @@ namespace ts {
}
}
else {
// Since instantiations of the same anonymous type have the same symbol, tracking symbols instead
// of types allows us to catch circular references to instantiations of the same anonymous type
if (!context.visitedTypes) {
context.visitedTypes = createMap<true>();
}
if (!context.symbolDepth) {
context.symbolDepth = createMap<number>();
}

const depth = context.symbolDepth.get(id) || 0;
if (depth > 10) {
return createElidedInformationPlaceholder(context);
}
context.symbolDepth.set(id, depth + 1);
context.visitedTypes.set(typeId, true);
const result = createTypeNodeFromObjectType(type);
context.visitedTypes.delete(typeId);
context.symbolDepth.set(id, depth);
return result;
return visitAndTransformType(type, createTypeNodeFromObjectType);
}
}
else {
Expand All @@ -3871,6 +3849,37 @@ namespace ts {
}
}

function visitAndTransformType<T>(type: Type, transform: (type: Type) => T) {
const typeId = "" + type.id;
const symbol = type.symbol;
const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class;
const id = symbol && ((isConstructorObject ? "+" : "") + getSymbolId(symbol));
// Since instantiations of the same anonymous type have the same symbol, tracking symbols instead
// of types allows us to catch circular references to instantiations of the same anonymous type
if (!context.visitedTypes) {
context.visitedTypes = createMap<true>();
}
if (id && !context.symbolDepth) {
context.symbolDepth = createMap<number>();
}

let depth: number | undefined;
if (id) {
depth = context.symbolDepth!.get(id) || 0;
if (depth > 10) {
return createElidedInformationPlaceholder(context);
}
context.symbolDepth!.set(id, depth + 1);
}
context.visitedTypes.set(typeId, true);
const result = transform(type);
context.visitedTypes.delete(typeId);
if (id) {
context.symbolDepth!.set(id, depth!);
}
return result;
}

function createTypeNodeFromObjectType(type: ObjectType): TypeNode {
if (isGenericMappedType(type)) {
return createMappedTypeNodeFromType(type);
Expand Down Expand Up @@ -3909,17 +3918,14 @@ namespace ts {
function typeReferenceToTypeNode(type: TypeReference) {
const typeArguments: ReadonlyArray<Type> = getTypeArguments(type);
if (type.target === globalArrayType || type.target === globalReadonlyArrayType) {
const typeArgumentNode = context.visitedTypes && context.visitedTypes.has("" + getTypeId(typeArguments[0]))
? createElidedInformationPlaceholder(context)
: visitAndTransformType(typeArguments[0], t => typeToTypeNodeHelper(t, context));
if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) {
depth++;
const typeArgumentNode = typeToTypeNodeHelper(depth >= 5 ? anyType : typeArguments[0], context);
depth--;
createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]);
return createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]);
}

depth++;
const elementType = typeToTypeNodeHelper(depth >= 5 ? anyType : typeArguments[0], context);
depth--;
const arrayType = createArrayTypeNode(elementType);
const arrayType = createArrayTypeNode(typeArgumentNode);
return type.target === globalArrayType ? arrayType : createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType);
}
else if (type.target.objectFlags & ObjectFlags.Tuple) {
Expand Down Expand Up @@ -9077,19 +9083,29 @@ namespace ts {
return type;
}

function createDeferredTypeReference(target: GenericType, typeArgumentNodes: ReadonlyArray<TypeNode>, mapper?: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: ReadonlyArray<Type>): TypeReference {
function createDeferredTypeReference(target: GenericType, typeArgumentNodes: ReadonlyArray<TypeNode>, typeNodeDecoder: (node: TypeNode, i: number, len: number) => Type, mapper?: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: ReadonlyArray<Type>): TypeReference {
const type = <TypeReference>createObjectType(ObjectFlags.Reference, target.symbol);
type.target = target;
type.typeArgumentNodes = typeArgumentNodes;
type.typeNodeDecoder = typeNodeDecoder;
type.mapper = mapper;
type.aliasSymbol = aliasSymbol;
type.aliasTypeArguments = aliasTypeArguments;
return type;
}

function mapWithLength<T, U>(array: ReadonlyArray<T>, f: (x: T, i: number, length: number) => U): U[] {
const len = array.length;
const result: U[] = new Array(len);
for (let i = 0; i < len; i++) {
result[i] = f(array[i], i, len);
}
return result;
}

function getTypeArguments(type: TypeReference): ReadonlyArray<Type> {
if (!type.resolvedTypeArguments) {
const typeArguments = type.typeArgumentNodes ? map(type.typeArgumentNodes, getTypeFromTypeNode) : emptyArray;
const typeArguments = type.typeArgumentNodes ? mapWithLength(type.typeArgumentNodes, type.typeNodeDecoder!) : emptyArray;
type.resolvedTypeArguments = type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments;
}
return type.resolvedTypeArguments;
Expand Down Expand Up @@ -9587,7 +9603,7 @@ namespace ts {
const target = isReadonlyTypeOperator(node.parent) ? globalReadonlyArrayType : globalArrayType;
const aliasSymbol = getAliasSymbolForTypeNode(node);
const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol);
links.resolvedType = createDeferredTypeReference(target, [node.elementType], /*mapper*/ undefined, aliasSymbol, aliasTypeArguments);
links.resolvedType = createDeferredTypeReference(target, [node.elementType], getTypeFromTypeNode, /*mapper*/ undefined, aliasSymbol, aliasTypeArguments);
}
return links.resolvedType;
}
Expand Down Expand Up @@ -9665,17 +9681,29 @@ namespace ts {
return elementTypes.length ? createTypeReference(tupleType, elementTypes) : tupleType;
}

function createDeferredTupleType(typeNodes: readonly TypeNode[], aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, minLength = typeNodes.length, hasRestElement = false, readonly = false, associatedNames?: __String[]) {
const arity = typeNodes.length;
if (arity === 1 && hasRestElement) {
return createArrayType(getTypeFromTypeNode(typeNodes[0]), readonly);
}
const tupleType = getTupleTypeOfArity(arity, minLength, arity > 0 && hasRestElement, readonly, associatedNames);
return typeNodes.length ? createDeferredTypeReference(tupleType, typeNodes, getTypeFromTupleTypeArgumentNode, /*mapper*/ undefined, aliasSymbol, aliasTypeArguments) : tupleType;
}

function getTypeFromTupleTypeArgumentNode(node: TypeNode, idx: number, len: number) {
const type = getTypeFromTypeNode(node);
return node.kind === SyntaxKind.RestType && idx === (len - 1) && getIndexTypeOfType(type, IndexKind.Number) || type;
}

function getTypeFromTupleTypeNode(node: TupleTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
const lastElement = lastOrUndefined(node.elementTypes);
const restElement = lastElement && lastElement.kind === SyntaxKind.RestType ? lastElement : undefined;
const minLength = findLastIndex(node.elementTypes, n => n.kind !== SyntaxKind.OptionalType && n !== restElement) + 1;
const elementTypes = map(node.elementTypes, n => {
const type = getTypeFromTypeNode(n);
return n === restElement && getIndexTypeOfType(type, IndexKind.Number) || type;
});
links.resolvedType = createTupleType(elementTypes, minLength, !!restElement, isReadonlyTypeOperator(node.parent));
const aliasSymbol = getAliasSymbolForTypeNode(node);
const aliasArgs = getTypeArgumentsForAliasSymbol(aliasSymbol);
links.resolvedType = createDeferredTupleType(node.elementTypes, aliasSymbol, aliasArgs, minLength, !!restElement, isReadonlyTypeOperator(node.parent));
}
return links.resolvedType;
}
Expand Down Expand Up @@ -11653,7 +11681,7 @@ namespace ts {
const typeArgumentNodes = (<TypeReference>type).typeArgumentNodes;
if (typeArgumentNodes) {
const combinedMapper = combineTypeMappers((<TypeReference>type).mapper, mapper);
return createDeferredTypeReference((<TypeReference>type).target, typeArgumentNodes, combinedMapper,
return createDeferredTypeReference((<TypeReference>type).target, typeArgumentNodes, (<TypeReference>type).typeNodeDecoder!, combinedMapper,
(<TypeReference>type).aliasSymbol, instantiateTypes((<TypeReference>type).aliasTypeArguments, combinedMapper));
}
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4202,6 +4202,7 @@ namespace ts {
target: GenericType; // Type reference target
resolvedTypeArguments?: ReadonlyArray<Type>; // Type reference type arguments (undefined if none)
typeArgumentNodes?: ReadonlyArray<TypeNode>;
typeNodeDecoder?: (node: TypeNode, i: number, len: number) => Type;
mapper?: TypeMapper;
/* @internal */
literalType?: TypeReference; // Clone of type with ObjectFlags.ArrayLiteral set
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
type HypertextNode = string | [string, { [key: string]: any }, ...HypertextNode[]];

const hypertextNode: HypertextNode =
["div", { id: "parent" },
["div", { id: "first-child" }, "I'm the first child"],
["div", { id: "second-child" }, "I'm the second child"]
];

type Alternating<T> = [T, Alternating<T extends string ? number : string>?];

declare function reparam<T>(x: Alternating<T>): T;

// inference for this alternating reference pattern is.... interesting.
const re1 = reparam([12]);
const re2 = reparam(["ok"]);
const re3 = reparam([12, ["ok"]]);
const re4 = reparam(["ok", [12]]);
const re5 = reparam([12, ["ok", [0]]]);
const re6 = reparam(["ok", [12, ["k"]]]);
const re7 = reparam([12, "not ok"]); // arity error
const re8 = reparam(["ok", [12, ["ok", [12, "not ok"]]]]); // deep arity error
const re9 = reparam([12, [12]]); // non-alternating
const re10 = reparam(["ok", [12, ["ok", [12, ["ok", ["not ok"]]]]]]); // deep non-alternating - we should strive to issue an error here, I think, but we infer `string | number` for T and do not
2 changes: 1 addition & 1 deletion tests/cases/user/puppeteer/puppeteer
2 changes: 1 addition & 1 deletion tests/cases/user/webpack/webpack
Submodule webpack updated 1 files
+3 −3 yarn.lock