Skip to content
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

No contextual typing from return types for boolean literals deeper in instantiables #59898

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
24 changes: 14 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32065,8 +32065,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// If the given contextual type contains instantiable types and if a mapper representing
// return type inferences is available, instantiate those types using that mapper.
function instantiateContextualType(contextualType: Type | undefined, node: Node, contextFlags: ContextFlags | undefined): Type | undefined {
if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) {
const inferenceContext = getInferenceContext(node);
if (!contextualType) {
return;
}
let inferenceContext: InferenceContext | undefined;
if (maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) {
inferenceContext = getInferenceContext(node);
// If no inferences have been made, and none of the type parameters for which we are inferring
// specify default types, nothing is gained from instantiating as type parameters would just be
// replaced with their constraints similar to the apparent type.
Expand All @@ -32077,16 +32081,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
if (inferenceContext?.returnMapper) {
// For other purposes (e.g. determining whether to produce literal types) we only
// incorporate inferences made from the return type in a function call. We remove
// the 'boolean' type from the contextual type such that contextually typed boolean
// literals actually end up widening to 'boolean' (see #48363).
const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper);
return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ?
filterType(type, t => t !== regularFalseType && t !== regularTrueType) :
type;
// incorporate inferences made from the return type in a function call.
contextualType = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper);
}
}
return contextualType;

// We remove the 'boolean' type from the contextual type from return types such that contextually typed boolean
// literals actually end up widening to 'boolean' (see #48363).
return contextualType.flags & TypeFlags.Union && (inferenceContext ??= getInferenceContext(node))?.returnMapper && containsType((contextualType as UnionType).types, regularFalseType) && containsType((contextualType as UnionType).types, regularTrueType) ?
filterType(contextualType, t => t !== regularFalseType && t !== regularTrueType) :
contextualType;
}

// This function is similar to instantiateType, except that (a) it only instantiates types that
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/arrayLiteralInference.types
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ let b1: { x: boolean }[] = foo({ x: true }, { x: false });
> : ^^^^^ ^^^^^
>x : boolean
> : ^^^^^^^
>foo({ x: true }, { x: false }) : ({ x: true; } | { x: false; })[]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>foo({ x: true }, { x: false }) : { x: boolean; }[]
> : ^^^^^^^^^^^^^^^^^
>foo : <T>(...args: T[]) => T[]
> : ^ ^^^^^ ^^ ^^^^^
>{ x: true } : { x: true; }
Expand All @@ -210,8 +210,8 @@ let b1: { x: boolean }[] = foo({ x: true }, { x: false });
let b2: boolean[][] = foo([true], [false]);
>b2 : boolean[][]
> : ^^^^^^^^^^^
>foo([true], [false]) : (true[] | false[])[]
> : ^^^^^^^^^^^^^^^^^^^^
>foo([true], [false]) : boolean[][]
> : ^^^^^^^^^^^
>foo : <T>(...args: T[]) => T[]
> : ^ ^^^^^ ^^ ^^^^^
>[true] : true[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
contextuallyTypedBooleanLiterals.ts(32,7): error TS2322: Type 'Box<{ prop: false; other: number; }>' is not assignable to type 'Box<{ prop: true; other: string; } | { prop: false; other: number; }>'.
Types of property 'set' are incompatible.
Type '(value: { prop: false; other: number; }) => void' is not assignable to type '(value: { prop: true; other: string; } | { prop: false; other: number; }) => void'.
Types of parameters 'value' and 'value' are incompatible.
Type '{ prop: true; other: string; } | { prop: false; other: number; }' is not assignable to type '{ prop: false; other: number; }'.
Type '{ prop: true; other: string; }' is not assignable to type '{ prop: false; other: number; }'.
Types of property 'prop' are incompatible.
Type 'true' is not assignable to type 'false'.


==== contextuallyTypedBooleanLiterals.ts (1 errors) ====
// Repro from #48363

type Box<T> = {
get: () => T;
set: (value: T) => void;
};

declare function box<T>(value: T): Box<T>;

const bn1 = box(0); // Box<number>
const bn2: Box<number> = box(0); // Ok, Box<number>

const bb1 = box(false); // Box<boolean>
const bb2: Box<boolean> = box(false); // Ok, Box<boolean>

// https://github.com/microsoft/TypeScript/issues/59754

const bn3 = box({ prop: 0 }); // Box<{ prop: number }>
const bn4: Box<{ prop: number }> = box({ prop: 0 }); // Ok, Box<{ prop: number }>

const bb3 = box({ prop: false }); // Box<boolean>
const bb4: Box<{ prop: boolean }> = box({ prop: false }); // Ok, Box<{ prop: boolean }>

const bn5 = box([0]); // Box<[number]>
const bn6: Box<[number]> = box([0]); // Ok, Box<[number]>

const bb5 = box([false]); // Box<[boolean]>
const bb6: Box<[boolean]> = box([false]); // Ok, Box<[boolean]>

const bb7: Box<{ deep: { prop: boolean } }> = box({ deep: { prop: false } }); // Ok, Box<{ deep: { prop: boolean } }>

const bb8: Box<{ prop: true; other: string } | { prop: false; other: number }> = box({ prop: false, other: 0 }); // Error (T is invariant), Box<{ prop: false; other: number }>
~~~
!!! error TS2322: Type 'Box<{ prop: false; other: number; }>' is not assignable to type 'Box<{ prop: true; other: string; } | { prop: false; other: number; }>'.
!!! error TS2322: Types of property 'set' are incompatible.
!!! error TS2322: Type '(value: { prop: false; other: number; }) => void' is not assignable to type '(value: { prop: true; other: string; } | { prop: false; other: number; }) => void'.
!!! error TS2322: Types of parameters 'value' and 'value' are incompatible.
!!! error TS2322: Type '{ prop: true; other: string; } | { prop: false; other: number; }' is not assignable to type '{ prop: false; other: number; }'.
!!! error TS2322: Type '{ prop: true; other: string; }' is not assignable to type '{ prop: false; other: number; }'.
!!! error TS2322: Types of property 'prop' are incompatible.
!!! error TS2322: Type 'true' is not assignable to type 'false'.

const bb9: Box<false> = box(false); // Ok, Box<false>

const bb10: Box<{ prop: false }> = box({ prop: false }); // Ok, Box<{ prop: false }>

type Box2<T> = {
get: () => T;
};

const bb11: Box2<{ prop: true; other: string } | { prop: false; other: number }> = box2({ prop: false, other: 0 }); // Ok, Box2<{ prop: false; other: number }>

declare function box2<T>(value: T): Box2<T>;

// Repro from #48150

interface Observable<T>
{
(): T;
(value: T): any;
}

declare function observable<T>(value: T): Observable<T>;

const x: Observable<boolean> = observable(false);


106 changes: 97 additions & 9 deletions tests/baselines/reference/contextuallyTypedBooleanLiterals.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,47 @@
// Repro from #48363

type Box<T> = {
get: () => T,
set: (value: T) => void
}
get: () => T;
set: (value: T) => void;
};

declare function box<T>(value: T): Box<T>;

const bn1 = box(0); // Box<number>
const bn2: Box<number> = box(0); // Ok
const bn1 = box(0); // Box<number>
const bn2: Box<number> = box(0); // Ok, Box<number>

const bb1 = box(false); // Box<boolean>
const bb2: Box<boolean> = box(false); // Ok, Box<boolean>

// https://github.com/microsoft/TypeScript/issues/59754

const bn3 = box({ prop: 0 }); // Box<{ prop: number }>
const bn4: Box<{ prop: number }> = box({ prop: 0 }); // Ok, Box<{ prop: number }>

const bb3 = box({ prop: false }); // Box<boolean>
const bb4: Box<{ prop: boolean }> = box({ prop: false }); // Ok, Box<{ prop: boolean }>

const bn5 = box([0]); // Box<[number]>
const bn6: Box<[number]> = box([0]); // Ok, Box<[number]>

const bb5 = box([false]); // Box<[boolean]>
const bb6: Box<[boolean]> = box([false]); // Ok, Box<[boolean]>

const bb7: Box<{ deep: { prop: boolean } }> = box({ deep: { prop: false } }); // Ok, Box<{ deep: { prop: boolean } }>

const bb1 = box(false); // Box<boolean>
const bb2: Box<boolean> = box(false); // Error, box<false> not assignable to Box<boolean>
const bb8: Box<{ prop: true; other: string } | { prop: false; other: number }> = box({ prop: false, other: 0 }); // Error (T is invariant), Box<{ prop: false; other: number }>

const bb9: Box<false> = box(false); // Ok, Box<false>

const bb10: Box<{ prop: false }> = box({ prop: false }); // Ok, Box<{ prop: false }>

type Box2<T> = {
get: () => T;
};

const bb11: Box2<{ prop: true; other: string } | { prop: false; other: number }> = box2({ prop: false, other: 0 }); // Ok, Box2<{ prop: false; other: number }>

declare function box2<T>(value: T): Box2<T>;

// Repro from #48150

Expand All @@ -27,15 +57,30 @@ interface Observable<T>
declare function observable<T>(value: T): Observable<T>;

const x: Observable<boolean> = observable(false);



//// [contextuallyTypedBooleanLiterals.js]
"use strict";
// Repro from #48363
var bn1 = box(0); // Box<number>
var bn2 = box(0); // Ok
var bn2 = box(0); // Ok, Box<number>
var bb1 = box(false); // Box<boolean>
var bb2 = box(false); // Error, box<false> not assignable to Box<boolean>
var bb2 = box(false); // Ok, Box<boolean>
// https://github.com/microsoft/TypeScript/issues/59754
var bn3 = box({ prop: 0 }); // Box<{ prop: number }>
var bn4 = box({ prop: 0 }); // Ok, Box<{ prop: number }>
var bb3 = box({ prop: false }); // Box<boolean>
var bb4 = box({ prop: false }); // Ok, Box<{ prop: boolean }>
var bn5 = box([0]); // Box<[number]>
var bn6 = box([0]); // Ok, Box<[number]>
var bb5 = box([false]); // Box<[boolean]>
var bb6 = box([false]); // Ok, Box<[boolean]>
var bb7 = box({ deep: { prop: false } }); // Ok, Box<{ deep: { prop: boolean } }>
var bb8 = box({ prop: false, other: 0 }); // Error (T is invariant), Box<{ prop: false; other: number }>
var bb9 = box(false); // Ok, Box<false>
var bb10 = box({ prop: false }); // Ok, Box<{ prop: false }>
var bb11 = box2({ prop: false, other: 0 }); // Ok, Box2<{ prop: false; other: number }>
var x = observable(false);


Expand All @@ -49,6 +94,49 @@ declare const bn1: Box<number>;
declare const bn2: Box<number>;
declare const bb1: Box<boolean>;
declare const bb2: Box<boolean>;
declare const bn3: Box<{
prop: number;
}>;
declare const bn4: Box<{
prop: number;
}>;
declare const bb3: Box<{
prop: boolean;
}>;
declare const bb4: Box<{
prop: boolean;
}>;
declare const bn5: Box<number[]>;
declare const bn6: Box<[number]>;
declare const bb5: Box<boolean[]>;
declare const bb6: Box<[boolean]>;
declare const bb7: Box<{
deep: {
prop: boolean;
};
}>;
declare const bb8: Box<{
prop: true;
other: string;
} | {
prop: false;
other: number;
}>;
declare const bb9: Box<false>;
declare const bb10: Box<{
prop: false;
}>;
type Box2<T> = {
get: () => T;
};
declare const bb11: Box2<{
prop: true;
other: string;
} | {
prop: false;
other: number;
}>;
declare function box2<T>(value: T): Box2<T>;
interface Observable<T> {
(): T;
(value: T): any;
Expand Down
Loading