Contextually typed binding element initializers (#35855)

* Binding element initializers contextually typed by parent initializers

* Accept new baselines

* Literal type widening should be last step in inference

* Accept new baselines

* Add tests
This commit is contained in:
Anders Hejlsberg
2020-01-06 12:53:23 -08:00
committed by GitHub
parent df3b5bbdab
commit f8bfc6f5d6
10 changed files with 243 additions and 72 deletions

View File

@@ -6981,7 +6981,7 @@ namespace ts {
getTypeWithFacts(type, TypeFacts.NEUndefined) :
type;
}
return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype);
return widenTypeInferredFromInitializer(declaration, getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype));
}
function getTypeForDeclarationFromJSDocComment(declaration: Node) {
@@ -7098,7 +7098,7 @@ namespace ts {
// Use the type of the initializer expression if one is present and the declaration is
// not a parameter of a contextually typed function
if (declaration.initializer && !isParameterOfContextuallyTypedFunction(declaration)) {
const type = checkDeclarationInitializer(declaration);
const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration));
return addOptionality(type, isOptional);
}
@@ -7330,7 +7330,11 @@ namespace ts {
// pattern. Otherwise, it is the type any.
function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type {
if (element.initializer) {
return addOptionality(checkDeclarationInitializer(element));
// The type implied by a binding pattern is independent of context, so we check the initializer with no
// contextual type or, if the element itself is a binding pattern, with the type implied by that binding
// pattern.
const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType;
return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, contextualType)));
}
if (isBindingPattern(element.name)) {
return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors);
@@ -21195,9 +21199,10 @@ namespace ts {
}
function getContextualTypeForBindingElement(declaration: BindingElement): Type | undefined {
const parentDeclaration = declaration.parent.parent;
const parent = declaration.parent.parent;
const name = declaration.propertyName || declaration.name;
const parentType = getContextualTypeForVariableLikeDeclaration(parentDeclaration);
const parentType = getContextualTypeForVariableLikeDeclaration(parent) ||
parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent);
if (parentType && !isBindingPattern(name) && !isComputedNonLiteralName(name)) {
const nameType = getLiteralTypeFromPropertyName(name);
if (isTypeUsableAsPropertyName(nameType)) {
@@ -27661,27 +27666,13 @@ namespace ts {
return node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression;
}
function checkDeclarationInitializer(declaration: HasExpressionInitializer) {
function checkDeclarationInitializer(declaration: HasExpressionInitializer, contextualType?: Type | undefined) {
const initializer = getEffectiveInitializer(declaration)!;
const type = getQuickTypeOfExpression(initializer) || checkExpressionCached(initializer);
const padded = isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
const type = getQuickTypeOfExpression(initializer) ||
(contextualType ? checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, CheckMode.Normal) : checkExpressionCached(initializer));
return isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern &&
isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ?
padTupleType(type, declaration.name) : type;
const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const ||
isDeclarationReadonly(declaration) ||
isTypeAssertion(initializer) ||
isLiteralOfContextualType(padded, getContextualType(initializer)) ? padded : getWidenedLiteralType(padded);
if (isInJSFile(declaration)) {
if (widened.flags & TypeFlags.Nullable) {
reportImplicitAny(declaration, anyType);
return anyType;
}
else if (isEmptyArrayLiteralType(widened)) {
reportImplicitAny(declaration, anyArrayType);
return anyArrayType;
}
}
return widened;
}
function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) {
@@ -27700,6 +27691,21 @@ namespace ts {
return createTupleType(elementTypes, type.target.minLength, /*hasRestElement*/ false, type.target.readonly);
}
function widenTypeInferredFromInitializer(declaration: HasExpressionInitializer, type: Type) {
const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const || isDeclarationReadonly(declaration) ? type : getWidenedLiteralType(type);
if (isInJSFile(declaration)) {
if (widened.flags & TypeFlags.Nullable) {
reportImplicitAny(declaration, anyType);
return anyType;
}
else if (isEmptyArrayLiteralType(widened)) {
reportImplicitAny(declaration, anyArrayType);
return anyArrayType;
}
}
return widened;
}
function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean {
if (contextualType) {
if (contextualType.flags & TypeFlags.UnionOrIntersection) {

View File

@@ -1,36 +0,0 @@
tests/cases/compiler/crashInGetTextOfComputedPropertyName.ts(23,24): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
==== tests/cases/compiler/crashInGetTextOfComputedPropertyName.ts (1 errors) ====
// https://github.com/Microsoft/TypeScript/issues/29006
export interface A { type: 'a' }
export interface B { type: 'b' }
export type AB = A | B
const itemId = 'some-id'
// --- test on first level ---
const items: { [id: string]: AB } = {}
const { [itemId]: itemOk1 } = items
typeof itemOk1 // pass
// --- test on second level ---
interface ObjWithItems {
items: {[s: string]: AB}
}
const objWithItems: ObjWithItems = { items: {}}
const itemOk2 = objWithItems.items[itemId]
typeof itemOk2 // pass
const {
items: { [itemId]: itemWithTSError } = {} /*happens when default value is provided*/
~~~~~~~~~~~~~~~
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
} = objWithItems
// in order to re-produce the error, uncomment next line:
typeof itemWithTSError // :(
// will result in:
// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined

View File

@@ -56,8 +56,8 @@ const {
items: { [itemId]: itemWithTSError } = {} /*happens when default value is provided*/
>items : any
>itemId : "some-id"
>itemWithTSError : any
>{} : { "some-id": any; }
>itemWithTSError : AB
>{} : {}
} = objWithItems
>objWithItems : ObjWithItems
@@ -65,7 +65,7 @@ const {
// in order to re-produce the error, uncomment next line:
typeof itemWithTSError // :(
>typeof itemWithTSError : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>itemWithTSError : any
>itemWithTSError : AB
// will result in:
// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined

View File

@@ -0,0 +1,37 @@
//// [literalTypesAndDestructuring.ts]
declare let x: { a: 0 | 1 | undefined };
let { a: a1 } = x;
let { a: a2 = 0 } = x;
let { a: a3 = 2 } = x;
let { a: a4 = 2 as const } = x;
let b1 = x.a;
let b2 = x.a ?? 0;
let b3 = x.a ?? 2;
let b4 = x.a ?? 2 as const;
// Repro from #35693
interface Foo {
bar: 'yo' | 'ha' | undefined;
}
let { bar = 'yo' } = {} as Foo;
bar; // "yo" | "ha"
//// [literalTypesAndDestructuring.js]
"use strict";
var _a, _b, _c;
var a1 = x.a;
var _d = x.a, a2 = _d === void 0 ? 0 : _d;
var _e = x.a, a3 = _e === void 0 ? 2 : _e;
var _f = x.a, a4 = _f === void 0 ? 2 : _f;
var b1 = x.a;
var b2 = (_a = x.a) !== null && _a !== void 0 ? _a : 0;
var b3 = (_b = x.a) !== null && _b !== void 0 ? _b : 2;
var b4 = (_c = x.a) !== null && _c !== void 0 ? _c : 2;
var _g = {}.bar, bar = _g === void 0 ? 'yo' : _g;
bar; // "yo" | "ha"

View File

@@ -0,0 +1,65 @@
=== tests/cases/conformance/types/literal/literalTypesAndDestructuring.ts ===
declare let x: { a: 0 | 1 | undefined };
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
let { a: a1 } = x;
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
>a1 : Symbol(a1, Decl(literalTypesAndDestructuring.ts, 2, 5))
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
let { a: a2 = 0 } = x;
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
>a2 : Symbol(a2, Decl(literalTypesAndDestructuring.ts, 3, 5))
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
let { a: a3 = 2 } = x;
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
>a3 : Symbol(a3, Decl(literalTypesAndDestructuring.ts, 4, 5))
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
let { a: a4 = 2 as const } = x;
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
>a4 : Symbol(a4, Decl(literalTypesAndDestructuring.ts, 5, 5))
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
let b1 = x.a;
>b1 : Symbol(b1, Decl(literalTypesAndDestructuring.ts, 7, 3))
>x.a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
let b2 = x.a ?? 0;
>b2 : Symbol(b2, Decl(literalTypesAndDestructuring.ts, 8, 3))
>x.a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
let b3 = x.a ?? 2;
>b3 : Symbol(b3, Decl(literalTypesAndDestructuring.ts, 9, 3))
>x.a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
let b4 = x.a ?? 2 as const;
>b4 : Symbol(b4, Decl(literalTypesAndDestructuring.ts, 10, 3))
>x.a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
>x : Symbol(x, Decl(literalTypesAndDestructuring.ts, 0, 11))
>a : Symbol(a, Decl(literalTypesAndDestructuring.ts, 0, 16))
// Repro from #35693
interface Foo {
>Foo : Symbol(Foo, Decl(literalTypesAndDestructuring.ts, 10, 27))
bar: 'yo' | 'ha' | undefined;
>bar : Symbol(Foo.bar, Decl(literalTypesAndDestructuring.ts, 14, 15))
}
let { bar = 'yo' } = {} as Foo;
>bar : Symbol(bar, Decl(literalTypesAndDestructuring.ts, 18, 5))
>Foo : Symbol(Foo, Decl(literalTypesAndDestructuring.ts, 10, 27))
bar; // "yo" | "ha"
>bar : Symbol(bar, Decl(literalTypesAndDestructuring.ts, 18, 5))

View File

@@ -0,0 +1,76 @@
=== tests/cases/conformance/types/literal/literalTypesAndDestructuring.ts ===
declare let x: { a: 0 | 1 | undefined };
>x : { a: 0 | 1 | undefined; }
>a : 0 | 1 | undefined
let { a: a1 } = x;
>a : any
>a1 : 0 | 1 | undefined
>x : { a: 0 | 1 | undefined; }
let { a: a2 = 0 } = x;
>a : any
>a2 : 0 | 1
>0 : 0
>x : { a: 0 | 1 | undefined; }
let { a: a3 = 2 } = x;
>a : any
>a3 : number
>2 : 2
>x : { a: 0 | 1 | undefined; }
let { a: a4 = 2 as const } = x;
>a : any
>a4 : 0 | 1 | 2
>2 as const : 2
>2 : 2
>x : { a: 0 | 1 | undefined; }
let b1 = x.a;
>b1 : 0 | 1 | undefined
>x.a : 0 | 1 | undefined
>x : { a: 0 | 1 | undefined; }
>a : 0 | 1 | undefined
let b2 = x.a ?? 0;
>b2 : 0 | 1
>x.a ?? 0 : 0 | 1
>x.a : 0 | 1 | undefined
>x : { a: 0 | 1 | undefined; }
>a : 0 | 1 | undefined
>0 : 0
let b3 = x.a ?? 2;
>b3 : number
>x.a ?? 2 : 0 | 1 | 2
>x.a : 0 | 1 | undefined
>x : { a: 0 | 1 | undefined; }
>a : 0 | 1 | undefined
>2 : 2
let b4 = x.a ?? 2 as const;
>b4 : 0 | 1 | 2
>x.a ?? 2 as const : 0 | 1 | 2
>x.a : 0 | 1 | undefined
>x : { a: 0 | 1 | undefined; }
>a : 0 | 1 | undefined
>2 as const : 2
>2 : 2
// Repro from #35693
interface Foo {
bar: 'yo' | 'ha' | undefined;
>bar : "yo" | "ha" | undefined
}
let { bar = 'yo' } = {} as Foo;
>bar : "yo" | "ha"
>'yo' : "yo"
>{} as Foo : Foo
>{} : {}
bar; // "yo" | "ha"
>bar : "yo" | "ha"

View File

@@ -36,7 +36,7 @@ let { a = "foo" } = { a: "foo" };
>"foo" : "foo"
let { b = "foo" as "foo" } = { b: "bar" };
>b : "foo" | "bar"
>b : string
>"foo" as "foo" : "foo"
>"foo" : "foo"
>{ b: "bar" } : { b?: "bar"; }

View File

@@ -144,7 +144,7 @@ for (let {
>"secondary" : "secondary"
} = { primary: "none", secondary: "none" }
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
>primary : string
>"none" : "none"
>secondary : string
@@ -182,7 +182,7 @@ for (let {
>"secondary" : "secondary"
} = { primary: "none", secondary: "none" }
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
>primary : string
>"none" : "none"
>secondary : string
@@ -221,7 +221,7 @@ for (let {
>"secondary" : "secondary"
} = { primary: "none", secondary: "none" }
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
>primary : string
>"none" : "none"
>secondary : string
@@ -351,7 +351,7 @@ for (let {
>"secondary" : "secondary"
} = { primary: "none", secondary: "none" }
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
>primary : string
>"none" : "none"
>secondary : string
@@ -394,7 +394,7 @@ for (let {
>"secondary" : "secondary"
} = { primary: "none", secondary: "none" }
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
>primary : string
>"none" : "none"
>secondary : string
@@ -438,7 +438,7 @@ for (let {
>"secondary" : "secondary"
} = { primary: "none", secondary: "none" }
>{ primary: "none", secondary: "none" } : { primary?: string; secondary?: string; }
>{ primary: "none", secondary: "none" } : { primary: string; secondary: string; }
>primary : string
>"none" : "none"
>secondary : string

View File

@@ -60,7 +60,7 @@ var {
>"noSkill" : "noSkill"
} = { primary: "noSkill", secondary: "noSkill" }
>{ primary: "noSkill", secondary: "noSkill" } : { primary?: string; secondary?: string; }
>{ primary: "noSkill", secondary: "noSkill" } : { primary: string; secondary: string; }
>primary : string
>"noSkill" : "noSkill"
>secondary : string
@@ -89,7 +89,7 @@ var {
>"noSkill" : "noSkill"
} = { primary: "noSkill", secondary: "noSkill" }
>{ primary: "noSkill", secondary: "noSkill" } : { primary?: string; secondary?: string; }
>{ primary: "noSkill", secondary: "noSkill" } : { primary: string; secondary: string; }
>primary : string
>"noSkill" : "noSkill"
>secondary : string
@@ -118,7 +118,7 @@ var {
>"noSkill" : "noSkill"
} = { primary: "noSkill", secondary: "noSkill" }
>{ primary: "noSkill", secondary: "noSkill" } : { primary?: string; secondary?: string; }
>{ primary: "noSkill", secondary: "noSkill" } : { primary: string; secondary: string; }
>primary : string
>"noSkill" : "noSkill"
>secondary : string

View File

@@ -0,0 +1,23 @@
// @strict: true
declare let x: { a: 0 | 1 | undefined };
let { a: a1 } = x;
let { a: a2 = 0 } = x;
let { a: a3 = 2 } = x;
let { a: a4 = 2 as const } = x;
let b1 = x.a;
let b2 = x.a ?? 0;
let b3 = x.a ?? 2;
let b4 = x.a ?? 2 as const;
// Repro from #35693
interface Foo {
bar: 'yo' | 'ha' | undefined;
}
let { bar = 'yo' } = {} as Foo;
bar; // "yo" | "ha"