Skip to content

Commit 1ea52a4

Browse files
committed
Propegate outer type parameters of single signature types
1 parent a641405 commit 1ea52a4

File tree

6 files changed

+131
-13
lines changed

6 files changed

+131
-13
lines changed

src/compiler/checker.ts

+43-13
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,7 @@ import {
965965
SignatureFlags,
966966
SignatureKind,
967967
singleElementArray,
968+
SingleSignatureType,
968969
skipOuterExpressions,
969970
skipParentheses,
970971
skipTrivia,
@@ -7088,7 +7089,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
70887089

70897090
const abstractSignatures = filter(resolved.constructSignatures, signature => !!(signature.flags & SignatureFlags.Abstract));
70907091
if (some(abstractSignatures)) {
7091-
const types = map(abstractSignatures, getOrCreateTypeFromSignature);
7092+
const types = map(abstractSignatures, s => getOrCreateTypeFromSignature(s));
70927093
// count the number of type elements excluding abstract constructors
70937094
const typeElementCount = resolved.callSignatures.length +
70947095
(resolved.constructSignatures.length - abstractSignatures.length) +
@@ -15648,7 +15649,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1564815649
return signature;
1564915650
}
1565015651

15651-
function getOrCreateTypeFromSignature(signature: Signature): ObjectType {
15652+
function getOrCreateTypeFromSignature(signature: Signature, outerTypeParameters?: TypeParameter[]): ObjectType {
1565215653
// There are two ways to declare a construct signature, one is by declaring a class constructor
1565315654
// using the constructor keyword, and the other is declaring a bare construct signature in an
1565415655
// object type literal or interface (using the new keyword). Each way of declaring a constructor
@@ -15659,7 +15660,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1565915660
// If declaration is undefined, it is likely to be the signature of the default constructor.
1566015661
const isConstructor = kind === undefined || kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType;
1566115662

15662-
const type = createObjectType(ObjectFlags.Anonymous);
15663+
// The type must have a symbol with a `Function` flag and a declaration in order to be correctly flagged as possibly containing
15664+
// type variables by `couldContainTypeVariables`
15665+
const type = createObjectType(ObjectFlags.Anonymous | ObjectFlags.SingleSignatureType, createSymbol(SymbolFlags.Function, InternalSymbolName.Function)) as SingleSignatureType;
15666+
if (signature.declaration) {
15667+
type.symbol.declarations = [signature.declaration];
15668+
type.symbol.valueDeclaration = signature.declaration;
15669+
}
15670+
outerTypeParameters ||= signature.declaration && getOuterTypeParameters(signature.declaration, /*includeThisTypes*/ true);
15671+
type.outerTypeParameters = outerTypeParameters;
15672+
1566315673
type.members = emptySymbols;
1566415674
type.properties = emptyArray;
1566515675
type.callSignatures = !isConstructor ? [signature] : emptyArray;
@@ -19630,7 +19640,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1963019640
const links = getNodeLinks(declaration);
1963119641
const target = type.objectFlags & ObjectFlags.Reference ? links.resolvedType! as DeferredTypeReference :
1963219642
type.objectFlags & ObjectFlags.Instantiated ? type.target! : type;
19633-
let typeParameters = links.outerTypeParameters;
19643+
let typeParameters = type.objectFlags & ObjectFlags.SingleSignatureType ? (type as SingleSignatureType).outerTypeParameters : links.outerTypeParameters;
1963419644
if (!typeParameters) {
1963519645
// The first time an anonymous type is instantiated we compute and store a list of the type
1963619646
// parameters that are in scope (and therefore potentially referenced). For type literals that
@@ -19860,6 +19870,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1986019870
if (type.objectFlags & ObjectFlags.InstantiationExpressionType) {
1986119871
(result as InstantiationExpressionType).node = (type as InstantiationExpressionType).node;
1986219872
}
19873+
if (type.objectFlags & ObjectFlags.SingleSignatureType) {
19874+
(result as SingleSignatureType).outerTypeParameters = (type as SingleSignatureType).outerTypeParameters;
19875+
}
1986319876
result.target = type;
1986419877
result.mapper = mapper;
1986519878
result.aliasSymbol = aliasSymbol || type.aliasSymbol;
@@ -25483,6 +25496,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2548325496
}
2548425497
}
2548525498

25499+
/**
25500+
* @returns `true` if `type` has the shape `[T[0]]` where `T` is `typeParameter`
25501+
*/
25502+
function isTupleOfSelf(typeParameter: TypeParameter, type: Type) {
25503+
return isTupleType(type) && getTupleElementType(type, 0) === getIndexedAccessType(typeParameter, getNumberLiteralType(0)) && !getTypeOfPropertyOfType(type, "1" as __String);
25504+
}
25505+
2548625506
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority = InferencePriority.None, contravariant = false) {
2548725507
let bivariant = false;
2548825508
let propagationType: Type;
@@ -25611,6 +25631,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2561125631
inference.priority = priority;
2561225632
}
2561325633
if (priority === inference.priority) {
25634+
// Inferring A to [A[0]] is a zero information inference (it guarantees A becomes its constraint), but oft arises from generic argument list inferences
25635+
// By discarding it early, we can allow more fruitful results to be used instead.
25636+
if (isTupleOfSelf(inference.typeParameter, candidate)) {
25637+
return;
25638+
}
2561425639
// We make contravariant inferences only if we are in a pure contravariant position,
2561525640
// i.e. only if we have not descended into a bivariant position.
2561625641
if (contravariant && !bivariant) {
@@ -26347,23 +26372,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2634726372
// inference error only occurs when there are *conflicting* candidates, i.e.
2634826373
// candidates with no common supertype.
2634926374
const defaultType = getDefaultFromTypeParameter(inference.typeParameter);
26350-
if (defaultType) {
26351-
// Instantiate the default type. Any forward reference to a type
26352-
// parameter should be instantiated to the empty object type.
26353-
inferredType = instantiateType(defaultType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper));
26354-
}
26375+
inferredType = defaultType;
2635526376
}
2635626377
}
2635726378
else {
2635826379
inferredType = getTypeFromInference(inference);
2635926380
}
2636026381

26361-
inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault));
26382+
const isDefault = !inferredType;
26383+
inferredType ||= getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault));
26384+
inference.inferredType = inferredType;
26385+
// Instantiate the inferred type. Any forward reference to a type
26386+
// parameter should be instantiated to the empty object type.
26387+
inferredType = instantiateType(inferredType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper));
26388+
inference.inferredType = inferredType;
2636226389

2636326390
const constraint = getConstraintOfTypeParameter(inference.typeParameter);
2636426391
if (constraint) {
2636526392
const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper);
26366-
if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
26393+
if (isDefault || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
2636726394
// If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint.
2636826395
inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint;
2636926396
}
@@ -34265,7 +34292,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3426534292
// If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive),
3426634293
// we obtain the regular type of any object literal arguments because we may not have inferred complete
3426734294
// parameter types yet and therefore excess property checks may yield false positives (see #17041).
34268-
const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType;
34295+
const checkArgType = instantiateType(checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType, signature.mapper);
3426934296
const effectiveCheckArgumentNode = getEffectiveCheckNode(arg);
3427034297
if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? effectiveCheckArgumentNode : undefined, effectiveCheckArgumentNode, headMessage, containingMessageChain, errorOutputContainer)) {
3427134298
Debug.assert(!reportErrors || !!errorOutputContainer.errors, "parameter should have errors when reporting errors");
@@ -39287,7 +39314,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3928739314
}
3928839315
}
3928939316
}
39290-
return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context));
39317+
// TODO: The signature may reference any outer inference contexts, but we map pop off and then apply new inference contexts, and thus get different inferred types.
39318+
// That this is cached on the *first* such attempt is not currently an issue, since expression types *also* get cached on the first pass. If we ever properly speculate, though,
39319+
// the cached "isolatedSignatureType" signature field absolutely needs to be included in the list of speculative caches.
39320+
return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context), flatMap(inferenceContexts, c => c && map(c.inferences, i => i.typeParameter)).slice());
3929139321
}
3929239322
}
3929339323
}

src/compiler/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -6317,6 +6317,7 @@ export const enum ObjectFlags {
63176317
ContainsSpread = 1 << 21, // Object literal contains spread operation
63186318
ObjectRestType = 1 << 22, // Originates in object rest declaration
63196319
InstantiationExpressionType = 1 << 23, // Originates in instantiation expression
6320+
SingleSignatureType = 1 << 27, // A single signature type extracted from a potentially broader type
63206321
/** @internal */
63216322
IsClassInstanceClone = 1 << 24, // Type is a clone of a class instance type
63226323
// Flags that require TypeFlags.Object and ObjectFlags.Reference
@@ -6527,6 +6528,12 @@ export interface AnonymousType extends ObjectType {
65276528
instantiations?: Map<string, Type>; // Instantiations of generic type alias (undefined if non-generic)
65286529
}
65296530

6531+
/** @internal */
6532+
// A SingleSignatureType may have bespoke outer type parameters to handle free type variable inferences
6533+
export interface SingleSignatureType extends AnonymousType {
6534+
outerTypeParameters?: TypeParameter[];
6535+
}
6536+
65306537
/** @internal */
65316538
export interface InstantiationExpressionType extends AnonymousType {
65326539
node: NodeWithTypeArguments;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//// [tests/cases/compiler/nestedGenericSpreadInference.ts] ////
2+
3+
//// [nestedGenericSpreadInference.ts]
4+
declare function wrap<X>(x: X): { x: X };
5+
declare function call<A extends unknown[], T>(x: { x: (...args: A) => T }, ...args: A): T;
6+
7+
// This should be of type `number` - ideally, it also would not error.
8+
const leak = call(wrap(<T>(x: T) => x), 1);
9+
10+
11+
//// [nestedGenericSpreadInference.js]
12+
"use strict";
13+
// This should be of type `number` - ideally, it also would not error.
14+
var leak = call(wrap(function (x) { return x; }), 1);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//// [tests/cases/compiler/nestedGenericSpreadInference.ts] ////
2+
3+
=== nestedGenericSpreadInference.ts ===
4+
declare function wrap<X>(x: X): { x: X };
5+
>wrap : Symbol(wrap, Decl(nestedGenericSpreadInference.ts, 0, 0))
6+
>X : Symbol(X, Decl(nestedGenericSpreadInference.ts, 0, 22))
7+
>x : Symbol(x, Decl(nestedGenericSpreadInference.ts, 0, 25))
8+
>X : Symbol(X, Decl(nestedGenericSpreadInference.ts, 0, 22))
9+
>x : Symbol(x, Decl(nestedGenericSpreadInference.ts, 0, 33))
10+
>X : Symbol(X, Decl(nestedGenericSpreadInference.ts, 0, 22))
11+
12+
declare function call<A extends unknown[], T>(x: { x: (...args: A) => T }, ...args: A): T;
13+
>call : Symbol(call, Decl(nestedGenericSpreadInference.ts, 0, 41))
14+
>A : Symbol(A, Decl(nestedGenericSpreadInference.ts, 1, 22))
15+
>T : Symbol(T, Decl(nestedGenericSpreadInference.ts, 1, 42))
16+
>x : Symbol(x, Decl(nestedGenericSpreadInference.ts, 1, 46))
17+
>x : Symbol(x, Decl(nestedGenericSpreadInference.ts, 1, 50))
18+
>args : Symbol(args, Decl(nestedGenericSpreadInference.ts, 1, 55))
19+
>A : Symbol(A, Decl(nestedGenericSpreadInference.ts, 1, 22))
20+
>T : Symbol(T, Decl(nestedGenericSpreadInference.ts, 1, 42))
21+
>args : Symbol(args, Decl(nestedGenericSpreadInference.ts, 1, 74))
22+
>A : Symbol(A, Decl(nestedGenericSpreadInference.ts, 1, 22))
23+
>T : Symbol(T, Decl(nestedGenericSpreadInference.ts, 1, 42))
24+
25+
// This should be of type `number` - ideally, it also would not error.
26+
const leak = call(wrap(<T>(x: T) => x), 1);
27+
>leak : Symbol(leak, Decl(nestedGenericSpreadInference.ts, 4, 5))
28+
>call : Symbol(call, Decl(nestedGenericSpreadInference.ts, 0, 41))
29+
>wrap : Symbol(wrap, Decl(nestedGenericSpreadInference.ts, 0, 0))
30+
>T : Symbol(T, Decl(nestedGenericSpreadInference.ts, 4, 24))
31+
>x : Symbol(x, Decl(nestedGenericSpreadInference.ts, 4, 27))
32+
>T : Symbol(T, Decl(nestedGenericSpreadInference.ts, 4, 24))
33+
>x : Symbol(x, Decl(nestedGenericSpreadInference.ts, 4, 27))
34+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//// [tests/cases/compiler/nestedGenericSpreadInference.ts] ////
2+
3+
=== nestedGenericSpreadInference.ts ===
4+
declare function wrap<X>(x: X): { x: X };
5+
>wrap : <X>(x: X) => { x: X;}
6+
>x : X
7+
>x : X
8+
9+
declare function call<A extends unknown[], T>(x: { x: (...args: A) => T }, ...args: A): T;
10+
>call : <A extends unknown[], T>(x: { x: (...args: A) => T; }, ...args: A) => T
11+
>x : { x: (...args: A) => T; }
12+
>x : (...args: A) => T
13+
>args : A
14+
>args : A
15+
16+
// This should be of type `number` - ideally, it also would not error.
17+
const leak = call(wrap(<T>(x: T) => x), 1);
18+
>leak : number
19+
>call(wrap(<T>(x: T) => x), 1) : number
20+
>call : <A extends unknown[], T>(x: { x: (...args: A) => T; }, ...args: A) => T
21+
>wrap(<T>(x: T) => x) : { x: (x: A[0]) => A[0]; }
22+
>wrap : <X>(x: X) => { x: X; }
23+
><T>(x: T) => x : <T>(x: T) => T
24+
>x : T
25+
>x : T
26+
>1 : 1
27+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// @strict: true
2+
declare function wrap<X>(x: X): { x: X };
3+
declare function call<A extends unknown[], T>(x: { x: (...args: A) => T }, ...args: A): T;
4+
5+
// This should be of type `number` - ideally, it also would not error.
6+
const leak = call(wrap(<T>(x: T) => x), 1);

0 commit comments

Comments
 (0)