From b457de40679b7422b11f51e7c45d63dc8d3688e7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 12 Dec 2019 15:58:50 -0800 Subject: [PATCH 01/11] Increase selectivity of subtype relationship for signatures --- src/compiler/checker.ts | 59 ++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a16ac45852853..9b6e1e37c644b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -183,10 +183,12 @@ namespace ts { NoTupleBoundsCheck = 1 << 3, } - const enum CallbackCheck { - None, - Bivariant, - Strict, + const enum SignatureCheckMode { + BivariantCallback = 1 << 0, + StrictCallback = 1 << 1, + IgnoreReturnTypes = 1 << 2, + StrictArity = 1 << 3, + Callback = BivariantCallback | StrictCallback, } const enum MappedTypeModifiers { @@ -13984,7 +13986,7 @@ namespace ts { function isSignatureAssignableTo(source: Signature, target: Signature, ignoreReturnTypes: boolean): boolean { - return compareSignaturesRelated(source, target, CallbackCheck.None, ignoreReturnTypes, /*reportErrors*/ false, + return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : 0, /*reportErrors*/ false, /*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False; } @@ -14004,8 +14006,7 @@ namespace ts { */ function compareSignaturesRelated(source: Signature, target: Signature, - callbackCheck: CallbackCheck, - ignoreReturnTypes: boolean, + checkMode: SignatureCheckMode, reportErrors: boolean, errorReporter: ErrorReporter | undefined, incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined, @@ -14021,7 +14022,8 @@ namespace ts { } const targetCount = getParameterCount(target); - if (!hasEffectiveRestParameter(target) && getMinArgumentCount(source) > targetCount) { + if (!hasEffectiveRestParameter(target) && + (checkMode & SignatureCheckMode.StrictArity ? getParameterCount(source) : getMinArgumentCount(source)) > targetCount) { return Ternary.False; } @@ -14042,7 +14044,7 @@ namespace ts { } const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; - const strictVariance = !callbackCheck && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration && + const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration && kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor; let result = Ternary.True; @@ -14077,14 +14079,14 @@ namespace ts { // similar to return values, callback parameters are output positions. This means that a Promise, // where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) // with respect to T. - const sourceSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); - const targetSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(targetType)); + const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); + const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType)); const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && (getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable); const related = callbacks ? // TODO: GH#18217 It will work if they're both `undefined`, but not if only one is - compareSignaturesRelated(targetSig!, sourceSig!, strictVariance ? CallbackCheck.Strict : CallbackCheck.Bivariant, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : - !callbackCheck && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); + compareSignaturesRelated(targetSig!, sourceSig!, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : + !(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); if (!related) { if (reportErrors) { errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, @@ -14096,7 +14098,7 @@ namespace ts { result &= related; } - if (!ignoreReturnTypes) { + if (!(checkMode & SignatureCheckMode.IgnoreReturnTypes)) { // If a signature resolution is already in-flight, skip issuing a circularity error // here and just use the `any` type directly const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType @@ -14127,7 +14129,7 @@ namespace ts { // When relating callback signatures, we still need to relate return types bi-variantly as otherwise // the containing type wouldn't be co-variant. For example, interface Foo { add(cb: () => T): void } // wouldn't be co-variant for T without this rule. - result &= callbackCheck === CallbackCheck.Bivariant && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) || + result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) || compareTypes(sourceReturnType, targetReturnType, reportErrors); if (!result && reportErrors && incompatibleErrorReporter) { incompatibleErrorReporter(sourceReturnType, targetReturnType); @@ -14299,7 +14301,7 @@ namespace ts { return true; } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { - const related = relation.get(getRelationKey(source, target, /*isIntersectionConstituent*/ false, relation)); + const related = relation.get(getRelationKey(source, target, /*isIntersectionConstituent*/ false, /*strictArityChecks*/ false, relation)); if (related !== undefined) { return !!(related & RelationComparisonResult.Succeeded); } @@ -14351,6 +14353,7 @@ namespace ts { let depth = 0; let expandingFlags = ExpandingFlags.None; let overflow = false; + let strictArityChecks = relation === subtypeRelation; let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid let lastSkippedInfo: [Type, Type] | undefined; let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = []; @@ -15139,7 +15142,7 @@ namespace ts { if (overflow) { return Ternary.False; } - const id = getRelationKey(source, target, isIntersectionConstituent, relation); + const id = getRelationKey(source, target, isIntersectionConstituent, strictArityChecks, relation); const entry = relation.get(id); if (entry !== undefined) { if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) { @@ -15486,6 +15489,17 @@ namespace ts { // to X. Failing both of those we want to check if the aggregation of A and B's members structurally // relates to X. Thus, we include intersection types on the source side here. if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { + // When performing strict arity checks for signatures (under the subtype relationship), if source has + // every property of target, and target is missing some properties from source, then we disable strict + // arity checks for the members. This decreases the chances of "ties" in union subtype reduction. For + // example, it enables us to consider { f(): void } a supertype of { f(x?: string): void, g(): void }, + // which we need for backwards compatibility reasons (specifically, for Object and Number). + const saveStrictArityChecks = strictArityChecks; + if (strictArityChecks && + !getUnmatchedProperty(source, target, /*requireOptionalProperties*/ true, /*matchDiscriminantProperties*/ false) && + getUnmatchedProperty(target, source, /*requireOptionalProperties*/ true, /*matchDiscriminantProperties*/ false)) { + strictArityChecks = false; + } // Report structural errors only if we haven't reported any errors yet const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, isIntersectionConstituent); @@ -15501,6 +15515,7 @@ namespace ts { } } } + strictArityChecks = saveStrictArityChecks; if (varianceCheckFailed && result) { errorInfo = originalErrorInfo || errorInfo || saveErrorInfo.errorInfo; // Use variance error (there is no structural one) and return false } @@ -16045,7 +16060,7 @@ namespace ts { */ function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, - CallbackCheck.None, /*ignoreReturnTypes*/ false, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers); + strictArityChecks ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers); } function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { @@ -16353,18 +16368,18 @@ namespace ts { * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters. * For other cases, the types ids are used. */ - function getRelationKey(source: Type, target: Type, isIntersectionConstituent: boolean, relation: Map) { + function getRelationKey(source: Type, target: Type, isIntersectionConstituent: boolean, strictArityChecks: boolean, relation: Map) { if (relation === identityRelation && source.id > target.id) { const temp = source; source = target; target = temp; } - const intersection = isIntersectionConstituent ? "&" : ""; + const delimiter = isIntersectionConstituent ? strictArityChecks ? "|" : ";" : strictArityChecks ? "/" : ","; if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) { const typeParameters: Type[] = []; - return getTypeReferenceId(source, typeParameters) + "," + getTypeReferenceId(target, typeParameters) + intersection; + return getTypeReferenceId(source, typeParameters) + delimiter + getTypeReferenceId(target, typeParameters); } - return source.id + "," + target.id + intersection; + return source.id + delimiter + target.id; } // Invoke the callback for each underlying property symbol of the given symbol and return the first From 5524a61ce9d59b5874fe8a32f851763723eb9c74 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 12 Dec 2019 16:03:21 -0800 Subject: [PATCH 02/11] Add regression test --- .../compiler/unionReductionMutualSubtypes.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/cases/compiler/unionReductionMutualSubtypes.ts diff --git a/tests/cases/compiler/unionReductionMutualSubtypes.ts b/tests/cases/compiler/unionReductionMutualSubtypes.ts new file mode 100644 index 0000000000000..aa1c1320a0df7 --- /dev/null +++ b/tests/cases/compiler/unionReductionMutualSubtypes.ts @@ -0,0 +1,15 @@ +// @strict: true + +// Repro from #35414 + +interface ReturnVal { + something(): void; +} + +const k: ReturnVal = { something() { } } + +declare const val: ReturnVal; +function run(options: { something?(b?: string): void }) { + const something = options.something ?? val.something; + something(''); +} From 6d67054339c1f7c6d73eb78d7079f3f6a85c6831 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 12 Dec 2019 16:03:32 -0800 Subject: [PATCH 03/11] Accept new baselines --- .../reference/unionReductionMutualSubtypes.js | 25 ++++++++++++ .../unionReductionMutualSubtypes.symbols | 38 +++++++++++++++++++ .../unionReductionMutualSubtypes.types | 38 +++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 tests/baselines/reference/unionReductionMutualSubtypes.js create mode 100644 tests/baselines/reference/unionReductionMutualSubtypes.symbols create mode 100644 tests/baselines/reference/unionReductionMutualSubtypes.types diff --git a/tests/baselines/reference/unionReductionMutualSubtypes.js b/tests/baselines/reference/unionReductionMutualSubtypes.js new file mode 100644 index 0000000000000..91623b5d00e8a --- /dev/null +++ b/tests/baselines/reference/unionReductionMutualSubtypes.js @@ -0,0 +1,25 @@ +//// [unionReductionMutualSubtypes.ts] +// Repro from #35414 + +interface ReturnVal { + something(): void; +} + +const k: ReturnVal = { something() { } } + +declare const val: ReturnVal; +function run(options: { something?(b?: string): void }) { + const something = options.something ?? val.something; + something(''); +} + + +//// [unionReductionMutualSubtypes.js] +"use strict"; +// Repro from #35414 +var k = { something: function () { } }; +function run(options) { + var _a; + var something = (_a = options.something) !== null && _a !== void 0 ? _a : val.something; + something(''); +} diff --git a/tests/baselines/reference/unionReductionMutualSubtypes.symbols b/tests/baselines/reference/unionReductionMutualSubtypes.symbols new file mode 100644 index 0000000000000..5bcc69ea6d407 --- /dev/null +++ b/tests/baselines/reference/unionReductionMutualSubtypes.symbols @@ -0,0 +1,38 @@ +=== tests/cases/compiler/unionReductionMutualSubtypes.ts === +// Repro from #35414 + +interface ReturnVal { +>ReturnVal : Symbol(ReturnVal, Decl(unionReductionMutualSubtypes.ts, 0, 0)) + + something(): void; +>something : Symbol(ReturnVal.something, Decl(unionReductionMutualSubtypes.ts, 2, 21)) +} + +const k: ReturnVal = { something() { } } +>k : Symbol(k, Decl(unionReductionMutualSubtypes.ts, 6, 5)) +>ReturnVal : Symbol(ReturnVal, Decl(unionReductionMutualSubtypes.ts, 0, 0)) +>something : Symbol(something, Decl(unionReductionMutualSubtypes.ts, 6, 22)) + +declare const val: ReturnVal; +>val : Symbol(val, Decl(unionReductionMutualSubtypes.ts, 8, 13)) +>ReturnVal : Symbol(ReturnVal, Decl(unionReductionMutualSubtypes.ts, 0, 0)) + +function run(options: { something?(b?: string): void }) { +>run : Symbol(run, Decl(unionReductionMutualSubtypes.ts, 8, 29)) +>options : Symbol(options, Decl(unionReductionMutualSubtypes.ts, 9, 13)) +>something : Symbol(something, Decl(unionReductionMutualSubtypes.ts, 9, 23)) +>b : Symbol(b, Decl(unionReductionMutualSubtypes.ts, 9, 35)) + + const something = options.something ?? val.something; +>something : Symbol(something, Decl(unionReductionMutualSubtypes.ts, 10, 9)) +>options.something : Symbol(something, Decl(unionReductionMutualSubtypes.ts, 9, 23)) +>options : Symbol(options, Decl(unionReductionMutualSubtypes.ts, 9, 13)) +>something : Symbol(something, Decl(unionReductionMutualSubtypes.ts, 9, 23)) +>val.something : Symbol(ReturnVal.something, Decl(unionReductionMutualSubtypes.ts, 2, 21)) +>val : Symbol(val, Decl(unionReductionMutualSubtypes.ts, 8, 13)) +>something : Symbol(ReturnVal.something, Decl(unionReductionMutualSubtypes.ts, 2, 21)) + + something(''); +>something : Symbol(something, Decl(unionReductionMutualSubtypes.ts, 10, 9)) +} + diff --git a/tests/baselines/reference/unionReductionMutualSubtypes.types b/tests/baselines/reference/unionReductionMutualSubtypes.types new file mode 100644 index 0000000000000..748826d08f6c9 --- /dev/null +++ b/tests/baselines/reference/unionReductionMutualSubtypes.types @@ -0,0 +1,38 @@ +=== tests/cases/compiler/unionReductionMutualSubtypes.ts === +// Repro from #35414 + +interface ReturnVal { + something(): void; +>something : () => void +} + +const k: ReturnVal = { something() { } } +>k : ReturnVal +>{ something() { } } : { something(): void; } +>something : () => void + +declare const val: ReturnVal; +>val : ReturnVal + +function run(options: { something?(b?: string): void }) { +>run : (options: { something?(b?: string | undefined): void; }) => void +>options : { something?(b?: string | undefined): void; } +>something : ((b?: string | undefined) => void) | undefined +>b : string | undefined + + const something = options.something ?? val.something; +>something : (b?: string | undefined) => void +>options.something ?? val.something : (b?: string | undefined) => void +>options.something : ((b?: string | undefined) => void) | undefined +>options : { something?(b?: string | undefined): void; } +>something : ((b?: string | undefined) => void) | undefined +>val.something : () => void +>val : ReturnVal +>something : () => void + + something(''); +>something('') : void +>something : (b?: string | undefined) => void +>'' : "" +} + From 47f26e604a8d5d6c2259e3c1d3302f31528ccce9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 14 Dec 2019 08:36:39 -0800 Subject: [PATCH 04/11] Use strictSubtypeRelation for union subtype reduction --- src/compiler/checker.ts | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9b6e1e37c644b..40d6deb5ee495 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -871,6 +871,7 @@ namespace ts { let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; const subtypeRelation = createMap(); + const strictSubtypeRelation = createMap(); const assignableRelation = createMap(); const comparableRelation = createMap(); const identityRelation = createMap(); @@ -11398,7 +11399,7 @@ namespace ts { } } count++; - if (isTypeSubtypeOf(source, target) && ( + if (isTypeRelatedTo(source, target, strictSubtypeRelation) && ( !(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) || !(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) || isTypeDerivedFrom(source, target))) { @@ -14301,7 +14302,7 @@ namespace ts { return true; } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { - const related = relation.get(getRelationKey(source, target, /*isIntersectionConstituent*/ false, /*strictArityChecks*/ false, relation)); + const related = relation.get(getRelationKey(source, target, /*isIntersectionConstituent*/ false, relation)); if (related !== undefined) { return !!(related & RelationComparisonResult.Succeeded); } @@ -14353,7 +14354,6 @@ namespace ts { let depth = 0; let expandingFlags = ExpandingFlags.None; let overflow = false; - let strictArityChecks = relation === subtypeRelation; let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid let lastSkippedInfo: [Type, Type] | undefined; let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = []; @@ -15142,7 +15142,7 @@ namespace ts { if (overflow) { return Ternary.False; } - const id = getRelationKey(source, target, isIntersectionConstituent, strictArityChecks, relation); + const id = getRelationKey(source, target, isIntersectionConstituent, relation); const entry = relation.get(id); if (entry !== undefined) { if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) { @@ -15438,7 +15438,7 @@ namespace ts { } else { // An empty object type is related to any mapped type that includes a '?' modifier. - if (relation !== subtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) { + if (relation !== subtypeRelation && relation !== strictSubtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) { return Ternary.True; } if (isGenericMappedType(target)) { @@ -15480,7 +15480,7 @@ namespace ts { } // Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})` // and not `{} <- fresh({}) <- {[idx: string]: any}` - else if (relation === subtypeRelation && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) { + else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) { return Ternary.False; } // Even if relationship doesn't hold for unions, intersections, or generic type references, @@ -15489,18 +15489,6 @@ namespace ts { // to X. Failing both of those we want to check if the aggregation of A and B's members structurally // relates to X. Thus, we include intersection types on the source side here. if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { - // When performing strict arity checks for signatures (under the subtype relationship), if source has - // every property of target, and target is missing some properties from source, then we disable strict - // arity checks for the members. This decreases the chances of "ties" in union subtype reduction. For - // example, it enables us to consider { f(): void } a supertype of { f(x?: string): void, g(): void }, - // which we need for backwards compatibility reasons (specifically, for Object and Number). - const saveStrictArityChecks = strictArityChecks; - if (strictArityChecks && - !getUnmatchedProperty(source, target, /*requireOptionalProperties*/ true, /*matchDiscriminantProperties*/ false) && - getUnmatchedProperty(target, source, /*requireOptionalProperties*/ true, /*matchDiscriminantProperties*/ false)) { - strictArityChecks = false; - } - // Report structural errors only if we haven't reported any errors yet const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, isIntersectionConstituent); if (result) { @@ -15515,7 +15503,6 @@ namespace ts { } } } - strictArityChecks = saveStrictArityChecks; if (varianceCheckFailed && result) { errorInfo = originalErrorInfo || errorInfo || saveErrorInfo.errorInfo; // Use variance error (there is no structural one) and return false } @@ -15837,7 +15824,7 @@ namespace ts { if (relation === identityRelation) { return propertiesIdenticalTo(source, target, excludedProperties); } - const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source); + const requireOptionalProperties = (relation === subtypeRelation || relation === strictSubtypeRelation) && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source); const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false); if (unmatchedProperty) { if (reportErrors) { @@ -16060,7 +16047,7 @@ namespace ts { */ function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, - strictArityChecks ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers); + relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers); } function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { @@ -16368,13 +16355,13 @@ namespace ts { * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters. * For other cases, the types ids are used. */ - function getRelationKey(source: Type, target: Type, isIntersectionConstituent: boolean, strictArityChecks: boolean, relation: Map) { + function getRelationKey(source: Type, target: Type, isIntersectionConstituent: boolean, relation: Map) { if (relation === identityRelation && source.id > target.id) { const temp = source; source = target; target = temp; } - const delimiter = isIntersectionConstituent ? strictArityChecks ? "|" : ";" : strictArityChecks ? "/" : ","; + const delimiter = isIntersectionConstituent ? ";" : ","; if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) { const typeParameters: Type[] = []; return getTypeReferenceId(source, typeParameters) + delimiter + getTypeReferenceId(target, typeParameters); From 42c0451e792c5273633eca1b6b7bc3e89233d5c2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 14 Dec 2019 14:55:04 -0800 Subject: [PATCH 05/11] (x: number | undefined) -> void is subtype of (x?: number | undefined) => void --- src/compiler/checker.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 40d6deb5ee495..20cdec30cb0e9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14023,8 +14023,7 @@ namespace ts { } const targetCount = getParameterCount(target); - if (!hasEffectiveRestParameter(target) && - (checkMode & SignatureCheckMode.StrictArity ? getParameterCount(source) : getMinArgumentCount(source)) > targetCount) { + if (!hasEffectiveRestParameter(target) && (checkMode & SignatureCheckMode.StrictArity ? getParameterCount(source) : getMinArgumentCount(source)) > targetCount) { return Ternary.False; } @@ -14084,10 +14083,13 @@ namespace ts { const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType)); const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && (getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable); - const related = callbacks ? - // TODO: GH#18217 It will work if they're both `undefined`, but not if only one is + let related = callbacks ? compareSignaturesRelated(targetSig!, sourceSig!, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : !(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); + // With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void + if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && isTypeIdenticalTo(sourceType, targetType)) { + related = Ternary.False; + } if (!related) { if (reportErrors) { errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, From e60d7ffe7dc5c1b5d9474e1fd18840b000c0e99e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 14 Dec 2019 14:55:27 -0800 Subject: [PATCH 06/11] Accept new baselines --- .../bestCommonTypeOfConditionalExpressions.types | 10 +++++----- .../functionWithMultipleReturnStatements2.types | 6 +++--- ...subtypesOfTypeParameterWithConstraints2.types | 8 ++++---- .../reference/typeGuardRedundancy.types | 16 ++++++++-------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/baselines/reference/bestCommonTypeOfConditionalExpressions.types b/tests/baselines/reference/bestCommonTypeOfConditionalExpressions.types index 60483f2721359..e94408dfb1e84 100644 --- a/tests/baselines/reference/bestCommonTypeOfConditionalExpressions.types +++ b/tests/baselines/reference/bestCommonTypeOfConditionalExpressions.types @@ -64,8 +64,8 @@ var r5 = true ? b : a; // typeof b >a : { x: number; y?: number; } var r6 = true ? (x: number) => { } : (x: Object) => { }; // returns number => void ->r6 : (x: number) => void ->true ? (x: number) => { } : (x: Object) => { } : (x: number) => void +>r6 : ((x: number) => void) | ((x: Object) => void) +>true ? (x: number) => { } : (x: Object) => { } : ((x: number) => void) | ((x: Object) => void) >true : true >(x: number) => { } : (x: number) => void >x : number @@ -75,7 +75,7 @@ var r6 = true ? (x: number) => { } : (x: Object) => { }; // returns number => vo var r7: (x: Object) => void = true ? (x: number) => { } : (x: Object) => { }; >r7 : (x: Object) => void >x : Object ->true ? (x: number) => { } : (x: Object) => { } : (x: number) => void +>true ? (x: number) => { } : (x: Object) => { } : ((x: number) => void) | ((x: Object) => void) >true : true >(x: number) => { } : (x: number) => void >x : number @@ -83,8 +83,8 @@ var r7: (x: Object) => void = true ? (x: number) => { } : (x: Object) => { }; >x : Object var r8 = true ? (x: Object) => { } : (x: number) => { }; // returns Object => void ->r8 : (x: Object) => void ->true ? (x: Object) => { } : (x: number) => { } : (x: Object) => void +>r8 : ((x: Object) => void) | ((x: number) => void) +>true ? (x: Object) => { } : (x: number) => { } : ((x: Object) => void) | ((x: number) => void) >true : true >(x: Object) => { } : (x: Object) => void >x : Object diff --git a/tests/baselines/reference/functionWithMultipleReturnStatements2.types b/tests/baselines/reference/functionWithMultipleReturnStatements2.types index e0a9fed1a1b79..f528ad04aaebb 100644 --- a/tests/baselines/reference/functionWithMultipleReturnStatements2.types +++ b/tests/baselines/reference/functionWithMultipleReturnStatements2.types @@ -58,7 +58,7 @@ function f4() { } function f5() { ->f5 : () => Object +>f5 : () => Object | 1 return 1; >1 : 1 @@ -136,7 +136,7 @@ function f10() { // returns number => void function f11() { ->f11 : () => (x: number) => void +>f11 : () => ((x: number) => void) | ((x: Object) => void) if (true) { >true : true @@ -154,7 +154,7 @@ function f11() { // returns Object => void function f12() { ->f12 : () => (x: Object) => void +>f12 : () => ((x: Object) => void) | ((x: number) => void) if (true) { >true : true diff --git a/tests/baselines/reference/subtypesOfTypeParameterWithConstraints2.types b/tests/baselines/reference/subtypesOfTypeParameterWithConstraints2.types index daf700b06f293..dc3f90fa545cb 100644 --- a/tests/baselines/reference/subtypesOfTypeParameterWithConstraints2.types +++ b/tests/baselines/reference/subtypesOfTypeParameterWithConstraints2.types @@ -566,16 +566,16 @@ function f20(x: T) { >x : T var r19 = true ? new Object() : x; // ok ->r19 : Object ->true ? new Object() : x : Object +>r19 : Object | T +>true ? new Object() : x : Object | T >true : true >new Object() : Object >Object : ObjectConstructor >x : T var r19 = true ? x : new Object(); // ok ->r19 : Object ->true ? x : new Object() : Object +>r19 : Object | T +>true ? x : new Object() : Object | T >true : true >x : T >new Object() : Object diff --git a/tests/baselines/reference/typeGuardRedundancy.types b/tests/baselines/reference/typeGuardRedundancy.types index 10bec968ed208..82b217494f3a5 100644 --- a/tests/baselines/reference/typeGuardRedundancy.types +++ b/tests/baselines/reference/typeGuardRedundancy.types @@ -3,8 +3,8 @@ var x: string|number; >x : string | number var r1 = typeof x === "string" && typeof x === "string" ? x.substr : x.toFixed; ->r1 : (from: number, length?: number) => string ->typeof x === "string" && typeof x === "string" ? x.substr : x.toFixed : (from: number, length?: number) => string +>r1 : ((from: number, length?: number) => string) | ((fractionDigits?: number) => string) +>typeof x === "string" && typeof x === "string" ? x.substr : x.toFixed : ((from: number, length?: number) => string) | ((fractionDigits?: number) => string) >typeof x === "string" && typeof x === "string" : boolean >typeof x === "string" : boolean >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" @@ -22,8 +22,8 @@ var r1 = typeof x === "string" && typeof x === "string" ? x.substr : x.toFixed; >toFixed : (fractionDigits?: number) => string var r2 = !(typeof x === "string" && typeof x === "string") ? x.toFixed : x.substr; ->r2 : (from: number, length?: number) => string ->!(typeof x === "string" && typeof x === "string") ? x.toFixed : x.substr : (from: number, length?: number) => string +>r2 : ((from: number, length?: number) => string) | ((fractionDigits?: number) => string) +>!(typeof x === "string" && typeof x === "string") ? x.toFixed : x.substr : ((from: number, length?: number) => string) | ((fractionDigits?: number) => string) >!(typeof x === "string" && typeof x === "string") : boolean >(typeof x === "string" && typeof x === "string") : boolean >typeof x === "string" && typeof x === "string" : boolean @@ -43,8 +43,8 @@ var r2 = !(typeof x === "string" && typeof x === "string") ? x.toFixed : x.subst >substr : (from: number, length?: number) => string var r3 = typeof x === "string" || typeof x === "string" ? x.substr : x.toFixed; ->r3 : (from: number, length?: number) => string ->typeof x === "string" || typeof x === "string" ? x.substr : x.toFixed : (from: number, length?: number) => string +>r3 : ((from: number, length?: number) => string) | ((fractionDigits?: number) => string) +>typeof x === "string" || typeof x === "string" ? x.substr : x.toFixed : ((from: number, length?: number) => string) | ((fractionDigits?: number) => string) >typeof x === "string" || typeof x === "string" : boolean >typeof x === "string" : boolean >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" @@ -62,8 +62,8 @@ var r3 = typeof x === "string" || typeof x === "string" ? x.substr : x.toFixed; >toFixed : (fractionDigits?: number) => string var r4 = !(typeof x === "string" || typeof x === "string") ? x.toFixed : x.substr; ->r4 : (from: number, length?: number) => string ->!(typeof x === "string" || typeof x === "string") ? x.toFixed : x.substr : (from: number, length?: number) => string +>r4 : ((from: number, length?: number) => string) | ((fractionDigits?: number) => string) +>!(typeof x === "string" || typeof x === "string") ? x.toFixed : x.substr : ((from: number, length?: number) => string) | ((fractionDigits?: number) => string) >!(typeof x === "string" || typeof x === "string") : boolean >(typeof x === "string" || typeof x === "string") : boolean >typeof x === "string" || typeof x === "string" : boolean From baac083a31cfc967ce722c385f000fc3f3b058ea Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 17 Dec 2019 13:09:58 -0800 Subject: [PATCH 07/11] Add tests --- .../types/union/unionTypeReduction2.ts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/cases/conformance/types/union/unionTypeReduction2.ts diff --git a/tests/cases/conformance/types/union/unionTypeReduction2.ts b/tests/cases/conformance/types/union/unionTypeReduction2.ts new file mode 100644 index 0000000000000..ed456f0cb0520 --- /dev/null +++ b/tests/cases/conformance/types/union/unionTypeReduction2.ts @@ -0,0 +1,66 @@ +// @strict: true + +function f1(x: { f(): void }, y: { f(x?: string): void }) { + let z = !!true ? x : y; // { f(x?: string): void } + z.f(); + z.f('hello'); +} + +function f2(x: { f(x: string | undefined): void }, y: { f(x?: string): void }) { + let z = !!true ? x : y; // { f(x?: string): void } + z.f(); + z.f('hello'); +} + +function f3(x: () => void, y: (x?: string) => void) { + let f = !!true ? x : y; // (x?: string) => void + f(); + f('hello'); +} + +function f4(x: (x: string | undefined) => void, y: (x?: string) => void) { + let f = !!true ? x : y; // (x?: string) => void + f(); + f('hello'); +} + +function f5(x: (x: string | undefined) => void, y: (x?: 'hello') => void) { + let f = !!true ? x : y; // (x?: 'hello') => void + f(); + f('hello'); +} + +function f6(x: (x: 'hello' | undefined) => void, y: (x?: string) => void) { + let f = !!true ? x : y; // (x: 'hello' | undefined) => void + f(); // Error + f('hello'); +} + +type A = { + f(): void; +} + +type B = { + f(x?: string): void; + g(): void; +} + +function f11(a: A, b: B) { + let z = !!true ? a : b; // A | B + z.f(); + z.f('hello'); +} + +// Repro from #35414 + +interface ReturnVal { + something(): void; +} + +const k: ReturnVal = { something() { } } + +declare const val: ReturnVal; +function run(options: { something?(b?: string): void }) { + const something = options.something ?? val.something; + something(''); +} From e0e45ec46cf4c4b80bf65e6a22eb784aa78016eb Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 17 Dec 2019 13:10:06 -0800 Subject: [PATCH 08/11] Accept new baselines --- .../reference/unionTypeReduction2.errors.txt | 72 +++++ .../reference/unionTypeReduction2.js | 110 ++++++++ .../reference/unionTypeReduction2.symbols | 203 ++++++++++++++ .../reference/unionTypeReduction2.types | 250 ++++++++++++++++++ 4 files changed, 635 insertions(+) create mode 100644 tests/baselines/reference/unionTypeReduction2.errors.txt create mode 100644 tests/baselines/reference/unionTypeReduction2.js create mode 100644 tests/baselines/reference/unionTypeReduction2.symbols create mode 100644 tests/baselines/reference/unionTypeReduction2.types diff --git a/tests/baselines/reference/unionTypeReduction2.errors.txt b/tests/baselines/reference/unionTypeReduction2.errors.txt new file mode 100644 index 0000000000000..74567f8808bf1 --- /dev/null +++ b/tests/baselines/reference/unionTypeReduction2.errors.txt @@ -0,0 +1,72 @@ +tests/cases/conformance/types/union/unionTypeReduction2.ts(33,5): error TS2554: Expected 1 arguments, but got 0. + + +==== tests/cases/conformance/types/union/unionTypeReduction2.ts (1 errors) ==== + function f1(x: { f(): void }, y: { f(x?: string): void }) { + let z = !!true ? x : y; // { f(x?: string): void } + z.f(); + z.f('hello'); + } + + function f2(x: { f(x: string | undefined): void }, y: { f(x?: string): void }) { + let z = !!true ? x : y; // { f(x?: string): void } + z.f(); + z.f('hello'); + } + + function f3(x: () => void, y: (x?: string) => void) { + let f = !!true ? x : y; // (x?: string) => void + f(); + f('hello'); + } + + function f4(x: (x: string | undefined) => void, y: (x?: string) => void) { + let f = !!true ? x : y; // (x?: string) => void + f(); + f('hello'); + } + + function f5(x: (x: string | undefined) => void, y: (x?: 'hello') => void) { + let f = !!true ? x : y; // (x?: 'hello') => void + f(); + f('hello'); + } + + function f6(x: (x: 'hello' | undefined) => void, y: (x?: string) => void) { + let f = !!true ? x : y; // (x: 'hello' | undefined) => void + f(); // Error + ~~~ +!!! error TS2554: Expected 1 arguments, but got 0. +!!! related TS6210 tests/cases/conformance/types/union/unionTypeReduction2.ts:31:17: An argument for 'x' was not provided. + f('hello'); + } + + type A = { + f(): void; + } + + type B = { + f(x?: string): void; + g(): void; + } + + function f11(a: A, b: B) { + let z = !!true ? a : b; // A | B + z.f(); + z.f('hello'); + } + + // Repro from #35414 + + interface ReturnVal { + something(): void; + } + + const k: ReturnVal = { something() { } } + + declare const val: ReturnVal; + function run(options: { something?(b?: string): void }) { + const something = options.something ?? val.something; + something(''); + } + \ No newline at end of file diff --git a/tests/baselines/reference/unionTypeReduction2.js b/tests/baselines/reference/unionTypeReduction2.js new file mode 100644 index 0000000000000..3749b9c8cf5a3 --- /dev/null +++ b/tests/baselines/reference/unionTypeReduction2.js @@ -0,0 +1,110 @@ +//// [unionTypeReduction2.ts] +function f1(x: { f(): void }, y: { f(x?: string): void }) { + let z = !!true ? x : y; // { f(x?: string): void } + z.f(); + z.f('hello'); +} + +function f2(x: { f(x: string | undefined): void }, y: { f(x?: string): void }) { + let z = !!true ? x : y; // { f(x?: string): void } + z.f(); + z.f('hello'); +} + +function f3(x: () => void, y: (x?: string) => void) { + let f = !!true ? x : y; // (x?: string) => void + f(); + f('hello'); +} + +function f4(x: (x: string | undefined) => void, y: (x?: string) => void) { + let f = !!true ? x : y; // (x?: string) => void + f(); + f('hello'); +} + +function f5(x: (x: string | undefined) => void, y: (x?: 'hello') => void) { + let f = !!true ? x : y; // (x?: 'hello') => void + f(); + f('hello'); +} + +function f6(x: (x: 'hello' | undefined) => void, y: (x?: string) => void) { + let f = !!true ? x : y; // (x: 'hello' | undefined) => void + f(); // Error + f('hello'); +} + +type A = { + f(): void; +} + +type B = { + f(x?: string): void; + g(): void; +} + +function f11(a: A, b: B) { + let z = !!true ? a : b; // A | B + z.f(); + z.f('hello'); +} + +// Repro from #35414 + +interface ReturnVal { + something(): void; +} + +const k: ReturnVal = { something() { } } + +declare const val: ReturnVal; +function run(options: { something?(b?: string): void }) { + const something = options.something ?? val.something; + something(''); +} + + +//// [unionTypeReduction2.js] +"use strict"; +function f1(x, y) { + var z = !!true ? x : y; // { f(x?: string): void } + z.f(); + z.f('hello'); +} +function f2(x, y) { + var z = !!true ? x : y; // { f(x?: string): void } + z.f(); + z.f('hello'); +} +function f3(x, y) { + var f = !!true ? x : y; // (x?: string) => void + f(); + f('hello'); +} +function f4(x, y) { + var f = !!true ? x : y; // (x?: string) => void + f(); + f('hello'); +} +function f5(x, y) { + var f = !!true ? x : y; // (x?: 'hello') => void + f(); + f('hello'); +} +function f6(x, y) { + var f = !!true ? x : y; // (x: 'hello' | undefined) => void + f(); // Error + f('hello'); +} +function f11(a, b) { + var z = !!true ? a : b; // A | B + z.f(); + z.f('hello'); +} +var k = { something: function () { } }; +function run(options) { + var _a; + var something = (_a = options.something) !== null && _a !== void 0 ? _a : val.something; + something(''); +} diff --git a/tests/baselines/reference/unionTypeReduction2.symbols b/tests/baselines/reference/unionTypeReduction2.symbols new file mode 100644 index 0000000000000..0faf0055279f5 --- /dev/null +++ b/tests/baselines/reference/unionTypeReduction2.symbols @@ -0,0 +1,203 @@ +=== tests/cases/conformance/types/union/unionTypeReduction2.ts === +function f1(x: { f(): void }, y: { f(x?: string): void }) { +>f1 : Symbol(f1, Decl(unionTypeReduction2.ts, 0, 0)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 0, 12)) +>f : Symbol(f, Decl(unionTypeReduction2.ts, 0, 16)) +>y : Symbol(y, Decl(unionTypeReduction2.ts, 0, 29)) +>f : Symbol(f, Decl(unionTypeReduction2.ts, 0, 34)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 0, 37)) + + let z = !!true ? x : y; // { f(x?: string): void } +>z : Symbol(z, Decl(unionTypeReduction2.ts, 1, 7)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 0, 12)) +>y : Symbol(y, Decl(unionTypeReduction2.ts, 0, 29)) + + z.f(); +>z.f : Symbol(f, Decl(unionTypeReduction2.ts, 0, 34)) +>z : Symbol(z, Decl(unionTypeReduction2.ts, 1, 7)) +>f : Symbol(f, Decl(unionTypeReduction2.ts, 0, 34)) + + z.f('hello'); +>z.f : Symbol(f, Decl(unionTypeReduction2.ts, 0, 34)) +>z : Symbol(z, Decl(unionTypeReduction2.ts, 1, 7)) +>f : Symbol(f, Decl(unionTypeReduction2.ts, 0, 34)) +} + +function f2(x: { f(x: string | undefined): void }, y: { f(x?: string): void }) { +>f2 : Symbol(f2, Decl(unionTypeReduction2.ts, 4, 1)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 6, 12)) +>f : Symbol(f, Decl(unionTypeReduction2.ts, 6, 16)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 6, 19)) +>y : Symbol(y, Decl(unionTypeReduction2.ts, 6, 50)) +>f : Symbol(f, Decl(unionTypeReduction2.ts, 6, 55)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 6, 58)) + + let z = !!true ? x : y; // { f(x?: string): void } +>z : Symbol(z, Decl(unionTypeReduction2.ts, 7, 7)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 6, 12)) +>y : Symbol(y, Decl(unionTypeReduction2.ts, 6, 50)) + + z.f(); +>z.f : Symbol(f, Decl(unionTypeReduction2.ts, 6, 55)) +>z : Symbol(z, Decl(unionTypeReduction2.ts, 7, 7)) +>f : Symbol(f, Decl(unionTypeReduction2.ts, 6, 55)) + + z.f('hello'); +>z.f : Symbol(f, Decl(unionTypeReduction2.ts, 6, 55)) +>z : Symbol(z, Decl(unionTypeReduction2.ts, 7, 7)) +>f : Symbol(f, Decl(unionTypeReduction2.ts, 6, 55)) +} + +function f3(x: () => void, y: (x?: string) => void) { +>f3 : Symbol(f3, Decl(unionTypeReduction2.ts, 10, 1)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 12, 12)) +>y : Symbol(y, Decl(unionTypeReduction2.ts, 12, 26)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 12, 31)) + + let f = !!true ? x : y; // (x?: string) => void +>f : Symbol(f, Decl(unionTypeReduction2.ts, 13, 7)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 12, 12)) +>y : Symbol(y, Decl(unionTypeReduction2.ts, 12, 26)) + + f(); +>f : Symbol(f, Decl(unionTypeReduction2.ts, 13, 7)) + + f('hello'); +>f : Symbol(f, Decl(unionTypeReduction2.ts, 13, 7)) +} + +function f4(x: (x: string | undefined) => void, y: (x?: string) => void) { +>f4 : Symbol(f4, Decl(unionTypeReduction2.ts, 16, 1)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 18, 12)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 18, 16)) +>y : Symbol(y, Decl(unionTypeReduction2.ts, 18, 47)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 18, 52)) + + let f = !!true ? x : y; // (x?: string) => void +>f : Symbol(f, Decl(unionTypeReduction2.ts, 19, 7)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 18, 12)) +>y : Symbol(y, Decl(unionTypeReduction2.ts, 18, 47)) + + f(); +>f : Symbol(f, Decl(unionTypeReduction2.ts, 19, 7)) + + f('hello'); +>f : Symbol(f, Decl(unionTypeReduction2.ts, 19, 7)) +} + +function f5(x: (x: string | undefined) => void, y: (x?: 'hello') => void) { +>f5 : Symbol(f5, Decl(unionTypeReduction2.ts, 22, 1)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 24, 12)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 24, 16)) +>y : Symbol(y, Decl(unionTypeReduction2.ts, 24, 47)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 24, 52)) + + let f = !!true ? x : y; // (x?: 'hello') => void +>f : Symbol(f, Decl(unionTypeReduction2.ts, 25, 7)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 24, 12)) +>y : Symbol(y, Decl(unionTypeReduction2.ts, 24, 47)) + + f(); +>f : Symbol(f, Decl(unionTypeReduction2.ts, 25, 7)) + + f('hello'); +>f : Symbol(f, Decl(unionTypeReduction2.ts, 25, 7)) +} + +function f6(x: (x: 'hello' | undefined) => void, y: (x?: string) => void) { +>f6 : Symbol(f6, Decl(unionTypeReduction2.ts, 28, 1)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 30, 12)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 30, 16)) +>y : Symbol(y, Decl(unionTypeReduction2.ts, 30, 48)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 30, 53)) + + let f = !!true ? x : y; // (x: 'hello' | undefined) => void +>f : Symbol(f, Decl(unionTypeReduction2.ts, 31, 7)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 30, 12)) +>y : Symbol(y, Decl(unionTypeReduction2.ts, 30, 48)) + + f(); // Error +>f : Symbol(f, Decl(unionTypeReduction2.ts, 31, 7)) + + f('hello'); +>f : Symbol(f, Decl(unionTypeReduction2.ts, 31, 7)) +} + +type A = { +>A : Symbol(A, Decl(unionTypeReduction2.ts, 34, 1)) + + f(): void; +>f : Symbol(f, Decl(unionTypeReduction2.ts, 36, 10)) +} + +type B = { +>B : Symbol(B, Decl(unionTypeReduction2.ts, 38, 1)) + + f(x?: string): void; +>f : Symbol(f, Decl(unionTypeReduction2.ts, 40, 10)) +>x : Symbol(x, Decl(unionTypeReduction2.ts, 41, 6)) + + g(): void; +>g : Symbol(g, Decl(unionTypeReduction2.ts, 41, 24)) +} + +function f11(a: A, b: B) { +>f11 : Symbol(f11, Decl(unionTypeReduction2.ts, 43, 1)) +>a : Symbol(a, Decl(unionTypeReduction2.ts, 45, 13)) +>A : Symbol(A, Decl(unionTypeReduction2.ts, 34, 1)) +>b : Symbol(b, Decl(unionTypeReduction2.ts, 45, 18)) +>B : Symbol(B, Decl(unionTypeReduction2.ts, 38, 1)) + + let z = !!true ? a : b; // A | B +>z : Symbol(z, Decl(unionTypeReduction2.ts, 46, 7)) +>a : Symbol(a, Decl(unionTypeReduction2.ts, 45, 13)) +>b : Symbol(b, Decl(unionTypeReduction2.ts, 45, 18)) + + z.f(); +>z.f : Symbol(f, Decl(unionTypeReduction2.ts, 36, 10), Decl(unionTypeReduction2.ts, 40, 10)) +>z : Symbol(z, Decl(unionTypeReduction2.ts, 46, 7)) +>f : Symbol(f, Decl(unionTypeReduction2.ts, 36, 10), Decl(unionTypeReduction2.ts, 40, 10)) + + z.f('hello'); +>z.f : Symbol(f, Decl(unionTypeReduction2.ts, 36, 10), Decl(unionTypeReduction2.ts, 40, 10)) +>z : Symbol(z, Decl(unionTypeReduction2.ts, 46, 7)) +>f : Symbol(f, Decl(unionTypeReduction2.ts, 36, 10), Decl(unionTypeReduction2.ts, 40, 10)) +} + +// Repro from #35414 + +interface ReturnVal { +>ReturnVal : Symbol(ReturnVal, Decl(unionTypeReduction2.ts, 49, 1)) + + something(): void; +>something : Symbol(ReturnVal.something, Decl(unionTypeReduction2.ts, 53, 21)) +} + +const k: ReturnVal = { something() { } } +>k : Symbol(k, Decl(unionTypeReduction2.ts, 57, 5)) +>ReturnVal : Symbol(ReturnVal, Decl(unionTypeReduction2.ts, 49, 1)) +>something : Symbol(something, Decl(unionTypeReduction2.ts, 57, 22)) + +declare const val: ReturnVal; +>val : Symbol(val, Decl(unionTypeReduction2.ts, 59, 13)) +>ReturnVal : Symbol(ReturnVal, Decl(unionTypeReduction2.ts, 49, 1)) + +function run(options: { something?(b?: string): void }) { +>run : Symbol(run, Decl(unionTypeReduction2.ts, 59, 29)) +>options : Symbol(options, Decl(unionTypeReduction2.ts, 60, 13)) +>something : Symbol(something, Decl(unionTypeReduction2.ts, 60, 23)) +>b : Symbol(b, Decl(unionTypeReduction2.ts, 60, 35)) + + const something = options.something ?? val.something; +>something : Symbol(something, Decl(unionTypeReduction2.ts, 61, 9)) +>options.something : Symbol(something, Decl(unionTypeReduction2.ts, 60, 23)) +>options : Symbol(options, Decl(unionTypeReduction2.ts, 60, 13)) +>something : Symbol(something, Decl(unionTypeReduction2.ts, 60, 23)) +>val.something : Symbol(ReturnVal.something, Decl(unionTypeReduction2.ts, 53, 21)) +>val : Symbol(val, Decl(unionTypeReduction2.ts, 59, 13)) +>something : Symbol(ReturnVal.something, Decl(unionTypeReduction2.ts, 53, 21)) + + something(''); +>something : Symbol(something, Decl(unionTypeReduction2.ts, 61, 9)) +} + diff --git a/tests/baselines/reference/unionTypeReduction2.types b/tests/baselines/reference/unionTypeReduction2.types new file mode 100644 index 0000000000000..5342ab692ab7d --- /dev/null +++ b/tests/baselines/reference/unionTypeReduction2.types @@ -0,0 +1,250 @@ +=== tests/cases/conformance/types/union/unionTypeReduction2.ts === +function f1(x: { f(): void }, y: { f(x?: string): void }) { +>f1 : (x: { f(): void; }, y: { f(x?: string | undefined): void; }) => void +>x : { f(): void; } +>f : () => void +>y : { f(x?: string | undefined): void; } +>f : (x?: string | undefined) => void +>x : string | undefined + + let z = !!true ? x : y; // { f(x?: string): void } +>z : { f(x?: string | undefined): void; } +>!!true ? x : y : { f(x?: string | undefined): void; } +>!!true : true +>!true : false +>true : true +>x : { f(): void; } +>y : { f(x?: string | undefined): void; } + + z.f(); +>z.f() : void +>z.f : (x?: string | undefined) => void +>z : { f(x?: string | undefined): void; } +>f : (x?: string | undefined) => void + + z.f('hello'); +>z.f('hello') : void +>z.f : (x?: string | undefined) => void +>z : { f(x?: string | undefined): void; } +>f : (x?: string | undefined) => void +>'hello' : "hello" +} + +function f2(x: { f(x: string | undefined): void }, y: { f(x?: string): void }) { +>f2 : (x: { f(x: string | undefined): void; }, y: { f(x?: string | undefined): void; }) => void +>x : { f(x: string | undefined): void; } +>f : (x: string | undefined) => void +>x : string | undefined +>y : { f(x?: string | undefined): void; } +>f : (x?: string | undefined) => void +>x : string | undefined + + let z = !!true ? x : y; // { f(x?: string): void } +>z : { f(x?: string | undefined): void; } +>!!true ? x : y : { f(x?: string | undefined): void; } +>!!true : true +>!true : false +>true : true +>x : { f(x: string | undefined): void; } +>y : { f(x?: string | undefined): void; } + + z.f(); +>z.f() : void +>z.f : (x?: string | undefined) => void +>z : { f(x?: string | undefined): void; } +>f : (x?: string | undefined) => void + + z.f('hello'); +>z.f('hello') : void +>z.f : (x?: string | undefined) => void +>z : { f(x?: string | undefined): void; } +>f : (x?: string | undefined) => void +>'hello' : "hello" +} + +function f3(x: () => void, y: (x?: string) => void) { +>f3 : (x: () => void, y: (x?: string | undefined) => void) => void +>x : () => void +>y : (x?: string | undefined) => void +>x : string | undefined + + let f = !!true ? x : y; // (x?: string) => void +>f : (x?: string | undefined) => void +>!!true ? x : y : (x?: string | undefined) => void +>!!true : true +>!true : false +>true : true +>x : () => void +>y : (x?: string | undefined) => void + + f(); +>f() : void +>f : (x?: string | undefined) => void + + f('hello'); +>f('hello') : void +>f : (x?: string | undefined) => void +>'hello' : "hello" +} + +function f4(x: (x: string | undefined) => void, y: (x?: string) => void) { +>f4 : (x: (x: string | undefined) => void, y: (x?: string | undefined) => void) => void +>x : (x: string | undefined) => void +>x : string | undefined +>y : (x?: string | undefined) => void +>x : string | undefined + + let f = !!true ? x : y; // (x?: string) => void +>f : (x?: string | undefined) => void +>!!true ? x : y : (x?: string | undefined) => void +>!!true : true +>!true : false +>true : true +>x : (x: string | undefined) => void +>y : (x?: string | undefined) => void + + f(); +>f() : void +>f : (x?: string | undefined) => void + + f('hello'); +>f('hello') : void +>f : (x?: string | undefined) => void +>'hello' : "hello" +} + +function f5(x: (x: string | undefined) => void, y: (x?: 'hello') => void) { +>f5 : (x: (x: string | undefined) => void, y: (x?: "hello" | undefined) => void) => void +>x : (x: string | undefined) => void +>x : string | undefined +>y : (x?: "hello" | undefined) => void +>x : "hello" | undefined + + let f = !!true ? x : y; // (x?: 'hello') => void +>f : (x?: "hello" | undefined) => void +>!!true ? x : y : (x?: "hello" | undefined) => void +>!!true : true +>!true : false +>true : true +>x : (x: string | undefined) => void +>y : (x?: "hello" | undefined) => void + + f(); +>f() : void +>f : (x?: "hello" | undefined) => void + + f('hello'); +>f('hello') : void +>f : (x?: "hello" | undefined) => void +>'hello' : "hello" +} + +function f6(x: (x: 'hello' | undefined) => void, y: (x?: string) => void) { +>f6 : (x: (x: "hello" | undefined) => void, y: (x?: string | undefined) => void) => void +>x : (x: "hello" | undefined) => void +>x : "hello" | undefined +>y : (x?: string | undefined) => void +>x : string | undefined + + let f = !!true ? x : y; // (x: 'hello' | undefined) => void +>f : (x: "hello" | undefined) => void +>!!true ? x : y : (x: "hello" | undefined) => void +>!!true : true +>!true : false +>true : true +>x : (x: "hello" | undefined) => void +>y : (x?: string | undefined) => void + + f(); // Error +>f() : void +>f : (x: "hello" | undefined) => void + + f('hello'); +>f('hello') : void +>f : (x: "hello" | undefined) => void +>'hello' : "hello" +} + +type A = { +>A : A + + f(): void; +>f : () => void +} + +type B = { +>B : B + + f(x?: string): void; +>f : (x?: string | undefined) => void +>x : string | undefined + + g(): void; +>g : () => void +} + +function f11(a: A, b: B) { +>f11 : (a: A, b: B) => void +>a : A +>b : B + + let z = !!true ? a : b; // A | B +>z : A | B +>!!true ? a : b : A | B +>!!true : true +>!true : false +>true : true +>a : A +>b : B + + z.f(); +>z.f() : void +>z.f : ((x?: string | undefined) => void) | (() => void) +>z : A | B +>f : ((x?: string | undefined) => void) | (() => void) + + z.f('hello'); +>z.f('hello') : void +>z.f : ((x?: string | undefined) => void) | (() => void) +>z : A | B +>f : ((x?: string | undefined) => void) | (() => void) +>'hello' : "hello" +} + +// Repro from #35414 + +interface ReturnVal { + something(): void; +>something : () => void +} + +const k: ReturnVal = { something() { } } +>k : ReturnVal +>{ something() { } } : { something(): void; } +>something : () => void + +declare const val: ReturnVal; +>val : ReturnVal + +function run(options: { something?(b?: string): void }) { +>run : (options: { something?(b?: string | undefined): void; }) => void +>options : { something?(b?: string | undefined): void; } +>something : ((b?: string | undefined) => void) | undefined +>b : string | undefined + + const something = options.something ?? val.something; +>something : (b?: string | undefined) => void +>options.something ?? val.something : (b?: string | undefined) => void +>options.something : ((b?: string | undefined) => void) | undefined +>options : { something?(b?: string | undefined): void; } +>something : ((b?: string | undefined) => void) | undefined +>val.something : () => void +>val : ReturnVal +>something : () => void + + something(''); +>something('') : void +>something : (b?: string | undefined) => void +>'' : "" +} + From 8228888f5b4f22aab8794055ad15dd4a31efc207 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 20 Dec 2019 13:25:59 -0800 Subject: [PATCH 09/11] Address CR feedback --- src/compiler/checker.ts | 17 ++++++++++++++--- src/compiler/types.ts | 4 ++-- src/executeCommandLine/executeCommandLine.ts | 1 + 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 22b0ee708e02a..4f60cef1ee9d3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -345,6 +345,7 @@ namespace ts { assignable: assignableRelation.size, identity: identityRelation.size, subtype: subtypeRelation.size, + strictSubtype: strictSubtypeRelation.size, }), isUndefinedSymbol: symbol => symbol === undefinedSymbol, isArgumentsSymbol: symbol => symbol === argumentsSymbol, @@ -14027,8 +14028,17 @@ namespace ts { } const targetCount = getParameterCount(target); - if (!hasEffectiveRestParameter(target) && (checkMode & SignatureCheckMode.StrictArity ? getParameterCount(source) : getMinArgumentCount(source)) > targetCount) { - return Ternary.False; + if (checkMode & SignatureCheckMode.StrictArity) { + const sourceHasRest = hasEffectiveRestParameter(source); + const targetHasRest = hasEffectiveRestParameter(target); + if (sourceHasRest && !targetHasRest || sourceHasRest === targetHasRest && getParameterCount(source) > getParameterCount(target)) { + return Ternary.False; + } + } + else { + if (!hasEffectiveRestParameter(target) && getMinArgumentCount(source) > targetCount) { + return Ternary.False; + } } if (source.typeParameters && source.typeParameters !== target.typeParameters) { @@ -14091,7 +14101,7 @@ namespace ts { compareSignaturesRelated(targetSig!, sourceSig!, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : !(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); // With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void - if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && isTypeIdenticalTo(sourceType, targetType)) { + if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) { related = Ternary.False; } if (!related) { @@ -15495,6 +15505,7 @@ namespace ts { // to X. Failing both of those we want to check if the aggregation of A and B's members structurally // relates to X. Thus, we include intersection types on the source side here. if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { + // Report structural errors only if we haven't reported any errors yet const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, isIntersectionConstituent); if (result) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1e0e32bcc45f0..8c81f3c519899 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3161,7 +3161,7 @@ namespace ts { getIdentifierCount(): number; getSymbolCount(): number; getTypeCount(): number; - getRelationCacheSizes(): { assignable: number, identity: number, subtype: number }; + getRelationCacheSizes(): { assignable: number, identity: number, subtype: number, strictSubtype: number }; /* @internal */ getFileProcessingDiagnostics(): DiagnosticCollection; /* @internal */ getResolvedTypeReferenceDirectives(): Map; @@ -3479,7 +3479,7 @@ namespace ts { /* @internal */ getIdentifierCount(): number; /* @internal */ getSymbolCount(): number; /* @internal */ getTypeCount(): number; - /* @internal */ getRelationCacheSizes(): { assignable: number, identity: number, subtype: number }; + /* @internal */ getRelationCacheSizes(): { assignable: number, identity: number, subtype: number, strictSubtype: number }; /* @internal */ isArrayType(type: Type): boolean; /* @internal */ isTupleType(type: Type): boolean; diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index 94d7a19f30e25..d182d6cbcfc40 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -654,6 +654,7 @@ namespace ts { reportCountStatistic("Assignability cache size", caches.assignable); reportCountStatistic("Identity cache size", caches.identity); reportCountStatistic("Subtype cache size", caches.subtype); + reportCountStatistic("Strict subtype cache size", caches.strictSubtype); performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration)); } else { From 273bb3e6de4211bf41ee79c8bfcced854b415a19 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 20 Dec 2019 14:30:34 -0800 Subject: [PATCH 10/11] Fix parameter list length check --- src/compiler/checker.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4f60cef1ee9d3..571ab2f83f48d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14028,17 +14028,10 @@ namespace ts { } const targetCount = getParameterCount(target); - if (checkMode & SignatureCheckMode.StrictArity) { - const sourceHasRest = hasEffectiveRestParameter(source); - const targetHasRest = hasEffectiveRestParameter(target); - if (sourceHasRest && !targetHasRest || sourceHasRest === targetHasRest && getParameterCount(source) > getParameterCount(target)) { - return Ternary.False; - } - } - else { - if (!hasEffectiveRestParameter(target) && getMinArgumentCount(source) > targetCount) { - return Ternary.False; - } + const sourceHasMoreParameters = !hasEffectiveRestParameter(target) && + (checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount); + if (sourceHasMoreParameters) { + return Ternary.False; } if (source.typeParameters && source.typeParameters !== target.typeParameters) { From 4154dcb3954e4f1022317787c514f63911feda5a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 20 Dec 2019 14:33:07 -0800 Subject: [PATCH 11/11] Accept API baseline changes --- tests/baselines/reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 00890b067f855..9be8107c8e2bf 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1919,6 +1919,7 @@ declare namespace ts { assignable: number; identity: number; subtype: number; + strictSubtype: number; }; isSourceFileFromExternalLibrary(file: SourceFile): boolean; isSourceFileDefaultLibrary(file: SourceFile): boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 89f8bb0543dea..f43fa77317b10 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1919,6 +1919,7 @@ declare namespace ts { assignable: number; identity: number; subtype: number; + strictSubtype: number; }; isSourceFileFromExternalLibrary(file: SourceFile): boolean; isSourceFileDefaultLibrary(file: SourceFile): boolean;