Skip to content
Merged
58 changes: 33 additions & 25 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -343,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,
Expand Down Expand Up @@ -869,6 +872,7 @@ namespace ts {
let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined;

const subtypeRelation = createMap<RelationComparisonResult>();
const strictSubtypeRelation = createMap<RelationComparisonResult>();
const assignableRelation = createMap<RelationComparisonResult>();
const comparableRelation = createMap<RelationComparisonResult>();
const identityRelation = createMap<RelationComparisonResult>();
Expand Down Expand Up @@ -11400,7 +11404,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))) {
Expand Down Expand Up @@ -13988,7 +13992,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;
}

Expand All @@ -14008,8 +14012,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,
Expand All @@ -14025,7 +14028,9 @@ namespace ts {
}

const targetCount = getParameterCount(target);
if (!hasEffectiveRestParameter(target) && getMinArgumentCount(source) > targetCount) {
const sourceHasMoreParameters = !hasEffectiveRestParameter(target) &&
(checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount);
if (sourceHasMoreParameters) {
return Ternary.False;
}

Expand All @@ -14046,7 +14051,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;

Expand Down Expand Up @@ -14081,14 +14086,17 @@ namespace ts {
// similar to return values, callback parameters are output positions. This means that a Promise<T>,
// 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);
let related = callbacks ?
compareSignaturesRelated(targetSig!, sourceSig!, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) :
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the isTypeIdenticalTo check here for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without it a signature like (x?: string | undefined) => void wouldn't be a subtype of (x: string) => void. We want it to be such that we reduce a union to just (x: string) => void. It's basically a tiebreaker for positions with identical types but one with and one without the ? modifier.

Copy link
Member

@weswigham weswigham Dec 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...Mmm, but isTypeIdenticalTo doesn't seem quite right...? Shouldn't it be compareTypes(sourceType, targetType, /*reportErrors*/ false)? The difference, I think, should really only be visible, but from what I gather is important when callbacks with optional parameters are the parameter type of callbacks with optional parameters, eg (x?: (x?: string | undefined) => void) => void and (x: (x: string | undefined) => void) => void The first should be a subtype of the second because the second's parameter is a subtype of the first's.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, probably better to use compareTypes.

!(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) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) {
related = Ternary.False;
}
if (!related) {
if (reportErrors) {
errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible,
Expand All @@ -14100,7 +14108,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
Expand Down Expand Up @@ -14131,7 +14139,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<T> { 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);
Expand Down Expand Up @@ -15439,7 +15447,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)) {
Expand Down Expand Up @@ -15481,7 +15489,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,
Expand Down Expand Up @@ -15826,7 +15834,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) {
Expand Down Expand Up @@ -16049,7 +16057,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);
relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers);
}

function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary {
Expand Down Expand Up @@ -16363,12 +16371,12 @@ namespace ts {
source = target;
target = temp;
}
const intersection = isIntersectionConstituent ? "&" : "";
const delimiter = isIntersectionConstituent ? ";" : ",";
if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) {
const typeParameters: Type[] = [];
return getTypeReferenceId(<TypeReference>source, typeParameters) + "," + getTypeReferenceId(<TypeReference>target, typeParameters) + intersection;
return getTypeReferenceId(<TypeReference>source, typeParameters) + delimiter + getTypeReferenceId(<TypeReference>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
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ResolvedTypeReferenceDirective | undefined>;
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/executeCommandLine/executeCommandLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1919,6 +1919,7 @@ declare namespace ts {
assignable: number;
identity: number;
subtype: number;
strictSubtype: number;
};
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
isSourceFileDefaultLibrary(file: SourceFile): boolean;
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1919,6 +1919,7 @@ declare namespace ts {
assignable: number;
identity: number;
subtype: number;
strictSubtype: number;
};
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
isSourceFileDefaultLibrary(file: SourceFile): boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -75,16 +75,16 @@ 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
>(x: Object) => { } : (x: Object) => void
>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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function f4() {
}

function f5() {
>f5 : () => Object
>f5 : () => Object | 1

return 1;
>1 : 1
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -566,16 +566,16 @@ function f20<T extends Number>(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
Expand Down
Loading