Skip to content
Closed
62 changes: 62 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,7 @@ import {
ObjectLiteralElementLike,
ObjectLiteralExpression,
ObjectType,
OmittedExpression,
OptionalChain,
OptionalTypeNode,
or,
Expand Down Expand Up @@ -2241,6 +2242,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
var potentialReflectCollisions: Node[] = [];
var potentialUnusedRenamedBindingElementsInTypes: BindingElement[] = [];
var awaitedTypeStack: number[] = [];
var ambiguousVariableDeclarationsWhenWidened: [node: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement, type: Type, widenedLiteralType: Type][] = [];

var diagnostics = createDiagnosticCollection();
var suggestionDiagnostics = createDiagnosticCollection();
Expand Down Expand Up @@ -43141,6 +43143,40 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
checkCollisionsForDeclarationName(node, node.name);
}

// TODO(jakebailey): copy pasted from declaration.ts; improve
type CanHaveLiteralInitializer = VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration;
function canHaveLiteralInitializer(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
return !hasEffectiveModifier(node, ModifierFlags.Private);
case SyntaxKind.Parameter:
case SyntaxKind.VariableDeclaration:
return true;
}
return false;
}

function shouldPrintWithInitializer(node: Node) {
return canHaveLiteralInitializer(node) && isLiteralConstDeclaration(node as CanHaveLiteralInitializer); // TODO: Make safe
}

function isPrivateDeclaration(node: Node) {
return hasEffectiveModifier(node, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(node);
}

if (
getEmitDeclarations(compilerOptions)
&& !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature))
&& !shouldPrintWithInitializer(node)
&& !isPrivateDeclaration(node)
) {
const widenedLiteralType = getWidenedLiteralType(type);
if (!isTypeIdenticalTo(type, widenedLiteralType)) {
ambiguousVariableDeclarationsWhenWidened.push([node, type, widenedLiteralType]);
}
}
}

function errorNextVariableOrPropertyDeclarationMustHaveSameType(firstDeclaration: Declaration | undefined, firstType: Type, nextDeclaration: Declaration, nextType: Type): void {
Expand Down Expand Up @@ -47243,6 +47279,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
clear(potentialWeakMapSetCollisions);
clear(potentialReflectCollisions);
clear(potentialUnusedRenamedBindingElementsInTypes);
clear(ambiguousVariableDeclarationsWhenWidened);

forEach(node.statements, checkSourceElement);
checkSourceElement(node.endOfFileToken);
Expand All @@ -47267,6 +47304,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
});

addLazyDiagnostic(() => {
function isVisibleExternally(elem: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | OmittedExpression): boolean {
if (isOmittedExpression(elem)) {
return false;
}
if (isBindingPattern(elem.name)) {
return some(elem.name.elements, isVisibleExternally);
}
else {
return isDeclarationVisible(elem);
}
}

for (const [node, type, widenedLiteralType] of ambiguousVariableDeclarationsWhenWidened) {
if (isVisibleExternally(node)) {
error(node.name, Diagnostics.The_type_of_this_declaration_is_ambiguous_and_may_be_observed_as_either_0_or_1, typeToString(widenedLiteralType), typeToString(type));
}
}
});

if (isExternalOrCommonJsModule(node)) {
checkExternalModuleExports(node);
}
Expand All @@ -47291,6 +47348,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
clear(potentialReflectCollisions);
}

if (ambiguousVariableDeclarationsWhenWidened.length) {
// forEach(potentialAmbiguousDeclarationsWhenWidened, checkAmbiguousDeclaration);
clear(ambiguousVariableDeclarationsWhenWidened);
}

links.flags |= NodeCheckFlags.TypeChecked;
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3755,6 +3755,10 @@
"category": "Error",
"code": 2868
},
"The type of this declaration is ambiguous and may be observed as either '{0}' or '{1}'.": {
"category": "Error",
"code": 2869
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
30 changes: 30 additions & 0 deletions tests/baselines/reference/ambientConstLiterals.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
ambientConstLiterals.ts(20,7): error TS2869: The type of this declaration is ambiguous and may be observed as either 'string' or '"abc" | "def"'.
ambientConstLiterals.ts(21,7): error TS2869: The type of this declaration is ambiguous and may be observed as either 'number' or '123 | 456'.


==== ambientConstLiterals.ts (2 errors) ====
function f<T>(x: T): T {
return x;
}

enum E { A, B, C, "non identifier" }

const c1 = "abc";
const c2 = 123;
const c3 = c1;
const c4 = c2;
const c5 = f(123);
const c6 = f(-123);
const c7 = true;
const c8 = E.A;
const c8b = E["non identifier"];
const c9 = { x: "abc" };
const c10 = [123];
const c11 = "abc" + "def";
const c12 = 123 + 456;
const c13 = Math.random() > 0.5 ? "abc" : "def";
~~~
!!! error TS2869: The type of this declaration is ambiguous and may be observed as either 'string' or '"abc" | "def"'.
const c14 = Math.random() > 0.5 ? 123 : 456;
~~~
!!! error TS2869: The type of this declaration is ambiguous and may be observed as either 'number' or '123 | 456'.
23 changes: 23 additions & 0 deletions tests/baselines/reference/ambiguousLiteralWideningEmit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//// [tests/cases/compiler/ambiguousLiteralWideningEmit.ts] ////

//// [ambiguousLiteralWideningEmit.ts]
declare function pad(n: number | string): string;

export default (dateString: string, type: 'date' | 'month' | 'year'): string => {
const [year, month = 1, date = 1] = dateString.split('-')
return `${year}-${pad(month)}-${pad(date)}`.substr(0, { date: 10, month: 7, year: 4 }[type])
}


//// [ambiguousLiteralWideningEmit.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = (function (dateString, type) {
var _a = dateString.split('-'), year = _a[0], _b = _a[1], month = _b === void 0 ? 1 : _b, _c = _a[2], date = _c === void 0 ? 1 : _c;
return "".concat(year, "-").concat(pad(month), "-").concat(pad(date)).substr(0, { date: 10, month: 7, year: 4 }[type]);
});


//// [ambiguousLiteralWideningEmit.d.ts]
declare const _default: (dateString: string, type: 'date' | 'month' | 'year') => string;
export default _default;
33 changes: 33 additions & 0 deletions tests/baselines/reference/ambiguousLiteralWideningEmit.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//// [tests/cases/compiler/ambiguousLiteralWideningEmit.ts] ////

=== ambiguousLiteralWideningEmit.ts ===
declare function pad(n: number | string): string;
>pad : Symbol(pad, Decl(ambiguousLiteralWideningEmit.ts, 0, 0))
>n : Symbol(n, Decl(ambiguousLiteralWideningEmit.ts, 0, 21))

export default (dateString: string, type: 'date' | 'month' | 'year'): string => {
>dateString : Symbol(dateString, Decl(ambiguousLiteralWideningEmit.ts, 2, 16))
>type : Symbol(type, Decl(ambiguousLiteralWideningEmit.ts, 2, 35))

const [year, month = 1, date = 1] = dateString.split('-')
>year : Symbol(year, Decl(ambiguousLiteralWideningEmit.ts, 3, 11))
>month : Symbol(month, Decl(ambiguousLiteralWideningEmit.ts, 3, 16))
>date : Symbol(date, Decl(ambiguousLiteralWideningEmit.ts, 3, 27))
>dateString.split : Symbol(String.split, Decl(lib.es5.d.ts, --, --))
>dateString : Symbol(dateString, Decl(ambiguousLiteralWideningEmit.ts, 2, 16))
>split : Symbol(String.split, Decl(lib.es5.d.ts, --, --))

return `${year}-${pad(month)}-${pad(date)}`.substr(0, { date: 10, month: 7, year: 4 }[type])
>`${year}-${pad(month)}-${pad(date)}`.substr : Symbol(String.substr, Decl(lib.es5.d.ts, --, --))
>year : Symbol(year, Decl(ambiguousLiteralWideningEmit.ts, 3, 11))
>pad : Symbol(pad, Decl(ambiguousLiteralWideningEmit.ts, 0, 0))
>month : Symbol(month, Decl(ambiguousLiteralWideningEmit.ts, 3, 16))
>pad : Symbol(pad, Decl(ambiguousLiteralWideningEmit.ts, 0, 0))
>date : Symbol(date, Decl(ambiguousLiteralWideningEmit.ts, 3, 27))
>substr : Symbol(String.substr, Decl(lib.es5.d.ts, --, --))
>date : Symbol(date, Decl(ambiguousLiteralWideningEmit.ts, 4, 59))
>month : Symbol(month, Decl(ambiguousLiteralWideningEmit.ts, 4, 69))
>year : Symbol(year, Decl(ambiguousLiteralWideningEmit.ts, 4, 79))
>type : Symbol(type, Decl(ambiguousLiteralWideningEmit.ts, 2, 35))
}

48 changes: 48 additions & 0 deletions tests/baselines/reference/ambiguousLiteralWideningEmit.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//// [tests/cases/compiler/ambiguousLiteralWideningEmit.ts] ////

=== ambiguousLiteralWideningEmit.ts ===
declare function pad(n: number | string): string;
>pad : (n: number | string) => string
>n : string | number

export default (dateString: string, type: 'date' | 'month' | 'year'): string => {
>(dateString: string, type: 'date' | 'month' | 'year'): string => { const [year, month = 1, date = 1] = dateString.split('-') return `${year}-${pad(month)}-${pad(date)}`.substr(0, { date: 10, month: 7, year: 4 }[type])} : (dateString: string, type: 'date' | 'month' | 'year') => string
>dateString : string
>type : "date" | "month" | "year"

const [year, month = 1, date = 1] = dateString.split('-')
>year : string
>month : string | 1
>1 : 1
>date : string | 1
>1 : 1
>dateString.split('-') : string[]
>dateString.split : (separator: string | RegExp, limit?: number | undefined) => string[]
>dateString : string
>split : (separator: string | RegExp, limit?: number | undefined) => string[]
>'-' : "-"

return `${year}-${pad(month)}-${pad(date)}`.substr(0, { date: 10, month: 7, year: 4 }[type])
>`${year}-${pad(month)}-${pad(date)}`.substr(0, { date: 10, month: 7, year: 4 }[type]) : string
>`${year}-${pad(month)}-${pad(date)}`.substr : (from: number, length?: number | undefined) => string
>`${year}-${pad(month)}-${pad(date)}` : string
>year : string
>pad(month) : string
>pad : (n: string | number) => string
>month : string | 1
>pad(date) : string
>pad : (n: string | number) => string
>date : string | 1
>substr : (from: number, length?: number | undefined) => string
>0 : 0
>{ date: 10, month: 7, year: 4 }[type] : number
>{ date: 10, month: 7, year: 4 } : { date: number; month: number; year: number; }
>date : number
>10 : 10
>month : number
>7 : 7
>year : number
>4 : 4
>type : "date" | "month" | "year"
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
declarationEmitBindingPatterns2.ts(7,16): error TS2869: The type of this declaration is ambiguous and may be observed as either 'number' or '1 | 0'.


==== declarationEmitBindingPatterns2.ts (1 errors) ====
// https://github.com/microsoft/TypeScript/issues/55439

function foo(): { y: 1 } {
return { y: 1 };
}

export const { y = 0 } = foo();
~
!!! error TS2869: The type of this declaration is ambiguous and may be observed as either 'number' or '1 | 0'.

16 changes: 16 additions & 0 deletions tests/baselines/reference/declarationEmitBindingPatterns2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//// [tests/cases/compiler/declarationEmitBindingPatterns2.ts] ////

//// [declarationEmitBindingPatterns2.ts]
// https://github.com/microsoft/TypeScript/issues/55439

function foo(): { y: 1 } {
return { y: 1 };
}

export const { y = 0 } = foo();




//// [declarationEmitBindingPatterns2.d.ts]
export declare const y: number;
17 changes: 17 additions & 0 deletions tests/baselines/reference/declarationEmitBindingPatterns2.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//// [tests/cases/compiler/declarationEmitBindingPatterns2.ts] ////

=== declarationEmitBindingPatterns2.ts ===
// https://github.com/microsoft/TypeScript/issues/55439

function foo(): { y: 1 } {
>foo : Symbol(foo, Decl(declarationEmitBindingPatterns2.ts, 0, 0))
>y : Symbol(y, Decl(declarationEmitBindingPatterns2.ts, 2, 17))

return { y: 1 };
>y : Symbol(y, Decl(declarationEmitBindingPatterns2.ts, 3, 10))
}

export const { y = 0 } = foo();
>y : Symbol(y, Decl(declarationEmitBindingPatterns2.ts, 6, 14))
>foo : Symbol(foo, Decl(declarationEmitBindingPatterns2.ts, 0, 0))

21 changes: 21 additions & 0 deletions tests/baselines/reference/declarationEmitBindingPatterns2.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//// [tests/cases/compiler/declarationEmitBindingPatterns2.ts] ////

=== declarationEmitBindingPatterns2.ts ===
// https://github.com/microsoft/TypeScript/issues/55439

function foo(): { y: 1 } {
>foo : () => { y: 1;}
>y : 1

return { y: 1 };
>{ y: 1 } : { y: 1; }
>y : 1
>1 : 1
}

export const { y = 0 } = foo();
>y : 1 | 0
>0 : 0
>foo() : { y: 1; }
>foo : () => { y: 1; }

Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,53 @@ export declare const SpotifyAgeGroupEnum: {
"60-150": (typeof AgeGroups)["60-150"];
};
export {};


//// [DtsFileErrors]


declarationEmitSpreadStringlyKeyedEnum.d.ts(12,5): error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["0-17"]'.
declarationEmitSpreadStringlyKeyedEnum.d.ts(13,5): error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["18-22"]'.
declarationEmitSpreadStringlyKeyedEnum.d.ts(14,5): error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["23-27"]'.
declarationEmitSpreadStringlyKeyedEnum.d.ts(15,5): error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["28-34"]'.
declarationEmitSpreadStringlyKeyedEnum.d.ts(16,5): error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["35-44"]'.
declarationEmitSpreadStringlyKeyedEnum.d.ts(17,5): error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["45-59"]'.
declarationEmitSpreadStringlyKeyedEnum.d.ts(18,5): error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["60-150"]'.


==== declarationEmitSpreadStringlyKeyedEnum.d.ts (7 errors) ====
declare enum AgeGroups {
"0-17" = 0,
"18-22" = 1,
"23-27" = 2,
"28-34" = 3,
"35-44" = 4,
"45-59" = 5,
"60-150" = 6
}
export declare const SpotifyAgeGroupEnum: {
[x: number]: string;
"0-17": (typeof AgeGroups)["0-17"];
~~~~~~
!!! error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["0-17"]'.
"18-22": (typeof AgeGroups)["18-22"];
~~~~~~~
!!! error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["18-22"]'.
"23-27": (typeof AgeGroups)["23-27"];
~~~~~~~
!!! error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["23-27"]'.
"28-34": (typeof AgeGroups)["28-34"];
~~~~~~~
!!! error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["28-34"]'.
"35-44": (typeof AgeGroups)["35-44"];
~~~~~~~
!!! error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["35-44"]'.
"45-59": (typeof AgeGroups)["45-59"];
~~~~~~~
!!! error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["45-59"]'.
"60-150": (typeof AgeGroups)["60-150"];
~~~~~~~~
!!! error TS2869: The type of this declaration is ambiguous and may be observed as either 'AgeGroups' or '(typeof AgeGroups)["60-150"]'.
};
export {};

Loading