In JS, this assignments in constructors are preferred and nullable initializers become any (#22882)

* First draft:in js, constructor declaration is preferred

* Add tests

* initializer of null|undefined gives any in JS

Also move this-assignment fixes out of binder. I'm going to put it in
the checker instead.

* In JS, initializer null|undefined: any, []: any[]

* First draft of js prefer-ctor-types overhaul

* Update tests, update baselines

* Improve readability of constructor-type preference

* Cleanup: Remove TODO and duplication

* Add noImplicitAny errors

* Add comment
This commit is contained in:
Nathan Shively-Sanders
2018-03-26 13:42:34 -07:00
committed by GitHub
parent fa794f6ee1
commit c9ac15ae56
18 changed files with 1340 additions and 121 deletions

View File

@@ -4255,10 +4255,12 @@ namespace ts {
return getWidenedLiteralType(checkExpressionCached(specialDeclaration));
}
const types: Type[] = [];
let constructorTypes: Type[];
let definedInConstructor = false;
let definedInMethod = false;
let jsDocType: Type;
for (const declaration of symbol.declarations) {
let declarationInConstructor = false;
const expression = declaration.kind === SyntaxKind.BinaryExpression ? <BinaryExpression>declaration :
declaration.kind === SyntaxKind.PropertyAccessExpression ? <BinaryExpression>getAncestor(declaration, SyntaxKind.BinaryExpression) :
undefined;
@@ -4271,9 +4273,10 @@ namespace ts {
const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false);
// Properties defined in a constructor (or javascript constructor function) don't get undefined added.
// Function expressions that are assigned to the prototype count as methods.
if (thisContainer.kind === SyntaxKind.Constructor ||
declarationInConstructor = thisContainer.kind === SyntaxKind.Constructor ||
thisContainer.kind === SyntaxKind.FunctionDeclaration ||
(thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent))) {
(thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent));
if (declarationInConstructor) {
definedInConstructor = true;
}
else {
@@ -4296,14 +4299,37 @@ namespace ts {
}
else if (!jsDocType) {
// If we don't have an explicit JSDoc type, get the type from the expression.
types.push(getWidenedLiteralType(checkExpressionCached(expression.right)));
const type = getWidenedLiteralType(checkExpressionCached(expression.right));
let anyedType = type;
if (isEmptyArrayLiteralType(type)) {
anyedType = anyArrayType;
if (noImplicitAny) {
reportImplicitAnyError(expression, anyArrayType);
}
}
types.push(anyedType);
if (declarationInConstructor) {
(constructorTypes || (constructorTypes = [])).push(anyedType);
}
}
}
const type = jsDocType || getUnionType(types, UnionReduction.Subtype);
return getWidenedType(addOptionality(type, definedInMethod && !definedInConstructor));
let type = jsDocType;
if (!type) {
// use only the constructor types unless only null | undefined (including widening variants) were assigned there
const sourceTypes = some(constructorTypes, t => !!(t.flags & ~(TypeFlags.Nullable | TypeFlags.ContainsWideningType))) ? constructorTypes : types;
type = getUnionType(sourceTypes, UnionReduction.Subtype);
}
const widened = getWidenedType(addOptionality(type, definedInMethod && !definedInConstructor));
if (filterType(widened, t => !!(t.flags & ~TypeFlags.Nullable)) === neverType) {
if (noImplicitAny) {
reportImplicitAnyError(symbol.valueDeclaration, anyType);
}
return anyType;
}
return widened;
}
// Return the type implied by a binding pattern element. This is the type of the initializer of the element if
// one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding
// pattern. Otherwise, it is the type any.
@@ -11394,6 +11420,7 @@ namespace ts {
const typeAsString = typeToString(getWidenedType(type));
let diagnostic: DiagnosticMessage;
switch (declaration.kind) {
case SyntaxKind.BinaryExpression:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
diagnostic = Diagnostics.Member_0_implicitly_has_an_1_type;
@@ -19689,11 +19716,27 @@ namespace ts {
}
function checkDeclarationInitializer(declaration: HasExpressionInitializer) {
const initializer = isInJavaScriptFile(declaration) && getDeclaredJavascriptInitializer(declaration) || declaration.initializer;
const inJs = isInJavaScriptFile(declaration);
const initializer = inJs && getDeclaredJavascriptInitializer(declaration) || declaration.initializer;
const type = getTypeOfExpression(initializer, /*cache*/ true);
return getCombinedNodeFlags(declaration) & NodeFlags.Const ||
const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const ||
(getCombinedModifierFlags(declaration) & ModifierFlags.Readonly && !isParameterPropertyDeclaration(declaration)) ||
isTypeAssertion(initializer) ? type : getWidenedLiteralType(type);
if (inJs) {
if (widened.flags & TypeFlags.Nullable) {
if (noImplicitAny) {
reportImplicitAnyError(declaration, anyType);
}
return anyType;
}
else if (isEmptyArrayLiteralType(widened)) {
if (noImplicitAny) {
reportImplicitAnyError(declaration, anyArrayType);
}
return anyArrayType;
}
}
return widened;
}
function isLiteralOfContextualType(candidateType: Type, contextualType: Type): boolean {