Skip to content

Improve logic that chooses co- vs. contra-variant inferences #52123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24504,11 +24504,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (signature) {
const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined;
if (inference.contraCandidates) {
// If we have both co- and contra-variant inferences, we prefer the contra-variant inference
// unless the co-variant inference is a subtype of some contra-variant inference and not 'never'.
inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) &&
some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) ?
inferredCovariantType : getContravariantInference(inference);
// If we have both co- and contra-variant inferences, we use the co-variant inference if it is not 'never',
// it is a subtype of some contra-variant inference, and no other type parameter is constrained to this type
// parameter and has inferences that would conflict. Otherwise, we use the contra-variant inference.
const useCovariantType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) &&
some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) &&
every(context.inferences, other => other === inference ||
getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter ||
every(other.candidates, t => isTypeSubtypeOf(t, inferredCovariantType)));
inferredType = useCovariantType ? inferredCovariantType : getContravariantInference(inference);
}
else if (inferredCovariantType) {
inferredType = inferredCovariantType;
Expand Down
140 changes: 140 additions & 0 deletions tests/baselines/reference/coAndContraVariantInferences2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//// [coAndContraVariantInferences2.ts]
interface A { a: string }
interface B extends A { b: string }
interface C extends A { c: string }

declare function cast<T, U extends T>(x: T, test: (x: T) => x is U): U;

declare function isC(x: A): x is C;

function f1(a: A, b: B) {
const x1 = cast(a, isC); // cast<A, C>
const x2 = cast(b, isC); // cast<A, C>
}

declare function useA(a: A): void;

declare function consume<T, U extends T>(t: T, u: U, f: (x: T) => void): void;

function f2(b: B, c: C) {
consume(b, c, useA); // consume<A, C>
consume(c, b, useA); // consume<A, B>
consume(b, b, useA); // consume<B, B>
consume(c, c, useA); // consume<C, C>
}

// Repro from #52111

enum SyntaxKind {
Block,
Identifier,
CaseClause,
FunctionExpression,
FunctionDeclaration,
}

interface Node { kind: SyntaxKind; }
interface Expression extends Node { _expressionBrand: any; }
interface Declaration extends Node { _declarationBrand: any; }
interface Block extends Node { kind: SyntaxKind.Block; }
interface Identifier extends Expression, Declaration { kind: SyntaxKind.Identifier; }
interface CaseClause extends Node { kind: SyntaxKind.CaseClause; }
interface FunctionDeclaration extends Declaration { kind: SyntaxKind.FunctionDeclaration; }

type HasLocals = Block | FunctionDeclaration;
declare function canHaveLocals(node: Node): node is HasLocals;

declare function assertNode<T extends Node, U extends T>(node: T | undefined, test: (node: T) => node is U): asserts node is U;
declare function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined): void;

function foo(node: FunctionDeclaration | CaseClause) {
assertNode(node, canHaveLocals); // assertNode<Node, HasLocals>
node; // FunctionDeclaration
}

declare function isExpression(node: Node): node is Expression;

declare function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut;

function bar(node: Identifier | FunctionDeclaration) {
const a = tryCast(node, isExpression); // tryCast<Expression, Node>
}

// Repro from #49924

const enum SyntaxKind1 {
ClassExpression,
ClassStatement,
}

interface Node1 {
kind: SyntaxKind1;
}

interface Statement1 extends Node1 {
_statementBrand: any;
}

interface ClassExpression1 extends Node1 {
kind: SyntaxKind1.ClassExpression;
}

interface ClassStatement1 extends Statement1 {
kind: SyntaxKind1.ClassStatement;
}

type ClassLike1 = ClassExpression1 | ClassStatement1;

declare function isClassLike(node: Node1): node is ClassLike1;

declare const statement: Statement1 | undefined;

const maybeClassStatement = tryCast(statement, isClassLike); // ClassLike1

// Repro from #49924

interface TypeNode extends Node {
typeInfo: string;
}

interface NodeArray<T extends Node> extends Array<T> {
someProp: string;
}

declare function isNodeArray<T extends Node>(array: readonly T[]): array is NodeArray<T>;

declare const types: readonly TypeNode[];

const x = tryCast(types, isNodeArray); // NodeAray<TypeNode>


//// [coAndContraVariantInferences2.js]
"use strict";
function f1(a, b) {
var x1 = cast(a, isC); // cast<A, C>
var x2 = cast(b, isC); // cast<A, C>
}
function f2(b, c) {
consume(b, c, useA); // consume<A, C>
consume(c, b, useA); // consume<A, B>
consume(b, b, useA); // consume<B, B>
consume(c, c, useA); // consume<C, C>
}
// Repro from #52111
var SyntaxKind;
(function (SyntaxKind) {
SyntaxKind[SyntaxKind["Block"] = 0] = "Block";
SyntaxKind[SyntaxKind["Identifier"] = 1] = "Identifier";
SyntaxKind[SyntaxKind["CaseClause"] = 2] = "CaseClause";
SyntaxKind[SyntaxKind["FunctionExpression"] = 3] = "FunctionExpression";
SyntaxKind[SyntaxKind["FunctionDeclaration"] = 4] = "FunctionDeclaration";
})(SyntaxKind || (SyntaxKind = {}));
function foo(node) {
assertNode(node, canHaveLocals); // assertNode<Node, HasLocals>
node; // FunctionDeclaration
}
function bar(node) {
var a = tryCast(node, isExpression); // tryCast<Expression, Node>
}
var maybeClassStatement = tryCast(statement, isClassLike); // ClassLike1
var x = tryCast(types, isNodeArray); // NodeAray<TypeNode>
Loading