Skip to content

Commit 628da10

Browse files
committed
Eliminate redundant or meaningless elaborations in type relations
1 parent 880e2c0 commit 628da10

File tree

2 files changed

+66
-46
lines changed

2 files changed

+66
-46
lines changed

src/compiler/checker.ts

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11823,6 +11823,16 @@ namespace ts {
1182311823
return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined;
1182411824
}
1182511825

11826+
function hasStructuredOrInstantiableConstraint(type: Type) {
11827+
const constraint = type.flags & TypeFlags.Instantiable ? getConstraintOfType(type) : undefined;
11828+
return constraint && !!(constraint.flags & TypeFlags.StructuredOrInstantiable);
11829+
}
11830+
11831+
function isUnionContainingMultipleObjectLikeTypes(type: Type) {
11832+
return !!(type.flags & TypeFlags.Union) &&
11833+
countWhere((type as UnionType).types, t => !!(t.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Substitution))) >= 2;
11834+
}
11835+
1182611836
function getEffectiveConstraintOfIntersection(types: readonly Type[], targetIsUnion: boolean) {
1182711837
let constraints: Type[] | undefined;
1182811838
let hasDisjointDomainType = false;
@@ -18346,18 +18356,27 @@ namespace ts {
1834618356
let result = Ternary.False;
1834718357
const saveErrorInfo = captureErrorCalculationState();
1834818358

18349-
if ((source.flags & TypeFlags.Union || target.flags & TypeFlags.Union) && getConstituentCount(source) * getConstituentCount(target) < 4) {
18359+
if (source.flags & TypeFlags.UnionOrIntersection || target.flags & TypeFlags.UnionOrIntersection) {
1835018360
// We skip caching when source or target is a union with no more than three constituents.
18351-
result = structuredTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck);
18352-
}
18353-
else if (source.flags & TypeFlags.UnionOrIntersection || target.flags & TypeFlags.UnionOrIntersection) {
18354-
result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck, recursionFlags);
18355-
}
18356-
if (!result && !(source.flags & TypeFlags.Union) && (source.flags & (TypeFlags.StructuredOrInstantiable) || target.flags & TypeFlags.StructuredOrInstantiable)) {
18357-
if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags)) {
18358-
resetErrorInfo(saveErrorInfo);
18361+
result = (source.flags & TypeFlags.Union || target.flags & TypeFlags.Union) && getConstituentCount(source) * getConstituentCount(target) < 4 ?
18362+
structuredTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck) :
18363+
recursiveTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck, recursionFlags);
18364+
// The ordered decomposition above doesn't handle all cases. Specifically, we also need to handle:
18365+
// (1) Source is an intersection of object types { a } & { b } and target is an object type { a, b }.
18366+
// (2) Source is an object type { a, b: boolean } and target is a union { a, b: true } | { a, b: false }.
18367+
// (3) Source is an intersection { a } & { b: boolean } and target is a union { a, b: true } | { a, b: false }.
18368+
// (4) Source is an instantiable type with a union constraint and target is a union.
18369+
if (!result && (source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Object ||
18370+
(source.flags & (TypeFlags.Object | TypeFlags.Intersection) && isUnionContainingMultipleObjectLikeTypes(target)) ||
18371+
(target.flags & TypeFlags.Union && hasStructuredOrInstantiableConstraint(source)))) {
18372+
if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags)) {
18373+
resetErrorInfo(saveErrorInfo);
18374+
}
1835918375
}
1836018376
}
18377+
else if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) {
18378+
result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags);
18379+
}
1836118380
if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) {
1836218381
// The combined constraint of an intersection type is the intersection of the constraints of
1836318382
// the constituents. When an intersection type contains instantiable types with union type
@@ -18628,8 +18647,11 @@ namespace ts {
1862818647
}
1862918648
}
1863018649
if (reportErrors) {
18650+
// Elaborate only if we can find a best matching type in the target union
1863118651
const bestMatchingType = getBestMatchingType(source, target, isRelatedTo);
18632-
isRelatedTo(source, bestMatchingType || targetTypes[targetTypes.length - 1], RecursionFlags.Target, /*reportErrors*/ true);
18652+
if (bestMatchingType) {
18653+
isRelatedTo(source, bestMatchingType, RecursionFlags.Target, /*reportErrors*/ true);
18654+
}
1863318655
}
1863418656
return Ternary.False;
1863518657
}
@@ -35937,34 +35959,26 @@ namespace ts {
3593735959
return;
3593835960
}
3593935961

35962+
let headMessage: DiagnosticMessage;
3594035963
let expectedReturnType: Type;
35941-
const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node);
35942-
let errorInfo: DiagnosticMessageChain | undefined;
3594335964
switch (node.parent.kind) {
3594435965
case SyntaxKind.ClassDeclaration:
35966+
headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1;
3594535967
const classSymbol = getSymbolOfNode(node.parent);
3594635968
const classConstructorType = getTypeOfSymbol(classSymbol);
3594735969
expectedReturnType = getUnionType([classConstructorType, voidType]);
3594835970
break;
3594935971

35950-
case SyntaxKind.Parameter:
35951-
expectedReturnType = voidType;
35952-
errorInfo = chainDiagnosticMessages(
35953-
/*details*/ undefined,
35954-
Diagnostics.The_return_type_of_a_parameter_decorator_function_must_be_either_void_or_any);
35955-
35956-
break;
35957-
3595835972
case SyntaxKind.PropertyDeclaration:
35973+
case SyntaxKind.Parameter:
35974+
headMessage = Diagnostics.Decorator_function_return_type_is_0_but_is_expected_to_be_void_or_any;
3595935975
expectedReturnType = voidType;
35960-
errorInfo = chainDiagnosticMessages(
35961-
/*details*/ undefined,
35962-
Diagnostics.The_return_type_of_a_property_decorator_function_must_be_either_void_or_any);
3596335976
break;
3596435977

3596535978
case SyntaxKind.MethodDeclaration:
3596635979
case SyntaxKind.GetAccessor:
3596735980
case SyntaxKind.SetAccessor:
35981+
headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1;
3596835982
const methodType = getTypeOfNode(node.parent);
3596935983
const descriptorType = createTypedPropertyDescriptorType(methodType);
3597035984
expectedReturnType = getUnionType([descriptorType, voidType]);
@@ -35978,8 +35992,7 @@ namespace ts {
3597835992
returnType,
3597935993
expectedReturnType,
3598035994
node,
35981-
headMessage,
35982-
() => errorInfo);
35995+
headMessage);
3598335996
}
3598435997

3598535998
/**
@@ -44217,27 +44230,26 @@ namespace ts {
4421744230

4421844231
function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) {
4421944232
let bestMatch: Type | undefined;
44220-
let matchingCount = 0;
44221-
for (const target of unionTarget.types) {
44222-
const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]);
44223-
if (overlap.flags & TypeFlags.Index) {
44224-
// perfect overlap of keys
44225-
bestMatch = target;
44226-
matchingCount = Infinity;
44227-
}
44228-
else if (overlap.flags & TypeFlags.Union) {
44229-
// We only want to account for literal types otherwise.
44230-
// If we have a union of index types, it seems likely that we
44231-
// needed to elaborate between two generic mapped types anyway.
44232-
const len = length(filter((overlap as UnionType).types, isUnitType));
44233-
if (len >= matchingCount) {
44234-
bestMatch = target;
44235-
matchingCount = len;
44236-
}
44237-
}
44238-
else if (isUnitType(overlap) && 1 >= matchingCount) {
44239-
bestMatch = target;
44240-
matchingCount = 1;
44233+
if (!(source.flags & (TypeFlags.Primitive | TypeFlags.InstantiablePrimitive))) {
44234+
let matchingCount = 0;
44235+
for (const target of unionTarget.types) {
44236+
if (!(target.flags & (TypeFlags.Primitive | TypeFlags.InstantiablePrimitive))) {
44237+
const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]);
44238+
if (overlap.flags & TypeFlags.Index) {
44239+
// perfect overlap of keys
44240+
return target;
44241+
}
44242+
else if (isUnitType(overlap) || overlap.flags & TypeFlags.Union) {
44243+
// We only want to account for literal types otherwise.
44244+
// If we have a union of index types, it seems likely that we
44245+
// needed to elaborate between two generic mapped types anyway.
44246+
const len = overlap.flags & TypeFlags.Union ? countWhere((overlap as UnionType).types, isUnitType) : 1;
44247+
if (len >= matchingCount) {
44248+
bestMatch = target;
44249+
matchingCount = len;
44250+
}
44251+
}
44252+
}
4424144253
}
4424244254
}
4424344255
return bestMatch;

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,14 @@
867867
"category": "Error",
868868
"code": 1268
869869
},
870+
"Decorator function return type '{0}' is not assignable to type '{1}'.": {
871+
"category": "Error",
872+
"code": 1269
873+
},
874+
"Decorator function return type is '{0}' but is expected to be 'void' or 'any'.": {
875+
"category": "Error",
876+
"code": 1270
877+
},
870878

871879
"'with' statements are not allowed in an async function block.": {
872880
"category": "Error",

0 commit comments

Comments
 (0)