Skip to content

Commit cc67ce1

Browse files
authored
Property assignments in Typescript (#26368)
* Allow special property assignments in TS But only for functions and constant variable declarations initialised with functions. This specifically excludes class declarations and class expressions, which differs from Javascript. That's because Typescript supports `static` properties, which are equivalent to property assignments to a class. * Improve contextual typing predicate Don't think it's right yet, but probably closer? * More fixes. The code is still fantastically ugly, but everything works the way it should. Also update baselines, even where it is ill-advised. * Cleanup * Remove extra whitespace * Some kind of fix to isAnyDeclarationName It's not done yet. Specifically, in TS: Special property assignments are supposed to be declaration sites (but not all top-level assignments), and I think I got them to be. (But not sure). In JS: Special property assignments are supposed to be declaration sites (but not all top-level assignments), and I'm pretty sure ALL top-level assignments have been declaration sites for some time. This is incorrect, and probably means the predicate needs to be the same for both dialects. * Add fourslash and improve isAnyDeclarationName Now JS behaves the same as TS. * Cleanup from PR comments
1 parent 08eb99d commit cc67ce1

19 files changed

+886
-103
lines changed

src/compiler/binder.ts

+5
Original file line numberDiff line numberDiff line change
@@ -2480,6 +2480,11 @@ namespace ts {
24802480

24812481
function bindSpecialPropertyAssignment(node: BinaryExpression) {
24822482
const lhs = node.left as PropertyAccessEntityNameExpression;
2483+
// Class declarations in Typescript do not allow property declarations
2484+
const parentSymbol = lookupSymbolForPropertyAccess(lhs.expression);
2485+
if (!isInJavaScriptFile(node) && !isFunctionSymbol(parentSymbol)) {
2486+
return;
2487+
}
24832488
// Fix up parent pointers since we're going to use these nodes before we bind into them
24842489
node.left.parent = node;
24852490
node.right.parent = node;

src/compiler/checker.ts

+56-40
Original file line numberDiff line numberDiff line change
@@ -4821,48 +4821,45 @@ namespace ts {
48214821
}
48224822

48234823
/** If we don't have an explicit JSDoc type, get the type from the initializer. */
4824-
function getInitializerTypeFromSpecialDeclarations(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: Expression, special: SpecialPropertyAssignmentKind) {
4825-
if (isBinaryExpression(expression)) {
4826-
const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right));
4827-
if (type.flags & TypeFlags.Object &&
4828-
special === SpecialPropertyAssignmentKind.ModuleExports &&
4829-
symbol.escapedName === InternalSymbolName.ExportEquals) {
4830-
const exportedType = resolveStructuredTypeMembers(type as ObjectType);
4831-
const members = createSymbolTable();
4832-
copyEntries(exportedType.members, members);
4833-
if (resolvedSymbol && !resolvedSymbol.exports) {
4834-
resolvedSymbol.exports = createSymbolTable();
4835-
}
4836-
(resolvedSymbol || symbol).exports!.forEach((s, name) => {
4837-
if (members.has(name)) {
4838-
const exportedMember = exportedType.members.get(name)!;
4839-
const union = createSymbol(s.flags | exportedMember.flags, name);
4840-
union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]);
4841-
members.set(name, union);
4842-
}
4843-
else {
4844-
members.set(name, s);
4845-
}
4846-
});
4847-
const result = createAnonymousType(
4848-
exportedType.symbol,
4849-
members,
4850-
exportedType.callSignatures,
4851-
exportedType.constructSignatures,
4852-
exportedType.stringIndexInfo,
4853-
exportedType.numberIndexInfo);
4854-
result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag
4855-
return result;
4824+
function getInitializerTypeFromSpecialDeclarations(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: BinaryExpression, special: SpecialPropertyAssignmentKind) {
4825+
const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right));
4826+
if (type.flags & TypeFlags.Object &&
4827+
special === SpecialPropertyAssignmentKind.ModuleExports &&
4828+
symbol.escapedName === InternalSymbolName.ExportEquals) {
4829+
const exportedType = resolveStructuredTypeMembers(type as ObjectType);
4830+
const members = createSymbolTable();
4831+
copyEntries(exportedType.members, members);
4832+
if (resolvedSymbol && !resolvedSymbol.exports) {
4833+
resolvedSymbol.exports = createSymbolTable();
48564834
}
4857-
if (isEmptyArrayLiteralType(type)) {
4858-
if (noImplicitAny) {
4859-
reportImplicitAnyError(expression, anyArrayType);
4835+
(resolvedSymbol || symbol).exports!.forEach((s, name) => {
4836+
if (members.has(name)) {
4837+
const exportedMember = exportedType.members.get(name)!;
4838+
const union = createSymbol(s.flags | exportedMember.flags, name);
4839+
union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]);
4840+
members.set(name, union);
48604841
}
4861-
return anyArrayType;
4842+
else {
4843+
members.set(name, s);
4844+
}
4845+
});
4846+
const result = createAnonymousType(
4847+
exportedType.symbol,
4848+
members,
4849+
exportedType.callSignatures,
4850+
exportedType.constructSignatures,
4851+
exportedType.stringIndexInfo,
4852+
exportedType.numberIndexInfo);
4853+
result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag
4854+
return result;
4855+
}
4856+
if (isEmptyArrayLiteralType(type)) {
4857+
if (noImplicitAny) {
4858+
reportImplicitAnyError(expression, anyArrayType);
48624859
}
4863-
return type;
4860+
return anyArrayType;
48644861
}
4865-
return neverType;
4862+
return type;
48664863
}
48674864

48684865
function isDeclarationInConstructor(expression: Expression) {
@@ -5085,7 +5082,9 @@ namespace ts {
50855082
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
50865083
return getTypeOfFuncClassEnumModule(symbol);
50875084
}
5088-
type = tryGetTypeFromEffectiveTypeNode(declaration) || anyType;
5085+
type = isBinaryExpression(declaration.parent) ?
5086+
getWidenedTypeFromJSPropertyAssignments(symbol) :
5087+
tryGetTypeFromEffectiveTypeNode(declaration) || anyType;
50895088
}
50905089
else if (isPropertyAssignment(declaration)) {
50915090
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration);
@@ -16025,7 +16024,24 @@ namespace ts {
1602516024
case SpecialPropertyAssignmentKind.PrototypeProperty:
1602616025
// If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration.
1602716026
// See `bindStaticPropertyAssignment` in `binder.ts`.
16028-
return !binaryExpression.left.symbol || binaryExpression.left.symbol.valueDeclaration && !!getJSDocTypeTag(binaryExpression.left.symbol.valueDeclaration);
16027+
if (!binaryExpression.left.symbol) {
16028+
return true;
16029+
}
16030+
else {
16031+
const decl = binaryExpression.left.symbol.valueDeclaration;
16032+
if (!decl) {
16033+
return false;
16034+
}
16035+
if (isInJavaScriptFile(decl)) {
16036+
return !!getJSDocTypeTag(decl);
16037+
}
16038+
else if (isIdentifier((binaryExpression.left as PropertyAccessExpression).expression)) {
16039+
const id = (binaryExpression.left as PropertyAccessExpression).expression as Identifier;
16040+
const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true);
16041+
return !isFunctionSymbol(parentSymbol);
16042+
}
16043+
return true;
16044+
}
1602916045
case SpecialPropertyAssignmentKind.ThisProperty:
1603016046
case SpecialPropertyAssignmentKind.ModuleExports:
1603116047
return !binaryExpression.symbol || binaryExpression.symbol.valueDeclaration && !!getJSDocTypeTag(binaryExpression.symbol.valueDeclaration);

src/compiler/utilities.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -1725,12 +1725,15 @@ namespace ts {
17251725
}
17261726

17271727
export function getDeclarationOfJSInitializer(node: Node): Node | undefined {
1728-
if (!isInJavaScriptFile(node) || !node.parent) {
1728+
if (!node.parent) {
17291729
return undefined;
17301730
}
17311731
let name: Expression | BindingName | undefined;
17321732
let decl: Node | undefined;
17331733
if (isVariableDeclaration(node.parent) && node.parent.initializer === node) {
1734+
if (!isInJavaScriptFile(node) && !isVarConst(node.parent)) {
1735+
return undefined;
1736+
}
17341737
name = node.parent.name;
17351738
decl = node.parent;
17361739
}
@@ -1892,8 +1895,12 @@ namespace ts {
18921895
/// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property
18931896
/// assignments we treat as special in the binder
18941897
export function getSpecialPropertyAssignmentKind(expr: BinaryExpression): SpecialPropertyAssignmentKind {
1895-
if (!isInJavaScriptFile(expr) ||
1896-
expr.operatorToken.kind !== SyntaxKind.EqualsToken ||
1898+
const special = getSpecialPropertyAssignmentKindWorker(expr);
1899+
return special === SpecialPropertyAssignmentKind.Property || isInJavaScriptFile(expr) ? special : SpecialPropertyAssignmentKind.None;
1900+
}
1901+
1902+
function getSpecialPropertyAssignmentKindWorker(expr: BinaryExpression): SpecialPropertyAssignmentKind {
1903+
if (expr.operatorToken.kind !== SyntaxKind.EqualsToken ||
18971904
!isPropertyAccessExpression(expr.left)) {
18981905
return SpecialPropertyAssignmentKind.None;
18991906
}
@@ -1954,6 +1961,14 @@ namespace ts {
19541961
!!getJSDocTypeTag(expr.parent);
19551962
}
19561963

1964+
export function isFunctionSymbol(symbol: Symbol | undefined) {
1965+
if (!symbol || !symbol.valueDeclaration) {
1966+
return false;
1967+
}
1968+
const decl = symbol.valueDeclaration;
1969+
return decl.kind === SyntaxKind.FunctionDeclaration || isVariableDeclaration(decl) && decl.initializer && isFunctionLike(decl.initializer);
1970+
}
1971+
19571972
export function importFromModuleSpecifier(node: StringLiteralLike): AnyValidImportOrReExport {
19581973
return tryGetImportFromModuleSpecifier(node) || Debug.fail(Debug.showSyntaxKind(node.parent));
19591974
}
@@ -2354,7 +2369,10 @@ namespace ts {
23542369
}
23552370
else {
23562371
const binExp = name.parent.parent;
2357-
return isBinaryExpression(binExp) && getSpecialPropertyAssignmentKind(binExp) !== SpecialPropertyAssignmentKind.None && getNameOfDeclaration(binExp) === name;
2372+
return isBinaryExpression(binExp) &&
2373+
getSpecialPropertyAssignmentKind(binExp) !== SpecialPropertyAssignmentKind.None &&
2374+
(binExp.left.symbol || binExp.symbol) &&
2375+
getNameOfDeclaration(binExp) === name;
23582376
}
23592377
}
23602378
default:

tests/baselines/reference/fixSignatureCaching.errors.txt

+3-20
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ tests/cases/conformance/fixSignatureCaching.ts(474,38): error TS2339: Property '
2424
tests/cases/conformance/fixSignatureCaching.ts(481,22): error TS2339: Property 'findMatch' does not exist on type '{}'.
2525
tests/cases/conformance/fixSignatureCaching.ts(481,37): error TS2339: Property 'mobileDetectRules' does not exist on type '{}'.
2626
tests/cases/conformance/fixSignatureCaching.ts(489,18): error TS2339: Property 'isMobileFallback' does not exist on type '{}'.
27-
tests/cases/conformance/fixSignatureCaching.ts(490,39): error TS2339: Property 'isPhoneSized' does not exist on type '(userAgent: any, maxPhoneWidth: any) => void'.
2827
tests/cases/conformance/fixSignatureCaching.ts(492,37): error TS2339: Property 'FALLBACK_MOBILE' does not exist on type '{}'.
2928
tests/cases/conformance/fixSignatureCaching.ts(495,51): error TS2339: Property 'FALLBACK_PHONE' does not exist on type '{}'.
3029
tests/cases/conformance/fixSignatureCaching.ts(498,52): error TS2339: Property 'FALLBACK_TABLET' does not exist on type '{}'.
@@ -49,13 +48,8 @@ tests/cases/conformance/fixSignatureCaching.ts(872,25): error TS2339: Property '
4948
tests/cases/conformance/fixSignatureCaching.ts(893,25): error TS2339: Property 'getVersionStr' does not exist on type '{}'.
5049
tests/cases/conformance/fixSignatureCaching.ts(915,36): error TS2339: Property 'findMatches' does not exist on type '{}'.
5150
tests/cases/conformance/fixSignatureCaching.ts(915,53): error TS2339: Property 'mobileDetectRules' does not exist on type '{}'.
52-
tests/cases/conformance/fixSignatureCaching.ts(944,33): error TS2339: Property 'isPhoneSized' does not exist on type '(userAgent: any, maxPhoneWidth: any) => void'.
5351
tests/cases/conformance/fixSignatureCaching.ts(955,42): error TS2339: Property 'mobileGrade' does not exist on type '{}'.
54-
tests/cases/conformance/fixSignatureCaching.ts(963,22): error TS2339: Property 'isPhoneSized' does not exist on type '(userAgent: any, maxPhoneWidth: any) => void'.
5552
tests/cases/conformance/fixSignatureCaching.ts(964,57): error TS2339: Property 'getDeviceSmallerSide' does not exist on type '{}'.
56-
tests/cases/conformance/fixSignatureCaching.ts(967,22): error TS2339: Property 'isPhoneSized' does not exist on type '(userAgent: any, maxPhoneWidth: any) => void'.
57-
tests/cases/conformance/fixSignatureCaching.ts(971,18): error TS2339: Property '_impl' does not exist on type '(userAgent: any, maxPhoneWidth: any) => void'.
58-
tests/cases/conformance/fixSignatureCaching.ts(973,18): error TS2339: Property 'version' does not exist on type '(userAgent: any, maxPhoneWidth: any) => void'.
5953
tests/cases/conformance/fixSignatureCaching.ts(978,16): error TS2304: Cannot find name 'module'.
6054
tests/cases/conformance/fixSignatureCaching.ts(978,42): error TS2304: Cannot find name 'module'.
6155
tests/cases/conformance/fixSignatureCaching.ts(979,37): error TS2304: Cannot find name 'module'.
@@ -65,7 +59,7 @@ tests/cases/conformance/fixSignatureCaching.ts(981,16): error TS2304: Cannot fin
6559
tests/cases/conformance/fixSignatureCaching.ts(983,44): error TS2339: Property 'MobileDetect' does not exist on type 'Window'.
6660

6761

68-
==== tests/cases/conformance/fixSignatureCaching.ts (65 errors) ====
62+
==== tests/cases/conformance/fixSignatureCaching.ts (59 errors) ====
6963
// Repro from #10697
7064

7165
(function (define, undefined) {
@@ -608,8 +602,6 @@ tests/cases/conformance/fixSignatureCaching.ts(983,44): error TS2339: Property '
608602
~~~~~~~~~~~~~~~~
609603
!!! error TS2339: Property 'isMobileFallback' does not exist on type '{}'.
610604
phoneSized = MobileDetect.isPhoneSized(maxPhoneWidth);
611-
~~~~~~~~~~~~
612-
!!! error TS2339: Property 'isPhoneSized' does not exist on type '(userAgent: any, maxPhoneWidth: any) => void'.
613605
if (phoneSized === undefined) {
614606
cache.mobile = impl.FALLBACK_MOBILE;
615607
~~~~~~~~~~~~~~~
@@ -1112,8 +1104,6 @@ tests/cases/conformance/fixSignatureCaching.ts(983,44): error TS2339: Property '
11121104
*/
11131105
isPhoneSized: function (maxPhoneWidth) {
11141106
return MobileDetect.isPhoneSized(maxPhoneWidth || this.maxPhoneWidth);
1115-
~~~~~~~~~~~~
1116-
!!! error TS2339: Property 'isPhoneSized' does not exist on type '(userAgent: any, maxPhoneWidth: any) => void'.
11171107
},
11181108

11191109
/**
@@ -1135,26 +1125,18 @@ tests/cases/conformance/fixSignatureCaching.ts(983,44): error TS2339: Property '
11351125
// environment-dependent
11361126
if (typeof window !== 'undefined' && window.screen) {
11371127
MobileDetect.isPhoneSized = function (maxPhoneWidth) {
1138-
~~~~~~~~~~~~
1139-
!!! error TS2339: Property 'isPhoneSized' does not exist on type '(userAgent: any, maxPhoneWidth: any) => void'.
11401128
return maxPhoneWidth < 0 ? undefined : impl.getDeviceSmallerSide() <= maxPhoneWidth;
11411129
~~~~~~~~~~~~~~~~~~~~
11421130
!!! error TS2339: Property 'getDeviceSmallerSide' does not exist on type '{}'.
11431131
};
11441132
} else {
11451133
MobileDetect.isPhoneSized = function () {};
1146-
~~~~~~~~~~~~
1147-
!!! error TS2339: Property 'isPhoneSized' does not exist on type '(userAgent: any, maxPhoneWidth: any) => void'.
11481134
}
11491135

11501136
// should not be replaced by a completely new object - just overwrite existing methods
11511137
MobileDetect._impl = impl;
1152-
~~~~~
1153-
!!! error TS2339: Property '_impl' does not exist on type '(userAgent: any, maxPhoneWidth: any) => void'.
11541138

11551139
MobileDetect.version = '1.3.3 2016-07-31';
1156-
~~~~~~~
1157-
!!! error TS2339: Property 'version' does not exist on type '(userAgent: any, maxPhoneWidth: any) => void'.
11581140

11591141
return MobileDetect;
11601142
}); // end of call of define()
@@ -1183,4 +1165,5 @@ tests/cases/conformance/fixSignatureCaching.ts(983,44): error TS2339: Property '
11831165
// please file a bug if you get this error!
11841166
throw new Error('unknown environment');
11851167
}
1186-
})());
1168+
})());
1169+

tests/baselines/reference/fixSignatureCaching.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -986,7 +986,8 @@ define(function () {
986986
// please file a bug if you get this error!
987987
throw new Error('unknown environment');
988988
}
989-
})());
989+
})());
990+
990991

991992
//// [fixSignatureCaching.js]
992993
// Repro from #10697

0 commit comments

Comments
 (0)