Skip to content

Commit

Permalink
Improve logic that chooses co- vs. contra-variant inferences (#52123)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahejlsberg authored Jan 6, 2023
1 parent 4b52d3a commit fc85386
Show file tree
Hide file tree
Showing 5 changed files with 888 additions and 5 deletions.
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

0 comments on commit fc85386

Please sign in to comment.