Skip to content

Commit 2100e40

Browse files
committed
Centralize weak type checking + improve error message
1 parent 04c26b7 commit 2100e40

File tree

2 files changed

+53
-78
lines changed

2 files changed

+53
-78
lines changed

src/compiler/checker.ts

Lines changed: 52 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8701,7 +8701,7 @@ namespace ts {
87018701
let expandingFlags: number;
87028702
let depth = 0;
87038703
let overflow = false;
8704-
let disableWeakTypeCheckingForIntersectionConstituents = false;
8704+
let isIntersectionConstituent = false;
87058705

87068706
Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking");
87078707

@@ -8784,7 +8784,6 @@ namespace ts {
87848784
* * Ternary.False if they are not related.
87858785
*/
87868786
function isRelatedTo(source: Type, target: Type, reportErrors?: boolean, headMessage?: DiagnosticMessage): Ternary {
8787-
let result: Ternary;
87888787
if (source.flags & TypeFlags.StringOrNumberLiteral && source.flags & TypeFlags.FreshLiteral) {
87898788
source = (<LiteralType>source).regularType;
87908789
}
@@ -8816,32 +8815,39 @@ namespace ts {
88168815
}
88178816
}
88188817

8818+
if (!(source.flags & TypeFlags.UnionOrIntersection) &&
8819+
!(target.flags & TypeFlags.Union) &&
8820+
!isIntersectionConstituent &&
8821+
source !== globalObjectType &&
8822+
getPropertiesOfType(source).length > 0 &&
8823+
isWeakType(target) &&
8824+
!hasCommonProperties(source, target)) {
8825+
if (reportErrors) {
8826+
reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, typeToString(source), typeToString(target));
8827+
}
8828+
return Ternary.False;
8829+
}
8830+
8831+
let result = Ternary.False;
88198832
const saveErrorInfo = errorInfo;
8833+
const saveIsIntersectionConstituent = isIntersectionConstituent;
8834+
isIntersectionConstituent = false;
88208835

88218836
// Note that these checks are specifically ordered to produce correct results. In particular,
88228837
// we need to deconstruct unions before intersections (because unions are always at the top),
88238838
// and we need to handle "each" relations before "some" relations for the same kind of type.
88248839
if (source.flags & TypeFlags.Union) {
8825-
if (relation === comparableRelation) {
8826-
result = someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive));
8827-
}
8828-
else {
8829-
result = eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive));
8830-
}
8831-
if (result) {
8832-
return result;
8833-
}
8840+
result = relation === comparableRelation ?
8841+
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive)) :
8842+
eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive));
88348843
}
88358844
else {
88368845
if (target.flags & TypeFlags.Union) {
8837-
if (result = typeRelatedToSomeType(source, <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive))) {
8838-
return result;
8839-
}
8846+
result = typeRelatedToSomeType(source, <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive));
88408847
}
88418848
else if (target.flags & TypeFlags.Intersection) {
8842-
if (result = typeRelatedToEachType(source, target as IntersectionType, reportErrors)) {
8843-
return result;
8844-
}
8849+
isIntersectionConstituent = true;
8850+
result = typeRelatedToEachType(source, target as IntersectionType, reportErrors);
88458851
}
88468852
else if (source.flags & TypeFlags.Intersection) {
88478853
// Check to see if any constituents of the intersection are immediately related to the target.
@@ -8857,20 +8863,18 @@ namespace ts {
88578863
//
88588864
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
88598865
// breaking the intersection apart.
8860-
if (result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false)) {
8861-
return result;
8862-
}
8866+
result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false);
88638867
}
8864-
8865-
if (source.flags & TypeFlags.StructuredOrTypeVariable || target.flags & TypeFlags.StructuredOrTypeVariable) {
8868+
if (!result && (source.flags & TypeFlags.StructuredOrTypeVariable || target.flags & TypeFlags.StructuredOrTypeVariable)) {
88668869
if (result = recursiveTypeRelatedTo(source, target, reportErrors)) {
88678870
errorInfo = saveErrorInfo;
8868-
return result;
88698871
}
88708872
}
88718873
}
88728874

8873-
if (reportErrors) {
8875+
isIntersectionConstituent = saveIsIntersectionConstituent;
8876+
8877+
if (!result && reportErrors) {
88748878
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) {
88758879
tryElaborateErrorsForPrimitivesAndObjects(source, target);
88768880
}
@@ -8879,7 +8883,7 @@ namespace ts {
88798883
}
88808884
reportRelationError(headMessage, source, target);
88818885
}
8882-
return Ternary.False;
8886+
return result;
88838887
}
88848888

88858889
function isIdenticalTo(source: Type, target: Type): Ternary {
@@ -8981,39 +8985,14 @@ namespace ts {
89818985
function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean): Ternary {
89828986
let result = Ternary.True;
89838987
const targetTypes = target.types;
8984-
const saveDisableWeakTypeCheckingForIntersectionConstituents = disableWeakTypeCheckingForIntersectionConstituents;
8985-
disableWeakTypeCheckingForIntersectionConstituents = true;
89868988
for (const targetType of targetTypes) {
89878989
const related = isRelatedTo(source, targetType, reportErrors);
89888990
if (!related) {
8989-
disableWeakTypeCheckingForIntersectionConstituents = saveDisableWeakTypeCheckingForIntersectionConstituents;
89908991
return Ternary.False;
89918992
}
89928993
result &= related;
89938994
}
8994-
disableWeakTypeCheckingForIntersectionConstituents = saveDisableWeakTypeCheckingForIntersectionConstituents;
8995-
return reportAssignmentToWeakIntersection(source, target, reportErrors) ? Ternary.False : result;
8996-
}
8997-
8998-
/**
8999-
* An intersection is weak if all of its constituents are weak. Report an error on assignment to a weak intersection
9000-
* of a type that doesn't share any property names with it.
9001-
*
9002-
* Note: This function could create an anonymous type of the flattened intersection properties and call isRelatedTo,
9003-
* but this makes React's already-bad weak type errors even more confusing.
9004-
*/
9005-
function reportAssignmentToWeakIntersection(source: Type, target: IntersectionType, reportErrors: boolean) {
9006-
const needsWeakTypeCheck = source !== globalObjectType && getPropertiesOfType(source).length > 0 && every(target.types, isWeakType);
9007-
if (!needsWeakTypeCheck) {
9008-
return false;
9009-
}
9010-
const hasSharedProperty = forEach(
9011-
getPropertiesOfType(source),
9012-
p => isKnownProperty(target, p.name, /*isComparingJsxAttributes*/ false));
9013-
if (!hasSharedProperty && reportErrors) {
9014-
reportError(Diagnostics.Weak_type_0_has_no_properties_in_common_with_1, typeToString(target), typeToString(source));
9015-
}
9016-
return !hasSharedProperty;
8995+
return result;
90178996
}
90188997

90198998
function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean): Ternary {
@@ -9303,13 +9282,8 @@ namespace ts {
93039282
let result = Ternary.True;
93049283
const properties = getPropertiesOfObjectType(target);
93059284
const requireOptionalProperties = relation === subtypeRelation && !(getObjectFlags(source) & ObjectFlags.ObjectLiteral);
9306-
let foundMatchingProperty = !isWeakType(target);
93079285
for (const targetProp of properties) {
93089286
const sourceProp = getPropertyOfType(source, targetProp.name);
9309-
if (sourceProp) {
9310-
foundMatchingProperty = true;
9311-
}
9312-
93139287
if (sourceProp !== targetProp) {
93149288
if (!sourceProp) {
93159289
if (!(targetProp.flags & SymbolFlags.Optional) || requireOptionalProperties) {
@@ -9359,10 +9333,7 @@ namespace ts {
93599333
}
93609334
return Ternary.False;
93619335
}
9362-
const saveDisableWeakTypeCheckingForIntersectionConstituents = disableWeakTypeCheckingForIntersectionConstituents;
9363-
disableWeakTypeCheckingForIntersectionConstituents = false;
93649336
const related = isRelatedTo(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp), reportErrors);
9365-
disableWeakTypeCheckingForIntersectionConstituents = saveDisableWeakTypeCheckingForIntersectionConstituents;
93669337
if (!related) {
93679338
if (reportErrors) {
93689339
reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp));
@@ -9388,31 +9359,35 @@ namespace ts {
93889359
}
93899360
}
93909361
}
9391-
if (!foundMatchingProperty &&
9392-
!disableWeakTypeCheckingForIntersectionConstituents &&
9393-
source !== globalObjectType &&
9394-
getPropertiesOfType(source).length > 0) {
9395-
if (reportErrors) {
9396-
reportError(Diagnostics.Weak_type_0_has_no_properties_in_common_with_1, typeToString(target), typeToString(source));
9397-
}
9398-
return Ternary.False;
9399-
}
94009362
return result;
94019363
}
94029364

94039365
/**
94049366
* A type is 'weak' if it is an object type with at least one optional property
94059367
* and no required properties, call/construct signatures or index signatures
94069368
*/
9407-
function isWeakType(type: Type) {
9408-
const props = getPropertiesOfType(type);
9409-
return type.flags & TypeFlags.Object &&
9410-
props.length > 0 &&
9411-
every(props, p => !!(p.flags & SymbolFlags.Optional)) &&
9412-
!getSignaturesOfType(type, SignatureKind.Call).length &&
9413-
!getSignaturesOfType(type, SignatureKind.Construct).length &&
9414-
!getIndexTypeOfType(type, IndexKind.String) &&
9415-
!getIndexTypeOfType(type, IndexKind.Number);
9369+
function isWeakType(type: Type): boolean {
9370+
if (type.flags & TypeFlags.Object) {
9371+
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
9372+
return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 &&
9373+
!resolved.stringIndexInfo && !resolved.numberIndexInfo &&
9374+
resolved.properties.length > 0 &&
9375+
every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional));
9376+
}
9377+
if (type.flags & TypeFlags.Intersection) {
9378+
return every((<IntersectionType>type).types, isWeakType);
9379+
}
9380+
return false;
9381+
}
9382+
9383+
function hasCommonProperties(source: Type, target: Type) {
9384+
const isComparingJsxAttributes = !!(source.flags & TypeFlags.JsxAttributes);
9385+
for (const prop of getPropertiesOfType(source)) {
9386+
if (isKnownProperty(target, prop.name, isComparingJsxAttributes)) {
9387+
return true;
9388+
}
9389+
}
9390+
return false;
94169391
}
94179392

94189393
function propertiesIdenticalTo(source: Type, target: Type): Ternary {

src/compiler/diagnosticMessages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1879,7 +1879,7 @@
18791879
"category": "Error",
18801880
"code": 2558
18811881
},
1882-
"Weak type '{0}' has no properties in common with '{1}'.": {
1882+
"Type '{0}' has no properties in common with type '{1}'.": {
18831883
"category": "Error",
18841884
"code": 2559
18851885
},

0 commit comments

Comments
 (0)