For a this-property assignment with an empty object initializer, use type annotation if present (#26428)

* This-property w/empty object init: use type annotation

* JS initializer type doesn't apply to this-property assignments

* Move getJSExpandoType into getWidenedType* functions

Plus some cleanup.

* Improved style from PR comments

* Parameters are not expando
This commit is contained in:
Nathan Shively-Sanders 2018-08-15 14:53:30 -07:00 committed by GitHub
parent dfef227b18
commit 08eb99d8ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 161 additions and 91 deletions

View file

@ -4696,6 +4696,12 @@ namespace ts {
return addOptionality(type, isOptional);
}
}
else if (isInJavaScriptFile(declaration)) {
const expandoType = getJSExpandoObjectType(declaration, getSymbolOfNode(declaration), getDeclaredJavascriptInitializer(declaration));
if (expandoType) {
return expandoType;
}
}
// Use the type of the initializer expression if one is present
if (declaration.initializer) {
@ -4718,7 +4724,7 @@ namespace ts {
return undefined;
}
function getWidenedTypeFromJSSpecialPropertyDeclarations(symbol: Symbol, resolvedSymbol?: Symbol) {
function getWidenedTypeFromJSPropertyAssignments(symbol: Symbol, resolvedSymbol?: Symbol) {
// function/class/{} assignments are fresh declarations, not property assignments, so only add prototype assignments
const specialDeclaration = getAssignedJavascriptInitializer(symbol.valueDeclaration);
if (specialDeclaration) {
@ -4726,7 +4732,8 @@ namespace ts {
if (tag && tag.typeExpression) {
return getTypeFromTypeNode(tag.typeExpression);
}
return getWidenedLiteralType(checkExpressionCached(specialDeclaration));
const expando = getJSExpandoObjectType(symbol.valueDeclaration, symbol, specialDeclaration);
return expando || getWidenedLiteralType(checkExpressionCached(specialDeclaration));
}
let definedInConstructor = false;
let definedInMethod = false;
@ -4778,6 +4785,27 @@ namespace ts {
return widened;
}
function getJSExpandoObjectType(decl: Node, symbol: Symbol, init: Expression | undefined): Type | undefined {
if (!init || !isObjectLiteralExpression(init) || init.properties.length) {
return undefined;
}
const exports = createSymbolTable();
while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) {
const s = getSymbolOfNode(decl);
if (s && hasEntries(s.exports)) {
mergeSymbolTable(exports, s.exports);
}
decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent;
}
const s = getSymbolOfNode(decl);
if (s && hasEntries(s.exports)) {
mergeSymbolTable(exports, s.exports);
}
const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, undefined, undefined);
type.objectFlags |= ObjectFlags.JSLiteral;
return type;
}
function getJSDocTypeFromSpecialDeclarations(declaredType: Type | undefined, expression: Expression, _symbol: Symbol, declaration: Declaration) {
const typeNode = getJSDocType(expression.parent);
if (typeNode) {
@ -5041,98 +5069,51 @@ namespace ts {
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return errorType;
}
let type = getJSSpecialType(symbol, declaration);
if (!type) {
if (isJSDocPropertyLikeTag(declaration)
|| isPropertyAccessExpression(declaration)
|| isIdentifier(declaration)
|| isClassDeclaration(declaration)
|| isFunctionDeclaration(declaration)
|| (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration))
|| isMethodSignature(declaration)) {
// Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty`
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
return getTypeOfFuncClassEnumModule(symbol);
}
type = tryGetTypeFromEffectiveTypeNode(declaration) || anyType;
}
else if (isPropertyAssignment(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration);
}
else if (isJsxAttribute(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration);
}
else if (isShorthandPropertyAssignment(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal);
}
else if (isObjectLiteralMethod(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal);
}
else if (isParameter(declaration)
|| isPropertyDeclaration(declaration)
|| isPropertySignature(declaration)
|| isVariableDeclaration(declaration)
|| isBindingElement(declaration)) {
type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true);
}
else {
return Debug.fail("Unhandled declaration kind! " + Debug.showSyntaxKind(declaration) + " for " + Debug.showSymbol(symbol));
let type: Type | undefined;
if (isInJavaScriptFile(declaration) &&
(isBinaryExpression(declaration) || isPropertyAccessExpression(declaration) && isBinaryExpression(declaration.parent))) {
type = getWidenedTypeFromJSPropertyAssignments(symbol);
}
else if (isJSDocPropertyLikeTag(declaration)
|| isPropertyAccessExpression(declaration)
|| isIdentifier(declaration)
|| isClassDeclaration(declaration)
|| isFunctionDeclaration(declaration)
|| (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration))
|| isMethodSignature(declaration)) {
// Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty`
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
return getTypeOfFuncClassEnumModule(symbol);
}
type = tryGetTypeFromEffectiveTypeNode(declaration) || anyType;
}
else if (isPropertyAssignment(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration);
}
else if (isJsxAttribute(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration);
}
else if (isShorthandPropertyAssignment(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal);
}
else if (isObjectLiteralMethod(declaration)) {
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal);
}
else if (isParameter(declaration)
|| isPropertyDeclaration(declaration)
|| isPropertySignature(declaration)
|| isVariableDeclaration(declaration)
|| isBindingElement(declaration)) {
type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true);
}
else {
return Debug.fail("Unhandled declaration kind! " + Debug.showSyntaxKind(declaration) + " for " + Debug.showSymbol(symbol));
}
if (!popTypeResolution()) {
type = reportCircularityError(symbol);
}
return type;
}
function getJSSpecialType(symbol: Symbol, decl: Declaration): Type | undefined {
if (!isInJavaScriptFile(decl)) {
return undefined;
}
// Handle certain special assignment kinds, which happen to union across multiple declarations:
// * module.exports = expr
// * exports.p = expr
// * this.p = expr
// * className.prototype.method = expr
else if (isBinaryExpression(decl) ||
isPropertyAccessExpression(decl) && isBinaryExpression(decl.parent)) {
return getJSInitializerType(decl, symbol, getAssignedJavascriptInitializer(isBinaryExpression(decl) ? decl.left : decl)) ||
getWidenedTypeFromJSSpecialPropertyDeclarations(symbol);
}
else if (isParameter(decl)
|| isPropertyDeclaration(decl)
|| isPropertySignature(decl)
|| isVariableDeclaration(decl)
|| isBindingElement(decl)) {
// Use type from type annotation if one is present
const isOptional = isParameter(decl) && isJSDocOptionalParameter(decl) ||
!isBindingElement(decl) && !isVariableDeclaration(decl) && !!decl.questionToken;
const declaredType = tryGetTypeFromEffectiveTypeNode(decl);
return declaredType && addOptionality(declaredType, isOptional) ||
getJSInitializerType(decl, symbol, getDeclaredJavascriptInitializer(decl)) ||
getWidenedTypeForVariableLikeDeclaration(decl, /*includeOptionality*/ true);
}
}
function getJSInitializerType(decl: Node, symbol: Symbol, init: Expression | undefined): Type | undefined {
if (init && isInJavaScriptFile(init) && isObjectLiteralExpression(init) && init.properties.length === 0) {
const exports = createSymbolTable();
while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) {
const s = getSymbolOfNode(decl);
if (s && hasEntries(s.exports)) {
mergeSymbolTable(exports, s.exports);
}
decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent;
}
const s = getSymbolOfNode(decl);
if (s && hasEntries(s.exports)) {
mergeSymbolTable(exports, s.exports);
}
const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, undefined, undefined);
type.objectFlags |= ObjectFlags.JSLiteral;
return type;
}
}
function getAnnotatedAccessorTypeNode(accessor: AccessorDeclaration | undefined): TypeNode | undefined {
@ -5264,7 +5245,7 @@ namespace ts {
}
else if (declaration.kind === SyntaxKind.BinaryExpression ||
declaration.kind === SyntaxKind.PropertyAccessExpression && declaration.parent.kind === SyntaxKind.BinaryExpression) {
return getWidenedTypeFromJSSpecialPropertyDeclarations(symbol);
return getWidenedTypeFromJSPropertyAssignments(symbol);
}
else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) {
const resolvedModule = resolveExternalModuleSymbol(symbol);
@ -5273,7 +5254,7 @@ namespace ts {
return errorType;
}
const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!);
const type = getWidenedTypeFromJSSpecialPropertyDeclarations(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule);
const type = getWidenedTypeFromJSPropertyAssignments(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule);
if (!popTypeResolution()) {
return reportCircularityError(symbol);
}

View file

@ -0,0 +1,28 @@
=== tests/cases/conformance/salsa/Compilation.js ===
// from webpack/lib/Compilation.js and filed at #26427
/** @param {{ [s: string]: number }} map */
function mappy(map) {}
>mappy : Symbol(mappy, Decl(Compilation.js, 0, 0))
>map : Symbol(map, Decl(Compilation.js, 2, 15))
export class C {
>C : Symbol(C, Decl(Compilation.js, 2, 22))
constructor() {
/** @type {{ [assetName: string]: number}} */
this.assets = {};
>this.assets : Symbol(C.assets, Decl(Compilation.js, 5, 19))
>this : Symbol(C, Decl(Compilation.js, 2, 22))
>assets : Symbol(C.assets, Decl(Compilation.js, 5, 19))
}
m() {
>m : Symbol(C.m, Decl(Compilation.js, 8, 5))
mappy(this.assets)
>mappy : Symbol(mappy, Decl(Compilation.js, 0, 0))
>this.assets : Symbol(C.assets, Decl(Compilation.js, 5, 19))
>this : Symbol(C, Decl(Compilation.js, 2, 22))
>assets : Symbol(C.assets, Decl(Compilation.js, 5, 19))
}
}

View file

@ -0,0 +1,31 @@
=== tests/cases/conformance/salsa/Compilation.js ===
// from webpack/lib/Compilation.js and filed at #26427
/** @param {{ [s: string]: number }} map */
function mappy(map) {}
>mappy : (map: { [s: string]: number; }) => void
>map : { [s: string]: number; }
export class C {
>C : C
constructor() {
/** @type {{ [assetName: string]: number}} */
this.assets = {};
>this.assets = {} : {}
>this.assets : { [assetName: string]: number; }
>this : this
>assets : { [assetName: string]: number; }
>{} : {}
}
m() {
>m : () => void
mappy(this.assets)
>mappy(this.assets) : void
>mappy : (map: { [s: string]: number; }) => void
>this.assets : { [assetName: string]: number; }
>this : this
>assets : { [assetName: string]: number; }
}
}

View file

@ -1,14 +1,14 @@
=== tests/cases/compiler/bug25434.js ===
// should not crash while checking
function Test({ b = '' } = {}) {}
>Test : ({ b }?: {}) => void
>Test : ({ b }?: { b?: string; }) => void
>b : string
>'' : ""
>{} : { b?: string; }
Test(({ b = '5' } = {}));
>Test(({ b = '5' } = {})) : void
>Test : ({ b }?: {}) => void
>Test : ({ b }?: { b?: string; }) => void
>({ b = '5' } = {}) : { b?: any; }
>{ b = '5' } = {} : { b?: any; }
>{ b = '5' } : { b?: any; }

View file

@ -0,0 +1,13 @@
tests/cases/conformance/salsa/b.js(1,7): error TS2322: Type '{}' is not assignable to type 'typeof A'.
Property 'prototype' is missing in type '{}'.
==== tests/cases/conformance/salsa/a.d.ts (0 errors) ====
declare class A {}
==== tests/cases/conformance/salsa/b.js (1 errors) ====
const A = { };
~
!!! error TS2322: Type '{}' is not assignable to type 'typeof A'.
!!! error TS2322: Property 'prototype' is missing in type '{}'.
A.d = { };

View file

@ -0,0 +1,17 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @Filename: Compilation.js
// from webpack/lib/Compilation.js and filed at #26427
/** @param {{ [s: string]: number }} map */
function mappy(map) {}
export class C {
constructor() {
/** @type {{ [assetName: string]: number}} */
this.assets = {};
}
m() {
mappy(this.assets)
}
}