Detect weak type correctly for intersection types

Plus add an intersection test case.
This commit is contained in:
Nathan Shively-Sanders
2017-05-23 13:28:48 -07:00
parent c583c326da
commit 396071be9c
2 changed files with 55 additions and 17 deletions
+35 -17
View File
@@ -8959,15 +8959,32 @@ namespace ts {
// unless all of target.types are weak, in which case run weak type detection on the *combined* properties of target
// at a minimum, do the first part.
// but the thing should be flexible enough to easily do the second part as a separate call
const saveDynamicDisableWeakTypeErrors = dynamicDisableWeakTypeErrors;
dynamicDisableWeakTypeErrors = true;
for (const targetType of targetTypes) {
dynamicDisableWeakTypeErrors = true;
const related = isRelatedTo(source, targetType, reportErrors);
dynamicDisableWeakTypeErrors = false;
if (!related) {
dynamicDisableWeakTypeErrors = saveDynamicDisableWeakTypeErrors;
return Ternary.False;
}
result &= related;
}
dynamicDisableWeakTypeErrors = saveDynamicDisableWeakTypeErrors;
if (source !== globalObjectType && getPropertiesOfType(source).length > 0 && every(target.types, isWeak)) {
let found = false;
for (const property of getPropertiesOfType(source)) {
if (isKnownProperty(target, property.name, /*isComparingJsxAttributes*/ false)) {
found = true;
break;
}
}
if (!found) {
if (reportErrors) {
reportError(Diagnostics.Weak_type_0_has_no_properties_in_common_with_1, typeToString(target), typeToString(source));
}
return Ternary.False;
}
}
return result;
}
@@ -9340,7 +9357,7 @@ namespace ts {
}
}
}
if (!foundMatchingProperty && !dynamicDisableWeakTypeErrors && getPropertiesOfType(source).length > 0) {
if (!foundMatchingProperty && !dynamicDisableWeakTypeErrors && source !== globalObjectType && getPropertiesOfType(source).length > 0) {
if (reportErrors) {
reportError(Diagnostics.Weak_type_0_has_no_properties_in_common_with_1, typeToString(target), typeToString(source));
}
@@ -9349,6 +9366,17 @@ namespace ts {
return result;
}
// A type is 'weak' if it is an object type with at least one optional property
// and no required properties or index signatures
function isWeak(type: Type) {
let props = getPropertiesOfType(type);
return type.flags & TypeFlags.Object &&
props.length > 0 &&
every(props, p => !!(p.flags & SymbolFlags.Optional)) &&
!getIndexTypeOfType(type, IndexKind.String) &&
!getIndexTypeOfType(type, IndexKind.Number);
}
function propertiesIdenticalTo(source: Type, target: Type): Ternary {
if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) {
return Ternary.False;
@@ -10604,17 +10632,6 @@ namespace ts {
return links.resolvedSymbol;
}
// A type is 'weak' if it is an object type with at least one optional property
// and no required properties or index signatures
function isWeak(type: Type) {
let props = getPropertiesOfType(type);
return type.flags & TypeFlags.Object &&
props.length > 0 &&
every(props, p => !!(p.flags & SymbolFlags.Optional)) &&
!getIndexTypeOfType(type, IndexKind.String) &&
!getIndexTypeOfType(type, IndexKind.Number);
}
function isInTypeQuery(node: Node): boolean {
// TypeScript 1.0 spec (April 2014): 3.6.3
// A type query consists of the keyword typeof followed by an expression.
@@ -10622,7 +10639,6 @@ namespace ts {
return !!findAncestor(
node,
n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit");
}
// Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers
@@ -14124,8 +14140,10 @@ namespace ts {
function isKnownProperty(targetType: Type, name: string, isComparingJsxAttributes: boolean): boolean {
if (targetType.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(<ObjectType>targetType);
if (resolved.stringIndexInfo || resolved.numberIndexInfo && isNumericLiteralName(name) ||
getPropertyOfType(targetType, name) || isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) {
if (resolved.stringIndexInfo ||
resolved.numberIndexInfo && isNumericLiteralName(name) ||
getPropertyOfType(targetType, name) ||
isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) {
// For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
return true;
}
+20
View File
@@ -9,4 +9,24 @@ function getDefaultSettings() {
function doSomething(settings: Settings) { /* ... */ }
// forgot to call `getDefaultSettings`
// but it is not caught because we don't check for call signatures
doSomething(getDefaultSettings);
// this is an oddly popular way of defining settings
// this example is from services/textChanges.ts
type ConfigurableStart = { useStart?: boolean }
type ConfigurableEnd = { useEnd?: boolean }
type ConfigurableStartEnd = ConfigurableStart & ConfigurableEnd
interface InsertOptions {
prefix?: string
suffix?: string
}
type ChangeOptions = ConfigurableStartEnd & InsertOptions;
function del(options: ConfigurableStartEnd = {},
error: { error?: number } = {}) {
let changes: ChangeOptions[];
changes.push(options);
changes.push(error);
}