Skip to content

Commit

Permalink
Simplify getWidenedTypeFromJSSpecialPropertyDeclarations (#25868)
Browse files Browse the repository at this point in the history
* Split getWidenedTypeFromJSSpecialPropertyDeclaration

1. Handle this-property assignments
2. Handle other special property assignment

I have only started simplifying [2].

* Split into two passes

1. Look for jsdoc
2. Look for type of initializers

Both can be broken into their own functions.

* Break into two functions

* Move back to original function and single loop

Then, convert the 2 extracted functions to reduce-style functions that
take state and return the updated state.

* Cleanup suggestions from review
  • Loading branch information
sandersn authored Jul 23, 2018
1 parent 93722c8 commit 2146a91
Showing 1 changed file with 86 additions and 72 deletions.
158 changes: 86 additions & 72 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4713,97 +4713,35 @@ namespace ts {
}
return getWidenedLiteralType(checkExpressionCached(specialDeclaration));
}
const types: Type[] = [];
let constructorTypes: Type[] | undefined;
let definedInConstructor = false;
let definedInMethod = false;
let jsDocType: Type | undefined;
let jsdocType: Type | undefined;
let types: Type[] | undefined;
for (const declaration of symbol.declarations) {
let declarationInConstructor = false;
const expression = isBinaryExpression(declaration) ? declaration :
isPropertyAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration :
undefined;

if (!expression) {
return errorType;
}

const special = isPropertyAccessExpression(expression) ? getSpecialPropertyAccessKind(expression) : getSpecialPropertyAssignmentKind(expression);
if (special === SpecialPropertyAssignmentKind.ThisProperty) {
const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false);
// Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added.
// Function expressions that are assigned to the prototype count as methods.
declarationInConstructor = thisContainer.kind === SyntaxKind.Constructor ||
thisContainer.kind === SyntaxKind.FunctionDeclaration ||
(thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent));
if (declarationInConstructor) {
if (isDeclarationInConstructor(expression)) {
definedInConstructor = true;
}
else {
definedInMethod = true;
}
}

// If there is a JSDoc type, use it
const type = getTypeForDeclarationFromJSDocComment(expression.parent);
if (type) {
const declarationType = getWidenedType(type);
if (!jsDocType) {
jsDocType = declarationType;
}
else if (jsDocType !== errorType && declarationType !== errorType &&
!isTypeIdenticalTo(jsDocType, declarationType) &&
!(symbol.flags & SymbolFlags.JSContainer)) {
errorNextVariableOrPropertyDeclarationMustHaveSameType(jsDocType, declaration, declarationType);
}
}
else if (!jsDocType && isBinaryExpression(expression)) {
// If we don't have an explicit JSDoc type, get the type from the expression.
let type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right));

if (type.flags & TypeFlags.Object &&
special === SpecialPropertyAssignmentKind.ModuleExports &&
symbol.escapedName === InternalSymbolName.ExportEquals) {
const exportedType = resolveStructuredTypeMembers(type as ObjectType);
const members = createSymbolTable();
copyEntries(exportedType.members, members);
if (resolvedSymbol && !resolvedSymbol.exports) {
resolvedSymbol.exports = createSymbolTable();
}
(resolvedSymbol || symbol).exports!.forEach((s, name) => {
if (members.has(name)) {
const exportedMember = exportedType.members.get(name)!;
const union = createSymbol(s.flags | exportedMember.flags, name);
union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]);
members.set(name, union);
}
else {
members.set(name, s);
}
});
type = createAnonymousType(
exportedType.symbol,
members,
exportedType.callSignatures,
exportedType.constructSignatures,
exportedType.stringIndexInfo,
exportedType.numberIndexInfo);
}
let anyedType = type;
if (isEmptyArrayLiteralType(type)) {
anyedType = anyArrayType;
if (noImplicitAny) {
reportImplicitAnyError(expression, anyArrayType);
}
}
types.push(anyedType);
if (declarationInConstructor) {
(constructorTypes || (constructorTypes = [])).push(anyedType);
}
jsdocType = getJSDocTypeFromSpecialDeclarations(jsdocType, expression, symbol, declaration);
if (!jsdocType) {
(types || (types = [])).push(isBinaryExpression(expression) ? getInitializerTypeFromSpecialDeclarations(symbol, resolvedSymbol, expression, special) : neverType);
}
}
let type = jsDocType;
let type = jsdocType;
if (!type) {
let constructorTypes = definedInConstructor ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined;
// use only the constructor types unless they were only assigned null | undefined (including widening variants)
if (definedInMethod) {
const propType = getTypeOfSpecialPropertyOfBaseType(symbol);
Expand All @@ -4812,8 +4750,8 @@ namespace ts {
definedInConstructor = true;
}
}
const sourceTypes = some(constructorTypes, t => !!(t.flags & ~(TypeFlags.Nullable | TypeFlags.ContainsWideningType))) ? constructorTypes! : types; // TODO: GH#18217
type = getUnionType(sourceTypes, UnionReduction.Subtype);
const sourceTypes = some(constructorTypes, t => !!(t.flags & ~(TypeFlags.Nullable | TypeFlags.ContainsWideningType))) ? constructorTypes : types; // TODO: GH#18217
type = getUnionType(sourceTypes!, UnionReduction.Subtype);
}
const widened = getWidenedType(addOptionality(type, definedInMethod && !definedInConstructor));
if (filterType(widened, t => !!(t.flags & ~TypeFlags.Nullable)) === neverType) {
Expand All @@ -4825,6 +4763,82 @@ namespace ts {
return widened;
}

function getJSDocTypeFromSpecialDeclarations(declaredType: Type | undefined, expression: Expression, _symbol: Symbol, declaration: Declaration) {
const typeNode = getJSDocType(expression.parent);
if (typeNode) {
const type = getWidenedType(getTypeFromTypeNode(typeNode));
if (!declaredType) {
return type;
}
else if (declaredType !== errorType && type !== errorType && !isTypeIdenticalTo(declaredType, type)) {
errorNextVariableOrPropertyDeclarationMustHaveSameType(declaredType, declaration, type);
}
}
return declaredType;
}

/** If we don't have an explicit JSDoc type, get the type from the initializer. */
function getInitializerTypeFromSpecialDeclarations(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: Expression, special: SpecialPropertyAssignmentKind) {
if (isBinaryExpression(expression)) {
const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right));
if (type.flags & TypeFlags.Object &&
special === SpecialPropertyAssignmentKind.ModuleExports &&
symbol.escapedName === InternalSymbolName.ExportEquals) {
const exportedType = resolveStructuredTypeMembers(type as ObjectType);
const members = createSymbolTable();
copyEntries(exportedType.members, members);
if (resolvedSymbol && !resolvedSymbol.exports) {
resolvedSymbol.exports = createSymbolTable();
}
(resolvedSymbol || symbol).exports!.forEach((s, name) => {
if (members.has(name)) {
const exportedMember = exportedType.members.get(name)!;
const union = createSymbol(s.flags | exportedMember.flags, name);
union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]);
members.set(name, union);
}
else {
members.set(name, s);
}
});
return createAnonymousType(
exportedType.symbol,
members,
exportedType.callSignatures,
exportedType.constructSignatures,
exportedType.stringIndexInfo,
exportedType.numberIndexInfo);
}
if (isEmptyArrayLiteralType(type)) {
if (noImplicitAny) {
reportImplicitAnyError(expression, anyArrayType);
}
return anyArrayType;
}
return type;
}
return neverType;
}

function isDeclarationInConstructor(expression: Expression) {
const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false);
// Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added.
// Function expressions that are assigned to the prototype count as methods.
return thisContainer.kind === SyntaxKind.Constructor ||
thisContainer.kind === SyntaxKind.FunctionDeclaration ||
(thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent));
}

function getConstructorDefinedThisAssignmentTypes(types: Type[], declarations: Declaration[]): Type[] | undefined {
Debug.assert(types.length === declarations.length);
return types.filter((_, i) => {
const declaration = declarations[i];
const expression = isBinaryExpression(declaration) ? declaration :
isBinaryExpression(declaration.parent) ? declaration.parent : undefined;
return expression && isDeclarationInConstructor(expression);
});
}

/** check for definition in base class if any declaration is in a class */
function getTypeOfSpecialPropertyOfBaseType(specialProperty: Symbol) {
const parentDeclaration = forEach(specialProperty.declarations, d => {
Expand Down

0 comments on commit 2146a91

Please sign in to comment.