Skip to content

Commit 2146a91

Browse files
authored
Simplify getWidenedTypeFromJSSpecialPropertyDeclarations (#25868)
* 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
1 parent 93722c8 commit 2146a91

File tree

1 file changed

+86
-72
lines changed

1 file changed

+86
-72
lines changed

Diff for: src/compiler/checker.ts

+86-72
Original file line numberDiff line numberDiff line change
@@ -4713,97 +4713,35 @@ namespace ts {
47134713
}
47144714
return getWidenedLiteralType(checkExpressionCached(specialDeclaration));
47154715
}
4716-
const types: Type[] = [];
4717-
let constructorTypes: Type[] | undefined;
47184716
let definedInConstructor = false;
47194717
let definedInMethod = false;
4720-
let jsDocType: Type | undefined;
4718+
let jsdocType: Type | undefined;
4719+
let types: Type[] | undefined;
47214720
for (const declaration of symbol.declarations) {
4722-
let declarationInConstructor = false;
47234721
const expression = isBinaryExpression(declaration) ? declaration :
47244722
isPropertyAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration :
47254723
undefined;
4726-
47274724
if (!expression) {
47284725
return errorType;
47294726
}
47304727

47314728
const special = isPropertyAccessExpression(expression) ? getSpecialPropertyAccessKind(expression) : getSpecialPropertyAssignmentKind(expression);
47324729
if (special === SpecialPropertyAssignmentKind.ThisProperty) {
4733-
const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false);
4734-
// Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added.
4735-
// Function expressions that are assigned to the prototype count as methods.
4736-
declarationInConstructor = thisContainer.kind === SyntaxKind.Constructor ||
4737-
thisContainer.kind === SyntaxKind.FunctionDeclaration ||
4738-
(thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent));
4739-
if (declarationInConstructor) {
4730+
if (isDeclarationInConstructor(expression)) {
47404731
definedInConstructor = true;
47414732
}
47424733
else {
47434734
definedInMethod = true;
47444735
}
47454736
}
4746-
4747-
// If there is a JSDoc type, use it
4748-
const type = getTypeForDeclarationFromJSDocComment(expression.parent);
4749-
if (type) {
4750-
const declarationType = getWidenedType(type);
4751-
if (!jsDocType) {
4752-
jsDocType = declarationType;
4753-
}
4754-
else if (jsDocType !== errorType && declarationType !== errorType &&
4755-
!isTypeIdenticalTo(jsDocType, declarationType) &&
4756-
!(symbol.flags & SymbolFlags.JSContainer)) {
4757-
errorNextVariableOrPropertyDeclarationMustHaveSameType(jsDocType, declaration, declarationType);
4758-
}
4759-
}
4760-
else if (!jsDocType && isBinaryExpression(expression)) {
4761-
// If we don't have an explicit JSDoc type, get the type from the expression.
4762-
let type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right));
4763-
4764-
if (type.flags & TypeFlags.Object &&
4765-
special === SpecialPropertyAssignmentKind.ModuleExports &&
4766-
symbol.escapedName === InternalSymbolName.ExportEquals) {
4767-
const exportedType = resolveStructuredTypeMembers(type as ObjectType);
4768-
const members = createSymbolTable();
4769-
copyEntries(exportedType.members, members);
4770-
if (resolvedSymbol && !resolvedSymbol.exports) {
4771-
resolvedSymbol.exports = createSymbolTable();
4772-
}
4773-
(resolvedSymbol || symbol).exports!.forEach((s, name) => {
4774-
if (members.has(name)) {
4775-
const exportedMember = exportedType.members.get(name)!;
4776-
const union = createSymbol(s.flags | exportedMember.flags, name);
4777-
union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]);
4778-
members.set(name, union);
4779-
}
4780-
else {
4781-
members.set(name, s);
4782-
}
4783-
});
4784-
type = createAnonymousType(
4785-
exportedType.symbol,
4786-
members,
4787-
exportedType.callSignatures,
4788-
exportedType.constructSignatures,
4789-
exportedType.stringIndexInfo,
4790-
exportedType.numberIndexInfo);
4791-
}
4792-
let anyedType = type;
4793-
if (isEmptyArrayLiteralType(type)) {
4794-
anyedType = anyArrayType;
4795-
if (noImplicitAny) {
4796-
reportImplicitAnyError(expression, anyArrayType);
4797-
}
4798-
}
4799-
types.push(anyedType);
4800-
if (declarationInConstructor) {
4801-
(constructorTypes || (constructorTypes = [])).push(anyedType);
4802-
}
4737+
jsdocType = getJSDocTypeFromSpecialDeclarations(jsdocType, expression, symbol, declaration);
4738+
if (!jsdocType) {
4739+
(types || (types = [])).push(isBinaryExpression(expression) ? getInitializerTypeFromSpecialDeclarations(symbol, resolvedSymbol, expression, special) : neverType);
48034740
}
48044741
}
4805-
let type = jsDocType;
4742+
let type = jsdocType;
48064743
if (!type) {
4744+
let constructorTypes = definedInConstructor ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined;
48074745
// use only the constructor types unless they were only assigned null | undefined (including widening variants)
48084746
if (definedInMethod) {
48094747
const propType = getTypeOfSpecialPropertyOfBaseType(symbol);
@@ -4812,8 +4750,8 @@ namespace ts {
48124750
definedInConstructor = true;
48134751
}
48144752
}
4815-
const sourceTypes = some(constructorTypes, t => !!(t.flags & ~(TypeFlags.Nullable | TypeFlags.ContainsWideningType))) ? constructorTypes! : types; // TODO: GH#18217
4816-
type = getUnionType(sourceTypes, UnionReduction.Subtype);
4753+
const sourceTypes = some(constructorTypes, t => !!(t.flags & ~(TypeFlags.Nullable | TypeFlags.ContainsWideningType))) ? constructorTypes : types; // TODO: GH#18217
4754+
type = getUnionType(sourceTypes!, UnionReduction.Subtype);
48174755
}
48184756
const widened = getWidenedType(addOptionality(type, definedInMethod && !definedInConstructor));
48194757
if (filterType(widened, t => !!(t.flags & ~TypeFlags.Nullable)) === neverType) {
@@ -4825,6 +4763,82 @@ namespace ts {
48254763
return widened;
48264764
}
48274765

4766+
function getJSDocTypeFromSpecialDeclarations(declaredType: Type | undefined, expression: Expression, _symbol: Symbol, declaration: Declaration) {
4767+
const typeNode = getJSDocType(expression.parent);
4768+
if (typeNode) {
4769+
const type = getWidenedType(getTypeFromTypeNode(typeNode));
4770+
if (!declaredType) {
4771+
return type;
4772+
}
4773+
else if (declaredType !== errorType && type !== errorType && !isTypeIdenticalTo(declaredType, type)) {
4774+
errorNextVariableOrPropertyDeclarationMustHaveSameType(declaredType, declaration, type);
4775+
}
4776+
}
4777+
return declaredType;
4778+
}
4779+
4780+
/** If we don't have an explicit JSDoc type, get the type from the initializer. */
4781+
function getInitializerTypeFromSpecialDeclarations(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: Expression, special: SpecialPropertyAssignmentKind) {
4782+
if (isBinaryExpression(expression)) {
4783+
const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right));
4784+
if (type.flags & TypeFlags.Object &&
4785+
special === SpecialPropertyAssignmentKind.ModuleExports &&
4786+
symbol.escapedName === InternalSymbolName.ExportEquals) {
4787+
const exportedType = resolveStructuredTypeMembers(type as ObjectType);
4788+
const members = createSymbolTable();
4789+
copyEntries(exportedType.members, members);
4790+
if (resolvedSymbol && !resolvedSymbol.exports) {
4791+
resolvedSymbol.exports = createSymbolTable();
4792+
}
4793+
(resolvedSymbol || symbol).exports!.forEach((s, name) => {
4794+
if (members.has(name)) {
4795+
const exportedMember = exportedType.members.get(name)!;
4796+
const union = createSymbol(s.flags | exportedMember.flags, name);
4797+
union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]);
4798+
members.set(name, union);
4799+
}
4800+
else {
4801+
members.set(name, s);
4802+
}
4803+
});
4804+
return createAnonymousType(
4805+
exportedType.symbol,
4806+
members,
4807+
exportedType.callSignatures,
4808+
exportedType.constructSignatures,
4809+
exportedType.stringIndexInfo,
4810+
exportedType.numberIndexInfo);
4811+
}
4812+
if (isEmptyArrayLiteralType(type)) {
4813+
if (noImplicitAny) {
4814+
reportImplicitAnyError(expression, anyArrayType);
4815+
}
4816+
return anyArrayType;
4817+
}
4818+
return type;
4819+
}
4820+
return neverType;
4821+
}
4822+
4823+
function isDeclarationInConstructor(expression: Expression) {
4824+
const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false);
4825+
// Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added.
4826+
// Function expressions that are assigned to the prototype count as methods.
4827+
return thisContainer.kind === SyntaxKind.Constructor ||
4828+
thisContainer.kind === SyntaxKind.FunctionDeclaration ||
4829+
(thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent));
4830+
}
4831+
4832+
function getConstructorDefinedThisAssignmentTypes(types: Type[], declarations: Declaration[]): Type[] | undefined {
4833+
Debug.assert(types.length === declarations.length);
4834+
return types.filter((_, i) => {
4835+
const declaration = declarations[i];
4836+
const expression = isBinaryExpression(declaration) ? declaration :
4837+
isBinaryExpression(declaration.parent) ? declaration.parent : undefined;
4838+
return expression && isDeclarationInConstructor(expression);
4839+
});
4840+
}
4841+
48284842
/** check for definition in base class if any declaration is in a class */
48294843
function getTypeOfSpecialPropertyOfBaseType(specialProperty: Symbol) {
48304844
const parentDeclaration = forEach(specialProperty.declarations, d => {

0 commit comments

Comments
 (0)