Skip to content

Commit 40905d4

Browse files
committed
Fixed inferred type widening with contextual types created by binding patterns
1 parent 63babdf commit 40905d4

File tree

37 files changed

+1195
-163
lines changed

37 files changed

+1195
-163
lines changed

src/compiler/checker.ts

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1968,10 +1968,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
19681968
var blockedStringType = createIntrinsicType(TypeFlags.Any, "any", /*objectFlags*/ undefined, "blocked string");
19691969
var errorType = createIntrinsicType(TypeFlags.Any, "error");
19701970
var unresolvedType = createIntrinsicType(TypeFlags.Any, "unresolved");
1971-
var nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType, "non-inferrable");
19721971
var intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic");
19731972
var unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown");
19741973
var nonNullUnknownType = createIntrinsicType(TypeFlags.Unknown, "unknown", /*objectFlags*/ undefined, "non-null");
1974+
var nonInferrableUnknownType = createIntrinsicType(TypeFlags.Unknown, "unknown", ObjectFlags.ContainsWideningType, "non-inferrable");
19751975
var undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined");
19761976
var undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType, "widening");
19771977
var missingType = createIntrinsicType(TypeFlags.Undefined, "undefined", /*objectFlags*/ undefined, "missing");
@@ -11444,10 +11444,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1144411444
reportImplicitAny(element, anyType);
1144511445
}
1144611446
// When we're including the pattern in the type (an indication we're obtaining a contextual type), we
11447-
// use a non-inferrable any type. Inference will never directly infer this type, but it is possible
11447+
// use a non-inferrable unknown type. Inference will never directly infer this type, but it is possible
1144811448
// to infer a type that contains it, e.g. for a binding pattern like [foo] or { foo }. In such cases,
11449-
// widening of the binding pattern type substitutes a regular any for the non-inferrable any.
11450-
return includePatternInType ? nonInferrableAnyType : anyType;
11449+
// widening of the binding pattern type substitutes a regular any for the non-inferrable unknown.
11450+
return includePatternInType ? nonInferrableUnknownType : anyType;
1145111451
}
1145211452

1145311453
// Return the type implied by an object binding pattern
@@ -20905,7 +20905,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2090520905
function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map<string, RelationComparisonResult>, errorReporter?: ErrorReporter) {
2090620906
const s = source.flags;
2090720907
const t = target.flags;
20908-
if (t & TypeFlags.Any || s & TypeFlags.Never || source === wildcardType) return true;
20908+
if (t & TypeFlags.Any || s & TypeFlags.Never || source === wildcardType || source === nonInferrableUnknownType) return true;
2090920909
if (t & TypeFlags.Unknown && !(relation === strictSubtypeRelation && s & TypeFlags.Any)) return true;
2091020910
if (t & TypeFlags.Never) return false;
2091120911
if (s & TypeFlags.StringLike && t & TypeFlags.String) return true;
@@ -24603,6 +24603,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2460324603
}
2460424604
else if (isObjectLiteralType(type)) {
2460524605
result = getWidenedTypeOfObjectLiteral(type, context);
24606+
if (type.pattern) {
24607+
result.pattern = type.pattern;
24608+
}
2460624609
}
2460724610
else if (type.flags & TypeFlags.Union) {
2460824611
const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type as UnionType).types);
@@ -24617,6 +24620,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2461724620
}
2461824621
else if (isArrayOrTupleType(type)) {
2461924622
result = createTypeReference(type.target, sameMap(getTypeArguments(type), getWidenedType));
24623+
if (type.pattern) {
24624+
result.pattern = type.pattern;
24625+
}
2462024626
}
2462124627
if (result && context === undefined) {
2462224628
type.widened = result;
@@ -25025,21 +25031,34 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2502525031
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been
2502625032
// applied to the element type(s).
2502725033
if (isArrayType(source)) {
25028-
return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source));
25034+
let reversed = createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source)) as TypeReference;
25035+
if (source.pattern) {
25036+
reversed = cloneTypeReference(reversed);
25037+
reversed.pattern = source.pattern;
25038+
}
25039+
return reversed;
2502925040
}
2503025041
if (isTupleType(source)) {
2503125042
const elementTypes = map(getElementTypes(source), t => inferReverseMappedType(t, target, constraint));
2503225043
const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ?
2503325044
sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) :
2503425045
source.target.elementFlags;
25035-
return createTupleType(elementTypes, elementFlags, source.target.readonly, source.target.labeledElementDeclarations);
25046+
let reversed = createTupleType(elementTypes, elementFlags, source.target.readonly, source.target.labeledElementDeclarations) as TypeReference;
25047+
if (source.pattern) {
25048+
reversed = cloneTypeReference(reversed);
25049+
reversed.pattern = source.pattern;
25050+
}
25051+
return reversed;
2503625052
}
2503725053
// For all other object types we infer a new object type where the reverse mapping has been
2503825054
// applied to the type of each property.
2503925055
const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType;
2504025056
reversed.source = source;
2504125057
reversed.mappedType = target;
2504225058
reversed.constraintType = constraint;
25059+
if (source.pattern) {
25060+
reversed.pattern = source.pattern;
25061+
}
2504325062
return reversed;
2504425063
}
2504525064

@@ -25056,7 +25075,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2505625075
const templateType = getTemplateTypeFromMappedType(target);
2505725076
const inference = createInferenceInfo(typeParameter);
2505825077
inferTypes([inference], sourceType, templateType);
25059-
return getTypeFromInference(inference) || unknownType;
25078+
return getTypeFromInference(inference) || (sourceType.pattern ? nonInferrableUnknownType : unknownType);
2506025079
}
2506125080

2506225081
function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator<Symbol> {
@@ -25089,8 +25108,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2508925108
}
2509025109

2509125110
function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) {
25092-
return !(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength ||
25093-
!target.target.hasRestElement && (source.target.hasRestElement || target.target.fixedLength < source.target.fixedLength);
25111+
return !source.pattern && (!(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength ||
25112+
!target.target.hasRestElement && (source.target.hasRestElement || target.target.fixedLength < source.target.fixedLength));
2509425113
}
2509525114

2509625115
function typesDefinitelyUnrelated(source: Type, target: Type) {
@@ -25396,9 +25415,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2539625415
// This flag is infectious; if we produce Box<never> (where never is silentNeverType), Box<never> is
2539725416
// also non-inferrable.
2539825417
//
25399-
// As a special case, also ignore nonInferrableAnyType, which is a special form of the any type
25400-
// used as a stand-in for binding elements when they are being inferred.
25401-
if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType) {
25418+
// As a special case, also ignore nonInferrableUnknownType, which is a special form of the unknown type
25419+
// used as a stand-in for binding elements when they are being inferred. It is ignored as a direct inference candidate
25420+
// but types containing it can be inferred.
25421+
if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableUnknownType) {
2540225422
return;
2540325423
}
2540425424
if (!inference.isFixed) {
@@ -26099,12 +26119,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2609926119
function getInferredType(context: InferenceContext, index: number): Type {
2610026120
const inference = context.inferences[index];
2610126121
if (!inference.inferredType) {
26122+
let isFromBindingPattern = false;
2610226123
let inferredType: Type | undefined;
2610326124
let fallbackType: Type | undefined;
2610426125
if (context.signature) {
2610526126
const inferredCovariantType = inference.candidates ? getCovariantInference(inference, context.signature) : undefined;
2610626127
const inferredContravariantType = inference.contraCandidates ? getContravariantInference(inference) : undefined;
26107-
if (inferredCovariantType || inferredContravariantType) {
26128+
if (!inferredContravariantType && inference.candidates?.length === 1 && inference.candidates[0].pattern) {
26129+
isFromBindingPattern = true;
26130+
inferredType = inferredCovariantType;
26131+
}
26132+
else if (inferredCovariantType || inferredContravariantType) {
2610826133
// If we have both co- and contra-variant inferences, we prefer the co-variant inference if it is not 'never',
2610926134
// all co-variant inferences are subtypes of it (i.e. it isn't one of a conflicting set of candidates), it is
2611026135
// a subtype of some contra-variant inference, and no other type parameter is constrained to this type parameter
@@ -26145,7 +26170,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2614526170
const constraint = getConstraintOfTypeParameter(inference.typeParameter);
2614626171
if (constraint) {
2614726172
const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper);
26148-
if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
26173+
if (isFromBindingPattern) {
26174+
inference.inferredType = getIntersectionType([inference.inferredType, instantiatedConstraint]);
26175+
}
26176+
else if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
2614926177
// If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint.
2615026178
inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint;
2615126179
}
@@ -30279,7 +30307,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3027930307
else if (t.flags & TypeFlags.StructuredType) {
3028030308
const prop = getPropertyOfType(t, name);
3028130309
if (prop) {
30282-
return isCircularMappedProperty(prop) ? undefined : removeMissingType(getTypeOfSymbol(prop), !!(prop && prop.flags & SymbolFlags.Optional));
30310+
if (isCircularMappedProperty(prop)) {
30311+
return undefined;
30312+
}
30313+
const type = getTypeOfSymbol(prop);
30314+
if (type !== nonInferrableUnknownType) {
30315+
return removeMissingType(type, !!(prop && prop.flags & SymbolFlags.Optional));
30316+
}
30317+
if (t.flags & TypeFlags.Intersection) {
30318+
t = getIntersectionType((t as IntersectionType).types.filter(t => !t.pattern));
30319+
}
3028330320
}
3028430321
if (isTupleType(t) && isNumericLiteralName(name) && +name >= 0) {
3028530322
const restType = getElementTypeOfSliceOfTupleType(t, t.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true);
@@ -31248,7 +31285,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3124831285

3124931286
pushCachedContextualType(node);
3125031287
const contextualType = getApparentTypeOfContextualType(node, /*contextFlags*/ undefined);
31251-
const contextualTypeHasPattern = contextualType && contextualType.pattern &&
31288+
const contextualTypeHasPattern = !(checkMode & CheckMode.Inferential) && contextualType && contextualType.pattern &&
3125231289
(contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression);
3125331290
const inConstContext = isConstContext(node);
3125431291
const checkFlags = inConstContext ? CheckFlags.Readonly : 0;

tests/baselines/reference/bindingPatternContextualTypeDoesNotCauseWidening.js

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//// [tests/cases/compiler/bindingPatternContextualTypeDoesNotCauseWidening1.ts] ////
2+
3+
//// [bindingPatternContextualTypeDoesNotCauseWidening1.ts]
4+
declare function pick<O, T extends keyof O>(keys: T[], obj?: O): Pick<O, T>;
5+
const _ = pick(['b'], { a: 'a', b: 'b' }); // T: "b"
6+
const { } = pick(['b'], { a: 'a', b: 'b' }); // T: "b"
7+
8+
9+
//// [bindingPatternContextualTypeDoesNotCauseWidening1.js]
10+
var _ = pick(['b'], { a: 'a', b: 'b' }); // T: "b"
11+
var _a = pick(['b'], { a: 'a', b: 'b' }); // T: "b"
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
//// [tests/cases/compiler/bindingPatternContextualTypeDoesNotCauseWidening.ts] ////
1+
//// [tests/cases/compiler/bindingPatternContextualTypeDoesNotCauseWidening1.ts] ////
22

3-
=== bindingPatternContextualTypeDoesNotCauseWidening.ts ===
3+
=== bindingPatternContextualTypeDoesNotCauseWidening1.ts ===
44
declare function pick<O, T extends keyof O>(keys: T[], obj?: O): Pick<O, T>;
5-
>pick : Symbol(pick, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 0, 0))
6-
>O : Symbol(O, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 0, 22))
7-
>T : Symbol(T, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 0, 24))
8-
>O : Symbol(O, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 0, 22))
9-
>keys : Symbol(keys, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 0, 44))
10-
>T : Symbol(T, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 0, 24))
11-
>obj : Symbol(obj, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 0, 54))
12-
>O : Symbol(O, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 0, 22))
5+
>pick : Symbol(pick, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 0, 0))
6+
>O : Symbol(O, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 0, 22))
7+
>T : Symbol(T, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 0, 24))
8+
>O : Symbol(O, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 0, 22))
9+
>keys : Symbol(keys, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 0, 44))
10+
>T : Symbol(T, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 0, 24))
11+
>obj : Symbol(obj, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 0, 54))
12+
>O : Symbol(O, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 0, 22))
1313
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
14-
>O : Symbol(O, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 0, 22))
15-
>T : Symbol(T, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 0, 24))
14+
>O : Symbol(O, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 0, 22))
15+
>T : Symbol(T, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 0, 24))
1616

1717
const _ = pick(['b'], { a: 'a', b: 'b' }); // T: "b"
18-
>_ : Symbol(_, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 1, 5))
19-
>pick : Symbol(pick, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 0, 0))
20-
>a : Symbol(a, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 1, 26))
21-
>b : Symbol(b, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 1, 34))
18+
>_ : Symbol(_, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 1, 5))
19+
>pick : Symbol(pick, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 0, 0))
20+
>a : Symbol(a, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 1, 26))
21+
>b : Symbol(b, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 1, 34))
2222

23-
const { } = pick(['b'], { a: 'a', b: 'b' }); // T: "b" | "a" ??? (before fix)
24-
>pick : Symbol(pick, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 0, 0))
25-
>a : Symbol(a, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 2, 26))
26-
>b : Symbol(b, Decl(bindingPatternContextualTypeDoesNotCauseWidening.ts, 2, 34))
23+
const { } = pick(['b'], { a: 'a', b: 'b' }); // T: "b"
24+
>pick : Symbol(pick, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 0, 0))
25+
>a : Symbol(a, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 2, 26))
26+
>b : Symbol(b, Decl(bindingPatternContextualTypeDoesNotCauseWidening1.ts, 2, 34))
2727

tests/baselines/reference/bindingPatternContextualTypeDoesNotCauseWidening.types renamed to tests/baselines/reference/bindingPatternContextualTypeDoesNotCauseWidening1.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
//// [tests/cases/compiler/bindingPatternContextualTypeDoesNotCauseWidening.ts] ////
1+
//// [tests/cases/compiler/bindingPatternContextualTypeDoesNotCauseWidening1.ts] ////
22

3-
=== bindingPatternContextualTypeDoesNotCauseWidening.ts ===
3+
=== bindingPatternContextualTypeDoesNotCauseWidening1.ts ===
44
declare function pick<O, T extends keyof O>(keys: T[], obj?: O): Pick<O, T>;
55
>pick : <O, T extends keyof O>(keys: T[], obj?: O) => Pick<O, T>
66
>keys : T[]
@@ -18,7 +18,7 @@ const _ = pick(['b'], { a: 'a', b: 'b' }); // T: "b"
1818
>b : string
1919
>'b' : "b"
2020

21-
const { } = pick(['b'], { a: 'a', b: 'b' }); // T: "b" | "a" ??? (before fix)
21+
const { } = pick(['b'], { a: 'a', b: 'b' }); // T: "b"
2222
>pick(['b'], { a: 'a', b: 'b' }) : Pick<{ a: string; b: string; }, "b">
2323
>pick : <O, T extends keyof O>(keys: T[], obj?: O) => Pick<O, T>
2424
>['b'] : "b"[]

0 commit comments

Comments
 (0)