Skip to content

Commit 24e35cd

Browse files
authored
Merge pull request #16047 from Microsoft/sandersn/weakType
Weak type detection
2 parents 71e25cd + 3e4b83e commit 24e35cd

File tree

76 files changed

+721
-421
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+721
-421
lines changed

src/compiler/checker.ts

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2285,7 +2285,7 @@ namespace ts {
22852285
Debug.assert(typeNode !== undefined, "should always get typenode?");
22862286
const options = { removeComments: true };
22872287
const writer = createTextWriter("");
2288-
const printer = createPrinter(options, writer);
2288+
const printer = createPrinter(options);
22892289
const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration);
22902290
printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer);
22912291
const result = writer.getText();
@@ -8702,6 +8702,7 @@ namespace ts {
87028702
let expandingFlags: number;
87038703
let depth = 0;
87048704
let overflow = false;
8705+
let isIntersectionConstituent = false;
87058706

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

@@ -8784,7 +8785,6 @@ namespace ts {
87848785
* * Ternary.False if they are not related.
87858786
*/
87868787
function isRelatedTo(source: Type, target: Type, reportErrors?: boolean, headMessage?: DiagnosticMessage): Ternary {
8787-
let result: Ternary;
87888788
if (source.flags & TypeFlags.StringOrNumberLiteral && source.flags & TypeFlags.FreshLiteral) {
87898789
source = (<LiteralType>source).regularType;
87908790
}
@@ -8816,32 +8816,39 @@ namespace ts {
88168816
}
88178817
}
88188818

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

88218837
// Note that these checks are specifically ordered to produce correct results. In particular,
88228838
// we need to deconstruct unions before intersections (because unions are always at the top),
88238839
// and we need to handle "each" relations before "some" relations for the same kind of type.
88248840
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-
}
8841+
result = relation === comparableRelation ?
8842+
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive)) :
8843+
eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive));
88348844
}
88358845
else {
88368846
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-
}
8847+
result = typeRelatedToSomeType(source, <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive));
88408848
}
88418849
else if (target.flags & TypeFlags.Intersection) {
8842-
if (result = typeRelatedToEachType(source, target as IntersectionType, reportErrors)) {
8843-
return result;
8844-
}
8850+
isIntersectionConstituent = true;
8851+
result = typeRelatedToEachType(source, target as IntersectionType, reportErrors);
88458852
}
88468853
else if (source.flags & TypeFlags.Intersection) {
88478854
// Check to see if any constituents of the intersection are immediately related to the target.
@@ -8857,20 +8864,18 @@ namespace ts {
88578864
//
88588865
// - For a primitive type or type parameter (such as 'number = A & B') there is no point in
88598866
// breaking the intersection apart.
8860-
if (result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false)) {
8861-
return result;
8862-
}
8867+
result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false);
88638868
}
8864-
8865-
if (source.flags & TypeFlags.StructuredOrTypeVariable || target.flags & TypeFlags.StructuredOrTypeVariable) {
8869+
if (!result && (source.flags & TypeFlags.StructuredOrTypeVariable || target.flags & TypeFlags.StructuredOrTypeVariable)) {
88668870
if (result = recursiveTypeRelatedTo(source, target, reportErrors)) {
88678871
errorInfo = saveErrorInfo;
8868-
return result;
88698872
}
88708873
}
88718874
}
88728875

8873-
if (reportErrors) {
8876+
isIntersectionConstituent = saveIsIntersectionConstituent;
8877+
8878+
if (!result && reportErrors) {
88748879
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) {
88758880
tryElaborateErrorsForPrimitivesAndObjects(source, target);
88768881
}
@@ -8879,7 +8884,7 @@ namespace ts {
88798884
}
88808885
reportRelationError(headMessage, source, target);
88818886
}
8882-
return Ternary.False;
8887+
return result;
88838888
}
88848889

88858890
function isIdenticalTo(source: Type, target: Type): Ternary {
@@ -8978,7 +8983,7 @@ namespace ts {
89788983
}
89798984
}
89808985

8981-
function typeRelatedToEachType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary {
8986+
function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean): Ternary {
89828987
let result = Ternary.True;
89838988
const targetTypes = target.types;
89848989
for (const targetType of targetTypes) {
@@ -9280,7 +9285,6 @@ namespace ts {
92809285
const requireOptionalProperties = relation === subtypeRelation && !(getObjectFlags(source) & ObjectFlags.ObjectLiteral);
92819286
for (const targetProp of properties) {
92829287
const sourceProp = getPropertyOfType(source, targetProp.name);
9283-
92849288
if (sourceProp !== targetProp) {
92859289
if (!sourceProp) {
92869290
if (!(targetProp.flags & SymbolFlags.Optional) || requireOptionalProperties) {
@@ -9359,6 +9363,34 @@ namespace ts {
93599363
return result;
93609364
}
93619365

9366+
/**
9367+
* A type is 'weak' if it is an object type with at least one optional property
9368+
* and no required properties, call/construct signatures or index signatures
9369+
*/
9370+
function isWeakType(type: Type): boolean {
9371+
if (type.flags & TypeFlags.Object) {
9372+
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
9373+
return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 &&
9374+
!resolved.stringIndexInfo && !resolved.numberIndexInfo &&
9375+
resolved.properties.length > 0 &&
9376+
every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional));
9377+
}
9378+
if (type.flags & TypeFlags.Intersection) {
9379+
return every((<IntersectionType>type).types, isWeakType);
9380+
}
9381+
return false;
9382+
}
9383+
9384+
function hasCommonProperties(source: Type, target: Type) {
9385+
const isComparingJsxAttributes = !!(source.flags & TypeFlags.JsxAttributes);
9386+
for (const prop of getPropertiesOfType(source)) {
9387+
if (isKnownProperty(target, prop.name, isComparingJsxAttributes)) {
9388+
return true;
9389+
}
9390+
}
9391+
return false;
9392+
}
9393+
93629394
function propertiesIdenticalTo(source: Type, target: Type): Ternary {
93639395
if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) {
93649396
return Ternary.False;
@@ -14147,8 +14179,10 @@ namespace ts {
1414714179
function isKnownProperty(targetType: Type, name: string, isComparingJsxAttributes: boolean): boolean {
1414814180
if (targetType.flags & TypeFlags.Object) {
1414914181
const resolved = resolveStructuredTypeMembers(<ObjectType>targetType);
14150-
if (resolved.stringIndexInfo || resolved.numberIndexInfo && isNumericLiteralName(name) ||
14151-
getPropertyOfType(targetType, name) || isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) {
14182+
if (resolved.stringIndexInfo ||
14183+
resolved.numberIndexInfo && isNumericLiteralName(name) ||
14184+
getPropertyOfType(targetType, name) ||
14185+
isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) {
1415214186
// For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
1415314187
return true;
1415414188
}
@@ -22704,12 +22738,12 @@ namespace ts {
2270422738
return symbols;
2270522739
}
2270622740
else if (symbol.flags & SymbolFlags.Transient) {
22707-
if ((symbol as SymbolLinks).leftSpread) {
22708-
const links = symbol as SymbolLinks;
22709-
return [...getRootSymbols(links.leftSpread), ...getRootSymbols(links.rightSpread)];
22741+
const transient = symbol as TransientSymbol;
22742+
if (transient.leftSpread) {
22743+
return [...getRootSymbols(transient.leftSpread), ...getRootSymbols(transient.rightSpread)];
2271022744
}
22711-
if ((symbol as SymbolLinks).syntheticOrigin) {
22712-
return getRootSymbols((symbol as SymbolLinks).syntheticOrigin);
22745+
if (transient.syntheticOrigin) {
22746+
return getRootSymbols(transient.syntheticOrigin);
2271322747
}
2271422748

2271522749
let target: Symbol;

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,6 +1879,10 @@
18791879
"category": "Error",
18801880
"code": 2558
18811881
},
1882+
"Type '{0}' has no properties in common with type '{1}'.": {
1883+
"category": "Error",
1884+
"code": 2559
1885+
},
18821886
"JSX element attributes type '{0}' may not be a union type.": {
18831887
"category": "Error",
18841888
"code": 2600

tests/baselines/reference/APISample_watcher.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ declare var fs: {
1111
existsSync(path: string): boolean;
1212
readdirSync(path: string): string[];
1313
readFileSync(filename: string, encoding?: string): string;
14-
writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void;
14+
writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; } | string): void;
1515
watchFile(filename: string, options: { persistent?: boolean; interval?: number; }, listener: (curr: { mtime: Date }, prev: { mtime: Date }) => void): void;
1616
};
1717
declare var path: any;

tests/baselines/reference/assignmentCompatWithObjectMembersOptionality2.errors.txt

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(33,5): error TS2559: Type 'D' has no properties in common with type 'C'.
2+
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(34,5): error TS2559: Type 'E' has no properties in common with type 'C'.
3+
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(35,5): error TS2559: Type 'F' has no properties in common with type 'C'.
4+
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(36,5): error TS2559: Type 'D' has no properties in common with type '{ opt?: Base; }'.
5+
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(37,5): error TS2559: Type 'E' has no properties in common with type '{ opt?: Base; }'.
6+
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(38,5): error TS2559: Type 'F' has no properties in common with type '{ opt?: Base; }'.
7+
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(39,5): error TS2559: Type 'D' has no properties in common with type '{ opt?: Base; }'.
8+
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(40,5): error TS2559: Type 'E' has no properties in common with type '{ opt?: Base; }'.
9+
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(41,5): error TS2559: Type 'F' has no properties in common with type '{ opt?: Base; }'.
110
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(74,5): error TS2322: Type 'D' is not assignable to type 'C'.
211
Property 'opt' is missing in type 'D'.
312
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(75,5): error TS2322: Type 'E' is not assignable to type 'C'.
@@ -18,7 +27,7 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
1827
Property 'opt' is missing in type 'F'.
1928

2029

21-
==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts (9 errors) ====
30+
==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts (18 errors) ====
2231
// M is optional and S contains no property with the same name as M
2332
// N is optional and T contains no property with the same name as N
2433

@@ -50,20 +59,38 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
5059
var e: E;
5160
var f: F;
5261

53-
// all ok
62+
// disallowed by weak type checking
5463
c = d;
64+
~
65+
!!! error TS2559: Type 'D' has no properties in common with type 'C'.
5566
c = e;
67+
~
68+
!!! error TS2559: Type 'E' has no properties in common with type 'C'.
5669
c = f;
57-
c = a;
58-
70+
~
71+
!!! error TS2559: Type 'F' has no properties in common with type 'C'.
5972
a = d;
73+
~
74+
!!! error TS2559: Type 'D' has no properties in common with type '{ opt?: Base; }'.
6075
a = e;
76+
~
77+
!!! error TS2559: Type 'E' has no properties in common with type '{ opt?: Base; }'.
6178
a = f;
62-
a = c;
63-
79+
~
80+
!!! error TS2559: Type 'F' has no properties in common with type '{ opt?: Base; }'.
6481
b = d;
82+
~
83+
!!! error TS2559: Type 'D' has no properties in common with type '{ opt?: Base; }'.
6584
b = e;
85+
~
86+
!!! error TS2559: Type 'E' has no properties in common with type '{ opt?: Base; }'.
6687
b = f;
88+
~
89+
!!! error TS2559: Type 'F' has no properties in common with type '{ opt?: Base; }'.
90+
91+
// ok
92+
c = a;
93+
a = c;
6794
b = a;
6895
b = c;
6996
}
@@ -134,4 +161,5 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
134161
!!! error TS2322: Property 'opt' is missing in type 'F'.
135162
b = a; // ok
136163
b = c; // ok
137-
}
164+
}
165+

tests/baselines/reference/assignmentCompatWithObjectMembersOptionality2.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,20 @@ module TargetHasOptional {
3030
var e: E;
3131
var f: F;
3232

33-
// all ok
33+
// disallowed by weak type checking
3434
c = d;
3535
c = e;
3636
c = f;
37-
c = a;
38-
3937
a = d;
4038
a = e;
4139
a = f;
42-
a = c;
43-
4440
b = d;
4541
b = e;
4642
b = f;
43+
44+
// ok
45+
c = a;
46+
a = c;
4747
b = a;
4848
b = c;
4949
}
@@ -87,7 +87,8 @@ module SourceHasOptional {
8787
b = f; // error
8888
b = a; // ok
8989
b = c; // ok
90-
}
90+
}
91+
9192

9293
//// [assignmentCompatWithObjectMembersOptionality2.js]
9394
// M is optional and S contains no property with the same name as M
@@ -129,18 +130,19 @@ var TargetHasOptional;
129130
var d;
130131
var e;
131132
var f;
132-
// all ok
133+
// disallowed by weak type checking
133134
c = d;
134135
c = e;
135136
c = f;
136-
c = a;
137137
a = d;
138138
a = e;
139139
a = f;
140-
a = c;
141140
b = d;
142141
b = e;
143142
b = f;
143+
// ok
144+
c = a;
145+
a = c;
144146
b = a;
145147
b = c;
146148
})(TargetHasOptional || (TargetHasOptional = {}));

tests/baselines/reference/checkJsxChildrenProperty1.symbols

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ interface Prop {
1313

1414
children: string | JSX.Element
1515
>children : Symbol(Prop.children, Decl(file.tsx, 4, 14))
16-
>JSX : Symbol(JSX, Decl(react.d.ts, 2352, 1))
17-
>Element : Symbol(JSX.Element, Decl(react.d.ts, 2355, 27))
16+
>JSX : Symbol(JSX, Decl(react.d.ts, 2353, 1))
17+
>Element : Symbol(JSX.Element, Decl(react.d.ts, 2356, 27))
1818
}
1919

2020
function Comp(p: Prop) {
@@ -23,11 +23,11 @@ function Comp(p: Prop) {
2323
>Prop : Symbol(Prop, Decl(file.tsx, 0, 32))
2424

2525
return <div>{p.b}</div>;
26-
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
26+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2400, 45))
2727
>p.b : Symbol(Prop.b, Decl(file.tsx, 3, 14))
2828
>p : Symbol(p, Decl(file.tsx, 8, 14))
2929
>b : Symbol(Prop.b, Decl(file.tsx, 3, 14))
30-
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
30+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2400, 45))
3131
}
3232

3333
// OK
@@ -59,8 +59,8 @@ let k2 =
5959
>b : Symbol(b, Decl(file.tsx, 19, 16))
6060

6161
<div>hi hi hi!</div>
62-
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
63-
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45))
62+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2400, 45))
63+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2400, 45))
6464

6565
</Comp>;
6666
>Comp : Symbol(Comp, Decl(file.tsx, 6, 1))

0 commit comments

Comments
 (0)