Skip to content

Commit 396071b

Browse files
committed
Detect weak type correctly for intersection types
Plus add an intersection test case.
1 parent c583c32 commit 396071b

File tree

2 files changed

+55
-17
lines changed

2 files changed

+55
-17
lines changed

src/compiler/checker.ts

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8959,15 +8959,32 @@ namespace ts {
89598959
// unless all of target.types are weak, in which case run weak type detection on the *combined* properties of target
89608960
// at a minimum, do the first part.
89618961
// but the thing should be flexible enough to easily do the second part as a separate call
8962+
const saveDynamicDisableWeakTypeErrors = dynamicDisableWeakTypeErrors;
8963+
dynamicDisableWeakTypeErrors = true;
89628964
for (const targetType of targetTypes) {
8963-
dynamicDisableWeakTypeErrors = true;
89648965
const related = isRelatedTo(source, targetType, reportErrors);
8965-
dynamicDisableWeakTypeErrors = false;
89668966
if (!related) {
8967+
dynamicDisableWeakTypeErrors = saveDynamicDisableWeakTypeErrors;
89678968
return Ternary.False;
89688969
}
89698970
result &= related;
89708971
}
8972+
dynamicDisableWeakTypeErrors = saveDynamicDisableWeakTypeErrors;
8973+
if (source !== globalObjectType && getPropertiesOfType(source).length > 0 && every(target.types, isWeak)) {
8974+
let found = false;
8975+
for (const property of getPropertiesOfType(source)) {
8976+
if (isKnownProperty(target, property.name, /*isComparingJsxAttributes*/ false)) {
8977+
found = true;
8978+
break;
8979+
}
8980+
}
8981+
if (!found) {
8982+
if (reportErrors) {
8983+
reportError(Diagnostics.Weak_type_0_has_no_properties_in_common_with_1, typeToString(target), typeToString(source));
8984+
}
8985+
return Ternary.False;
8986+
}
8987+
}
89718988
return result;
89728989
}
89738990

@@ -9340,7 +9357,7 @@ namespace ts {
93409357
}
93419358
}
93429359
}
9343-
if (!foundMatchingProperty && !dynamicDisableWeakTypeErrors && getPropertiesOfType(source).length > 0) {
9360+
if (!foundMatchingProperty && !dynamicDisableWeakTypeErrors && source !== globalObjectType && getPropertiesOfType(source).length > 0) {
93449361
if (reportErrors) {
93459362
reportError(Diagnostics.Weak_type_0_has_no_properties_in_common_with_1, typeToString(target), typeToString(source));
93469363
}
@@ -9349,6 +9366,17 @@ namespace ts {
93499366
return result;
93509367
}
93519368

9369+
// A type is 'weak' if it is an object type with at least one optional property
9370+
// and no required properties or index signatures
9371+
function isWeak(type: Type) {
9372+
let props = getPropertiesOfType(type);
9373+
return type.flags & TypeFlags.Object &&
9374+
props.length > 0 &&
9375+
every(props, p => !!(p.flags & SymbolFlags.Optional)) &&
9376+
!getIndexTypeOfType(type, IndexKind.String) &&
9377+
!getIndexTypeOfType(type, IndexKind.Number);
9378+
}
9379+
93529380
function propertiesIdenticalTo(source: Type, target: Type): Ternary {
93539381
if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) {
93549382
return Ternary.False;
@@ -10604,25 +10632,13 @@ namespace ts {
1060410632
return links.resolvedSymbol;
1060510633
}
1060610634

10607-
// A type is 'weak' if it is an object type with at least one optional property
10608-
// and no required properties or index signatures
10609-
function isWeak(type: Type) {
10610-
let props = getPropertiesOfType(type);
10611-
return type.flags & TypeFlags.Object &&
10612-
props.length > 0 &&
10613-
every(props, p => !!(p.flags & SymbolFlags.Optional)) &&
10614-
!getIndexTypeOfType(type, IndexKind.String) &&
10615-
!getIndexTypeOfType(type, IndexKind.Number);
10616-
}
10617-
1061810635
function isInTypeQuery(node: Node): boolean {
1061910636
// TypeScript 1.0 spec (April 2014): 3.6.3
1062010637
// A type query consists of the keyword typeof followed by an expression.
1062110638
// The expression is restricted to a single identifier or a sequence of identifiers separated by periods
1062210639
return !!findAncestor(
1062310640
node,
1062410641
n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit");
10625-
1062610642
}
1062710643

1062810644
// Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers
@@ -14124,8 +14140,10 @@ namespace ts {
1412414140
function isKnownProperty(targetType: Type, name: string, isComparingJsxAttributes: boolean): boolean {
1412514141
if (targetType.flags & TypeFlags.Object) {
1412614142
const resolved = resolveStructuredTypeMembers(<ObjectType>targetType);
14127-
if (resolved.stringIndexInfo || resolved.numberIndexInfo && isNumericLiteralName(name) ||
14128-
getPropertyOfType(targetType, name) || isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) {
14143+
if (resolved.stringIndexInfo ||
14144+
resolved.numberIndexInfo && isNumericLiteralName(name) ||
14145+
getPropertyOfType(targetType, name) ||
14146+
isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) {
1412914147
// For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
1413014148
return true;
1413114149
}

tests/cases/compiler/weakType.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,24 @@ function getDefaultSettings() {
99

1010
function doSomething(settings: Settings) { /* ... */ }
1111

12+
// forgot to call `getDefaultSettings`
13+
// but it is not caught because we don't check for call signatures
1214
doSomething(getDefaultSettings);
15+
16+
// this is an oddly popular way of defining settings
17+
// this example is from services/textChanges.ts
18+
type ConfigurableStart = { useStart?: boolean }
19+
type ConfigurableEnd = { useEnd?: boolean }
20+
type ConfigurableStartEnd = ConfigurableStart & ConfigurableEnd
21+
interface InsertOptions {
22+
prefix?: string
23+
suffix?: string
24+
}
25+
type ChangeOptions = ConfigurableStartEnd & InsertOptions;
26+
27+
function del(options: ConfigurableStartEnd = {},
28+
error: { error?: number } = {}) {
29+
let changes: ChangeOptions[];
30+
changes.push(options);
31+
changes.push(error);
32+
}

0 commit comments

Comments
 (0)