Skip to content

Commit 99ddaa5

Browse files
Merge pull request #28942 from Microsoft/missing3.2Prs
Port missing 3.2 PRs
2 parents 3d2d8c3 + 87bed40 commit 99ddaa5

19 files changed

Lines changed: 855 additions & 50 deletions

src/compiler/checker.ts

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9786,31 +9786,29 @@ namespace ts {
97869786
if (checkType === wildcardType || extendsType === wildcardType) {
97879787
return wildcardType;
97889788
}
9789-
// If this is a distributive conditional type and the check type is generic we need to defer
9790-
// resolution of the conditional type such that a later instantiation will properly distribute
9791-
// over union types.
9792-
const isDeferred = root.isDistributive && maybeTypeOfKind(checkType, TypeFlags.Instantiable);
9789+
const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable);
97939790
let combinedMapper: TypeMapper | undefined;
97949791
if (root.inferTypeParameters) {
97959792
const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None);
9796-
if (!isDeferred) {
9793+
if (!checkTypeInstantiable) {
97979794
// We don't want inferences from constraints as they may cause us to eagerly resolve the
97989795
// conditional type instead of deferring resolution. Also, we always want strict function
97999796
// types rules (i.e. proper contravariance) for inferences.
98009797
inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
98019798
}
98029799
combinedMapper = combineTypeMappers(mapper, context);
98039800
}
9804-
if (!isDeferred) {
9805-
if (extendsType.flags & TypeFlags.AnyOrUnknown) {
9801+
// Instantiate the extends type including inferences for 'infer T' type parameters
9802+
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
9803+
// We attempt to resolve the conditional type only when the check and extends types are non-generic
9804+
if (!checkTypeInstantiable && !maybeTypeOfKind(inferredExtendsType, TypeFlags.Instantiable)) {
9805+
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown) {
98069806
return instantiateType(root.trueType, mapper);
98079807
}
98089808
// Return union of trueType and falseType for 'any' since it matches anything
98099809
if (checkType.flags & TypeFlags.Any) {
98109810
return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]);
98119811
}
9812-
// Instantiate the extends type including inferences for 'infer T' type parameters
9813-
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
98149812
// Return falseType for a definitely false extends check. We check an instantations of the two
98159813
// types with type parameters mapped to the wildcard type, the most permissive instantiations
98169814
// possible (the wildcard type is assignable to and from all types). If those are not related,
@@ -11838,7 +11836,7 @@ namespace ts {
1183811836
if (!noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) {
1183911837
return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny
1184011838
}
11841-
if (maybeTypeOfKind(target, TypeFlags.Object) && !(getObjectFlags(target) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) {
11839+
if (isExcessPropertyCheckTarget(target)) {
1184211840
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
1184311841
if ((relation === assignableRelation || relation === definitelyAssignableRelation || relation === comparableRelation) &&
1184411842
(isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) {
@@ -11851,6 +11849,9 @@ namespace ts {
1185111849
for (const prop of getPropertiesOfObjectType(source)) {
1185211850
if (shouldCheckAsExcessProperty(prop, source.symbol) && !isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) {
1185311851
if (reportErrors) {
11852+
// Report error in terms of object types in the target as those are the only ones
11853+
// we check in isKnownProperty.
11854+
const errorTarget = filterType(target, isExcessPropertyCheckTarget);
1185411855
// We know *exactly* where things went wrong when comparing the types.
1185511856
// Use this property as the error node as this will be more helpful in
1185611857
// reasoning about what went wrong.
@@ -11859,7 +11860,7 @@ namespace ts {
1185911860
// JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal.
1186011861
// However, using an object-literal error message will be very confusing to the users so we give different a message.
1186111862
// TODO: Spelling suggestions for excess jsx attributes (needs new diagnostic messages)
11862-
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(prop), typeToString(target));
11863+
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(prop), typeToString(errorTarget));
1186311864
}
1186411865
else {
1186511866
// use the property's value declaration if the property is assigned inside the literal itself
@@ -11873,17 +11874,17 @@ namespace ts {
1187311874

1187411875
const name = propDeclaration.name!;
1187511876
if (isIdentifier(name)) {
11876-
suggestion = getSuggestionForNonexistentProperty(name, target);
11877+
suggestion = getSuggestionForNonexistentProperty(name, errorTarget);
1187711878
}
1187811879
}
1187911880

1188011881
if (suggestion !== undefined) {
1188111882
reportError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2,
11882-
symbolToString(prop), typeToString(target), suggestion);
11883+
symbolToString(prop), typeToString(errorTarget), suggestion);
1188311884
}
1188411885
else {
1188511886
reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1,
11886-
symbolToString(prop), typeToString(target));
11887+
symbolToString(prop), typeToString(errorTarget));
1188711888
}
1188811889
}
1188911890
}
@@ -14616,15 +14617,15 @@ namespace ts {
1461614617
getAccessedPropertyName(source as PropertyAccessExpression | ElementAccessExpression) === getAccessedPropertyName(target) &&
1461714618
isMatchingReference((source as PropertyAccessExpression | ElementAccessExpression).expression, target.expression);
1461814619
case SyntaxKind.BindingElement:
14619-
if (target.kind !== SyntaxKind.PropertyAccessExpression) return false;
14620-
const t = target as PropertyAccessExpression;
14621-
if (t.name.escapedText !== getBindingElementNameText(source as BindingElement)) return false;
14622-
if (source.parent.parent.kind === SyntaxKind.BindingElement && isMatchingReference(source.parent.parent, t.expression)) {
14623-
return true;
14624-
}
14625-
if (source.parent.parent.kind === SyntaxKind.VariableDeclaration) {
14626-
const maybeId = (source.parent.parent as VariableDeclaration).initializer;
14627-
return !!maybeId && isMatchingReference(maybeId, t.expression);
14620+
if (target.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>target).name.escapedText === getBindingElementNameText(<BindingElement>source)) {
14621+
const ancestor = source.parent.parent;
14622+
if (ancestor.kind === SyntaxKind.BindingElement) {
14623+
return isMatchingReference(ancestor, (<PropertyAccessExpression>target).expression);
14624+
}
14625+
if (ancestor.kind === SyntaxKind.VariableDeclaration) {
14626+
const initializer = (<VariableDeclaration>ancestor).initializer;
14627+
return !!initializer && isMatchingReference(initializer, (<PropertyAccessExpression>target).expression);
14628+
}
1462814629
}
1462914630
}
1463014631
return false;
@@ -14636,14 +14637,25 @@ namespace ts {
1463614637
undefined;
1463714638
}
1463814639

14640+
function getReferenceParent(source: Node) {
14641+
if (source.kind === SyntaxKind.PropertyAccessExpression) {
14642+
return (<PropertyAccessExpression>source).expression;
14643+
}
14644+
if (source.kind === SyntaxKind.BindingElement) {
14645+
const ancestor = source.parent.parent;
14646+
return ancestor.kind === SyntaxKind.VariableDeclaration ? (<VariableDeclaration>ancestor).initializer : ancestor;
14647+
}
14648+
return undefined;
14649+
}
14650+
1463914651
function containsMatchingReference(source: Node, target: Node) {
14640-
while (source.kind === SyntaxKind.PropertyAccessExpression) {
14641-
source = (<PropertyAccessExpression>source).expression;
14642-
if (isMatchingReference(source, target)) {
14652+
let parent = getReferenceParent(source);
14653+
while (parent) {
14654+
if (isMatchingReference(parent, target)) {
1464314655
return true;
1464414656
}
14657+
parent = getReferenceParent(parent);
1464514658
}
14646-
return false;
1464714659
}
1464814660

1464914661
// Return true if target is a property access xxx.yyy, source is a property access xxx.zzz, the declared
@@ -18585,20 +18597,23 @@ namespace ts {
1858518597
return true;
1858618598
}
1858718599
}
18588-
else if (targetType.flags & TypeFlags.UnionOrIntersection) {
18600+
else if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) {
1858918601
for (const t of (targetType as UnionOrIntersectionType).types) {
1859018602
if (isKnownProperty(t, name, isComparingJsxAttributes)) {
1859118603
return true;
1859218604
}
1859318605
}
1859418606
}
18595-
else if (targetType.flags & TypeFlags.Conditional) {
18596-
return isKnownProperty((targetType as ConditionalType).root.trueType, name, isComparingJsxAttributes) ||
18597-
isKnownProperty((targetType as ConditionalType).root.falseType, name, isComparingJsxAttributes);
18598-
}
1859918607
return false;
1860018608
}
1860118609

18610+
function isExcessPropertyCheckTarget(type: Type): boolean {
18611+
return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) ||
18612+
type.flags & TypeFlags.NonPrimitive ||
18613+
type.flags & TypeFlags.Union && some((<UnionType>type).types, isExcessPropertyCheckTarget) ||
18614+
type.flags & TypeFlags.Intersection && every((<IntersectionType>type).types, isExcessPropertyCheckTarget));
18615+
}
18616+
1860218617
function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) {
1860318618
if (node.expression) {
1860418619
const type = checkExpression(node.expression, checkMode);

tests/baselines/reference/conditionalTypes2.errors.txt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,4 +203,47 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
203203

204204
type C2<T, V, E> =
205205
T extends object ? { [Q in keyof T]: C2<T[Q], V, E>; } : T;
206+
207+
// Repro from #28654
208+
209+
type MaybeTrue<T extends { b: boolean }> = true extends T["b"] ? "yes" : "no";
210+
211+
type T0 = MaybeTrue<{ b: never }> // "no"
212+
type T1 = MaybeTrue<{ b: false }>; // "no"
213+
type T2 = MaybeTrue<{ b: true }>; // "yes"
214+
type T3 = MaybeTrue<{ b: boolean }>; // "yes"
215+
216+
// Repro from #28824
217+
218+
type Union = 'a' | 'b';
219+
type Product<A extends Union, B> = { f1: A, f2: B};
220+
type ProductUnion = Product<'a', 0> | Product<'b', 1>;
221+
222+
// {a: "b"; b: "a"}
223+
type UnionComplement = {
224+
[K in Union]: Exclude<Union, K>
225+
};
226+
type UCA = UnionComplement['a'];
227+
type UCB = UnionComplement['b'];
228+
229+
// {a: "a"; b: "b"}
230+
type UnionComplementComplement = {
231+
[K in Union]: Exclude<Union, Exclude<Union, K>>
232+
};
233+
type UCCA = UnionComplementComplement['a'];
234+
type UCCB = UnionComplementComplement['b'];
235+
236+
// {a: Product<'b', 1>; b: Product<'a', 0>}
237+
type ProductComplement = {
238+
[K in Union]: Exclude<ProductUnion, { f1: K }>
239+
};
240+
type PCA = ProductComplement['a'];
241+
type PCB = ProductComplement['b'];
242+
243+
// {a: Product<'a', 0>; b: Product<'b', 1>}
244+
type ProductComplementComplement = {
245+
[K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, { f1: K }>>
246+
};
247+
type PCCA = ProductComplementComplement['a'];
248+
type PCCB = ProductComplementComplement['b'];
206249

tests/baselines/reference/conditionalTypes2.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,49 @@ type B2<T, V> =
145145

146146
type C2<T, V, E> =
147147
T extends object ? { [Q in keyof T]: C2<T[Q], V, E>; } : T;
148+
149+
// Repro from #28654
150+
151+
type MaybeTrue<T extends { b: boolean }> = true extends T["b"] ? "yes" : "no";
152+
153+
type T0 = MaybeTrue<{ b: never }> // "no"
154+
type T1 = MaybeTrue<{ b: false }>; // "no"
155+
type T2 = MaybeTrue<{ b: true }>; // "yes"
156+
type T3 = MaybeTrue<{ b: boolean }>; // "yes"
157+
158+
// Repro from #28824
159+
160+
type Union = 'a' | 'b';
161+
type Product<A extends Union, B> = { f1: A, f2: B};
162+
type ProductUnion = Product<'a', 0> | Product<'b', 1>;
163+
164+
// {a: "b"; b: "a"}
165+
type UnionComplement = {
166+
[K in Union]: Exclude<Union, K>
167+
};
168+
type UCA = UnionComplement['a'];
169+
type UCB = UnionComplement['b'];
170+
171+
// {a: "a"; b: "b"}
172+
type UnionComplementComplement = {
173+
[K in Union]: Exclude<Union, Exclude<Union, K>>
174+
};
175+
type UCCA = UnionComplementComplement['a'];
176+
type UCCB = UnionComplementComplement['b'];
177+
178+
// {a: Product<'b', 1>; b: Product<'a', 0>}
179+
type ProductComplement = {
180+
[K in Union]: Exclude<ProductUnion, { f1: K }>
181+
};
182+
type PCA = ProductComplement['a'];
183+
type PCB = ProductComplement['b'];
184+
185+
// {a: Product<'a', 0>; b: Product<'b', 1>}
186+
type ProductComplementComplement = {
187+
[K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, { f1: K }>>
188+
};
189+
type PCCA = ProductComplementComplement['a'];
190+
type PCCB = ProductComplementComplement['b'];
148191

149192

150193
//// [conditionalTypes2.js]
@@ -304,3 +347,48 @@ declare type B2<T, V> = T extends object ? T extends any[] ? T : {
304347
declare type C2<T, V, E> = T extends object ? {
305348
[Q in keyof T]: C2<T[Q], V, E>;
306349
} : T;
350+
declare type MaybeTrue<T extends {
351+
b: boolean;
352+
}> = true extends T["b"] ? "yes" : "no";
353+
declare type T0 = MaybeTrue<{
354+
b: never;
355+
}>;
356+
declare type T1 = MaybeTrue<{
357+
b: false;
358+
}>;
359+
declare type T2 = MaybeTrue<{
360+
b: true;
361+
}>;
362+
declare type T3 = MaybeTrue<{
363+
b: boolean;
364+
}>;
365+
declare type Union = 'a' | 'b';
366+
declare type Product<A extends Union, B> = {
367+
f1: A;
368+
f2: B;
369+
};
370+
declare type ProductUnion = Product<'a', 0> | Product<'b', 1>;
371+
declare type UnionComplement = {
372+
[K in Union]: Exclude<Union, K>;
373+
};
374+
declare type UCA = UnionComplement['a'];
375+
declare type UCB = UnionComplement['b'];
376+
declare type UnionComplementComplement = {
377+
[K in Union]: Exclude<Union, Exclude<Union, K>>;
378+
};
379+
declare type UCCA = UnionComplementComplement['a'];
380+
declare type UCCB = UnionComplementComplement['b'];
381+
declare type ProductComplement = {
382+
[K in Union]: Exclude<ProductUnion, {
383+
f1: K;
384+
}>;
385+
};
386+
declare type PCA = ProductComplement['a'];
387+
declare type PCB = ProductComplement['b'];
388+
declare type ProductComplementComplement = {
389+
[K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, {
390+
f1: K;
391+
}>>;
392+
};
393+
declare type PCCA = ProductComplementComplement['a'];
394+
declare type PCCB = ProductComplementComplement['b'];

0 commit comments

Comments
 (0)