Skip to content

More prolific literal types #19587

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

Closed
wants to merge 16 commits into from
Closed
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
54 changes: 32 additions & 22 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4268,9 +4268,10 @@ namespace ts {
if (strictNullChecks && declaration.initializer && !(getFalsyFlags(checkExpressionCached(declaration.initializer)) & TypeFlags.Undefined)) {
type = getTypeWithFacts(type, TypeFacts.NEUndefined);
}
return declaration.initializer ?
type = declaration.initializer ?
getUnionType([type, checkExpressionCached(declaration.initializer)], /*subtypeReduction*/ true) :
type;
return shouldUseLiteralType(declaration) ? type : getWidenedLiteralType(type);
}

function getTypeForDeclarationFromJSDocComment(declaration: Node) {
Expand Down Expand Up @@ -9030,6 +9031,11 @@ namespace ts {
* * Ternary.False if they are not related.
*/
function isRelatedTo(source: Type, target: Type, reportErrors?: boolean, headMessage?: DiagnosticMessage): Ternary {
return (relation === comparableRelation && isRelatedToMonodirectional(target, source, /*reportErrors*/ false, headMessage))
|| isRelatedToMonodirectional(source, target, reportErrors, headMessage);
}

function isRelatedToMonodirectional(source: Type, target: Type, reportErrors?: boolean, headMessage?: DiagnosticMessage): Ternary {
if (source.flags & TypeFlags.StringOrNumberLiteral && source.flags & TypeFlags.FreshLiteral) {
source = (<LiteralType>source).regularType;
}
Expand Down Expand Up @@ -9236,14 +9242,14 @@ namespace ts {
return Ternary.True;
}
for (const type of targetTypes) {
const related = isRelatedTo(source, type, /*reportErrors*/ false);
const related = isRelatedToMonodirectional(source, type, /*reportErrors*/ false);
if (related) {
return related;
}
}
if (reportErrors) {
const discriminantType = findMatchingDiscriminantType(source, target);
isRelatedTo(source, discriminantType || targetTypes[targetTypes.length - 1], /*reportErrors*/ true);
isRelatedToMonodirectional(source, discriminantType || targetTypes[targetTypes.length - 1], /*reportErrors*/ true);
}
return Ternary.False;
}
Expand Down Expand Up @@ -9273,7 +9279,7 @@ namespace ts {
let result = Ternary.True;
const targetTypes = target.types;
for (const targetType of targetTypes) {
const related = isRelatedTo(source, targetType, reportErrors);
const related = isRelatedToMonodirectional(source, targetType, reportErrors);
if (!related) {
return Ternary.False;
}
Expand All @@ -9289,7 +9295,7 @@ namespace ts {
}
const len = sourceTypes.length;
for (let i = 0; i < len; i++) {
const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1);
const related = isRelatedToMonodirectional(sourceTypes[i], target, reportErrors && i === len - 1);
if (related) {
return related;
}
Expand All @@ -9301,7 +9307,7 @@ namespace ts {
let result = Ternary.True;
const sourceTypes = source.types;
for (const sourceType of sourceTypes) {
const related = isRelatedTo(sourceType, target, reportErrors);
const related = isRelatedToMonodirectional(sourceType, target, reportErrors);
if (!related) {
return Ternary.False;
}
Expand Down Expand Up @@ -9491,7 +9497,7 @@ namespace ts {
}
// Report constraint errors only if the constraint is not the empty object type
const reportConstraintErrors = reportErrors && constraint !== emptyObjectType;
if (result = isRelatedTo(constraint, target, reportConstraintErrors)) {
if (result = isRelatedToMonodirectional(constraint, target, reportConstraintErrors)) {
Copy link
Member

Choose a reason for hiding this comment

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

why only here and in the union/intersection type handling? why does it become bidirectional everywhere else?

errorInfo = saveErrorInfo;
return result;
}
Expand Down Expand Up @@ -11758,7 +11764,7 @@ namespace ts {
}
}
}
return mappedTypes ? getUnionType(mappedTypes) : mappedType;
return mappedTypes ? getUnionType(mappedTypes, /*subtypeReduction*/ undefined, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : mappedType;
}

function extractTypesOfKind(type: Type, kind: TypeFlags) {
Expand Down Expand Up @@ -17350,7 +17356,7 @@ namespace ts {
if (isUnitType(type) &&
!(contextualSignature &&
isLiteralContextualType(
contextualSignature === getSignatureFromDeclaration(func) ? type : getReturnTypeOfSignature(contextualSignature)))) {
contextualSignature === getSignatureFromDeclaration(func) ? type : getReturnTypeOfSignature(contextualSignature), type))) {
type = getWidenedLiteralType(type);
}

Expand Down Expand Up @@ -17849,7 +17855,7 @@ namespace ts {
// The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type,
// and the right operand to be of type Any, an object type, or a type parameter type.
// The result is always of the Boolean primitive type.
if (!(isTypeComparableTo(leftType, stringType) || isTypeAssignableToKind(leftType, TypeFlags.NumberLike | TypeFlags.ESSymbol))) {
if (!isTypeAssignableToKind(leftType, TypeFlags.NumberLike | TypeFlags.StringLike | TypeFlags.ESSymbol)) {
error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol);
}
if (!isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.TypeVariable)) {
Expand Down Expand Up @@ -18434,24 +18440,28 @@ namespace ts {

function checkDeclarationInitializer(declaration: VariableLikeDeclaration) {
const type = getTypeOfExpression(declaration.initializer, /*cache*/ true);
return shouldUseLiteralType(declaration) ? type : getWidenedLiteralType(type);
}

function shouldUseLiteralType(declaration: VariableLikeDeclaration) {
return getCombinedNodeFlags(declaration) & NodeFlags.Const ||
getCombinedModifierFlags(declaration) & ModifierFlags.Readonly && !isParameterPropertyDeclaration(declaration) ||
isTypeAssertion(declaration.initializer) ? type : getWidenedLiteralType(type);
(declaration.initializer && isTypeAssertion(declaration.initializer));
}

function isLiteralContextualType(contextualType: Type) {
function isLiteralContextualType(contextualType: Type, candidateLiteral: Type): boolean {
Copy link
Member

Choose a reason for hiding this comment

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

Consider flipping the parameters and changing the name to canBeContextuallyTypedAsLiteral (or something similar; probably the parameter names would need to change too.)

Copy link
Member Author

Choose a reason for hiding this comment

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

@rbuckton 's dynamic names PR already includes both of those things, actually.

if (contextualType) {
if (contextualType.flags & TypeFlags.UnionOrIntersection) {
return some((contextualType as UnionOrIntersectionType).types, t => isLiteralContextualType(t, candidateLiteral));
}
if (contextualType.flags & TypeFlags.TypeVariable) {
const constraint = getBaseConstraintOfType(contextualType) || emptyObjectType;
// If the type parameter is constrained to the base primitive type we're checking for,
// consider this a literal context. For example, given a type parameter 'T extends string',
// this causes us to infer string literal types for T.
if (constraint.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.Boolean | TypeFlags.Enum)) {
return true;
}
contextualType = constraint;
return isLiteralContextualType(constraint, candidateLiteral);
}
return maybeTypeOfKind(contextualType, (TypeFlags.Literal | TypeFlags.Index));
// No need to `maybeTypeOfKind` on the contextual type, as it can't be a union, _however_, `candidateLiteral` might still be one!
return !!(((contextualType.flags & TypeFlags.StringLike) && maybeTypeOfKind(candidateLiteral, TypeFlags.StringLike)) ||
((contextualType.flags & TypeFlags.NumberLike) && maybeTypeOfKind(candidateLiteral, TypeFlags.NumberLike)) ||
((contextualType.flags & TypeFlags.BooleanLike) && maybeTypeOfKind(candidateLiteral, TypeFlags.BooleanLike)));
}
return false;
}
Expand All @@ -18461,8 +18471,8 @@ namespace ts {
contextualType = getContextualType(node);
}
const type = checkExpression(node, checkMode);
const shouldWiden = isTypeAssertion(node) || isLiteralContextualType(contextualType);
return shouldWiden ? type : getWidenedLiteralType(type);
const shouldNotWiden = isTypeAssertion(node) || isLiteralContextualType(contextualType, type);
return shouldNotWiden ? type : getWidenedLiteralType(type);
}

function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Point {
static Origin(): Point { return { x: 0, y: 0 }; } // unexpected error here bug 840246
>Origin : () => Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down Expand Up @@ -38,7 +38,7 @@ module A {
static Origin(): Point { return { x: 0, y: 0 }; } // unexpected error here bug 840246
>Origin : () => Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Point {
static Origin(): Point { return { x: 0, y: 0 }; }
>Origin : () => Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down Expand Up @@ -38,7 +38,7 @@ module A {
static Origin(): Point { return { x: 0, y: 0 }; }
>Origin : () => Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Point {
static Origin: Point = { x: 0, y: 0 };
>Origin : Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down Expand Up @@ -38,7 +38,7 @@ module A {
static Origin: Point = { x: 0, y: 0 };
>Origin : Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Point {
static Origin: Point = { x: 0, y: 0 };
>Origin : Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down Expand Up @@ -38,7 +38,7 @@ module A {
static Origin: Point = { x: 0, y: 0 };
>Origin : Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ES5For-of30.types
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ var a: string, b: number;

var tuple: [number, string] = [2, "3"];
>tuple : [number, string]
>[2, "3"] : [number, string]
>[2, "3"] : [2, "3"]
>2 : 2
>"3" : "3"

Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ES5For-ofTypeCheck3.types
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
=== tests/cases/conformance/statements/for-ofStatements/ES5For-ofTypeCheck3.ts ===
var tuple: [string, number] = ["", 0];
>tuple : [string, number]
>["", 0] : [string, number]
>["", 0] : ["", 0]
>"" : ""
>0 : 0

Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/ES5for-of32.types
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ for (let num of array) {
>0 : 0

array = [4,5,6]
>array = [4,5,6] : number[]
>array = [4,5,6] : (4 | 5 | 6)[]
>array : number[]
>[4,5,6] : number[]
>[4,5,6] : (4 | 5 | 6)[]
>4 : 4
>5 : 5
>6 : 6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module A {
export var Origin: Point = { x: 0, y: 0 };
>Origin : Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand All @@ -32,7 +32,7 @@ module A {
export var Origin3d: Point3d = { x: 0, y: 0, z: 0 };
>Origin3d : Point3d
>Point3d : Point3d
>{ x: 0, y: 0, z: 0 } : { x: number; y: number; z: number; }
>{ x: 0, y: 0, z: 0 } : { x: 0; y: 0; z: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module A {
export var Origin: Point = { x: 0, y: 0 };
>Origin : Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand All @@ -32,7 +32,7 @@ module A {
export var Origin3d: Point3d = { x: 0, y: 0, z: 0 };
>Origin3d : Point3d
>Point3d : Point3d
>{ x: 0, y: 0, z: 0 } : { x: number; y: number; z: number; }
>{ x: 0, y: 0, z: 0 } : { x: 0; y: 0; z: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module A {
return new Line({ x: 0, y: 0 }, p);
>new Line({ x: 0, y: 0 }, p) : Line
>Line : typeof Line
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module A {
return new Line({ x: 0, y: 0 }, p);
>new Line({ x: 0, y: 0 }, p) : Line
>Line : typeof Line
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module A {
return new Line({ x: 0, y: 0 }, p);
>new Line({ x: 0, y: 0 }, p) : Line
>Line : typeof Line
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module A {
export var Origin: Point = { x: 0, y: 0 };
>Origin : Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand All @@ -32,7 +32,7 @@ module A {
export var Origin3d: Point3d = { x: 0, y: 0, z: 0 };
>Origin3d : Point3d
>Point3d : Point3d
>{ x: 0, y: 0, z: 0 } : { x: number; y: number; z: number; }
>{ x: 0, y: 0, z: 0 } : { x: 0; y: 0; z: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module A {
export var Origin: Point = { x: 0, y: 0 };
>Origin : Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand All @@ -32,7 +32,7 @@ module A {
export var Origin3d: Point3d = { x: 0, y: 0, z: 0 };
>Origin3d : Point3d
>Point3d : Point3d
>{ x: 0, y: 0, z: 0 } : { x: number; y: number; z: number; }
>{ x: 0, y: 0, z: 0 } : { x: 0; y: 0; z: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ module A {
return new Line({ x: 0, y: 0 }, p);
>new Line({ x: 0, y: 0 }, p) : Line
>Line : typeof Line
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module A {
export var Origin: Point = { x: 0, y: 0 };
>Origin : Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module A {
export var Origin: Point = { x: 0, y: 0 };
>Origin : Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module A {
export var Origin: Point = { x: 0, y: 0 };
>Origin : Point
>Point : Point
>{ x: 0, y: 0 } : { x: number; y: number; }
>{ x: 0, y: 0 } : { x: 0; y: 0; }
>x : number
>0 : 0
>y : number
Expand All @@ -34,7 +34,7 @@ module A {
export var Origin3d: Point3d = { x: 0, y: 0, z: 0 };
>Origin3d : Point3d
>Point3d : Point3d
>{ x: 0, y: 0, z: 0 } : { x: number; y: number; z: number; }
>{ x: 0, y: 0, z: 0 } : { x: 0; y: 0; z: 0; }
>x : number
>0 : 0
>y : number
Expand Down
Loading