2017-10-12 19:14:58 +02:00
|
|
|
/* @internal */
|
|
|
|
namespace ts.codefix {
|
2017-12-07 21:14:18 +01:00
|
|
|
const fixId = "inferFromUsage";
|
|
|
|
const errorCodes = [
|
|
|
|
// Variable declarations
|
|
|
|
Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code,
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2017-12-07 21:14:18 +01:00
|
|
|
// Variable uses
|
|
|
|
Diagnostics.Variable_0_implicitly_has_an_1_type.code,
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2017-12-07 21:14:18 +01:00
|
|
|
// Parameter declarations
|
|
|
|
Diagnostics.Parameter_0_implicitly_has_an_1_type.code,
|
|
|
|
Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code,
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2017-12-07 21:14:18 +01:00
|
|
|
// Get Accessor declarations
|
|
|
|
Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code,
|
|
|
|
Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code,
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2017-12-07 21:14:18 +01:00
|
|
|
// Set Accessor declarations
|
|
|
|
Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code,
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2017-12-07 21:14:18 +01:00
|
|
|
// Property declarations
|
|
|
|
Diagnostics.Member_0_implicitly_has_an_1_type.code,
|
2018-10-29 21:23:33 +01:00
|
|
|
|
|
|
|
//// Suggestions
|
|
|
|
// Variable declarations
|
|
|
|
Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_but_a_better_type_may_be_inferred_from_usage.code,
|
|
|
|
|
|
|
|
// Variable uses
|
|
|
|
Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
|
|
|
|
|
|
|
|
// Parameter declarations
|
|
|
|
Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
|
|
|
|
Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code,
|
|
|
|
|
|
|
|
// Get Accessor declarations
|
|
|
|
Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_get_accessor_may_be_inferred_from_usage.code,
|
|
|
|
Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage.code,
|
|
|
|
|
|
|
|
// Set Accessor declarations
|
|
|
|
Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_set_accessor_may_be_inferred_from_usage.code,
|
|
|
|
|
|
|
|
// Property declarations
|
|
|
|
Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
|
2019-06-18 04:00:15 +02:00
|
|
|
|
|
|
|
// Function expressions and declarations
|
|
|
|
Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code,
|
2017-12-07 21:14:18 +01:00
|
|
|
];
|
|
|
|
registerCodeFix({
|
|
|
|
errorCodes,
|
2018-03-07 23:40:50 +01:00
|
|
|
getCodeActions(context) {
|
2020-02-11 02:13:35 +01:00
|
|
|
const { sourceFile, program, span: { start }, errorCode, cancellationToken, host, preferences } = context;
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2018-06-27 01:20:44 +02:00
|
|
|
const token = getTokenAtPosition(sourceFile, start);
|
2020-02-11 02:13:35 +01:00
|
|
|
let declaration: Declaration | undefined;
|
2021-08-16 22:53:51 +02:00
|
|
|
const changes = textChanges.ChangeTracker.with(context, changes => {
|
|
|
|
declaration = doChange(changes, sourceFile, token, errorCode, program, cancellationToken, /*markSeen*/ returnTrue, host, preferences);
|
|
|
|
});
|
2018-06-27 22:56:31 +02:00
|
|
|
const name = declaration && getNameOfDeclaration(declaration);
|
2018-06-21 19:01:39 +02:00
|
|
|
return !name || changes.length === 0 ? undefined
|
|
|
|
: [createCodeFixAction(fixId, changes, [getDiagnostic(errorCode, token), name.getText(sourceFile)], fixId, Diagnostics.Infer_all_types_from_usage)];
|
2017-12-07 21:14:18 +01:00
|
|
|
},
|
|
|
|
fixIds: [fixId],
|
|
|
|
getAllCodeActions(context) {
|
2020-02-11 02:13:35 +01:00
|
|
|
const { sourceFile, program, cancellationToken, host, preferences } = context;
|
2018-05-16 21:28:27 +02:00
|
|
|
const markSeen = nodeSeenTracker();
|
2018-03-07 23:40:50 +01:00
|
|
|
return codeFixAll(context, errorCodes, (changes, err) => {
|
2020-02-11 02:13:35 +01:00
|
|
|
doChange(changes, sourceFile, getTokenAtPosition(err.file, err.start), err.code, program, cancellationToken, markSeen, host, preferences);
|
2017-12-07 21:14:18 +01:00
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2017-12-07 21:14:18 +01:00
|
|
|
function getDiagnostic(errorCode: number, token: Node): DiagnosticMessage {
|
|
|
|
switch (errorCode) {
|
|
|
|
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
|
2018-10-29 21:23:33 +01:00
|
|
|
case Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
|
2018-10-09 19:38:46 +02:00
|
|
|
return isSetAccessorDeclaration(getContainingFunction(token)!) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; // TODO: GH#18217
|
2017-12-07 21:14:18 +01:00
|
|
|
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
|
2018-10-29 21:23:33 +01:00
|
|
|
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code:
|
2017-12-07 21:14:18 +01:00
|
|
|
return Diagnostics.Infer_parameter_types_from_usage;
|
2019-06-18 04:00:15 +02:00
|
|
|
case Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code:
|
|
|
|
return Diagnostics.Infer_this_type_of_0_from_usage;
|
2017-12-07 21:14:18 +01:00
|
|
|
default:
|
|
|
|
return Diagnostics.Infer_type_of_0_from_usage;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
2017-12-07 21:14:18 +01:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2018-10-29 21:23:33 +01:00
|
|
|
/** Map suggestion code to error code */
|
|
|
|
function mapSuggestionDiagnostic(errorCode: number) {
|
|
|
|
switch (errorCode) {
|
|
|
|
case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_but_a_better_type_may_be_inferred_from_usage.code:
|
|
|
|
return Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code;
|
|
|
|
case Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
|
|
|
|
return Diagnostics.Variable_0_implicitly_has_an_1_type.code;
|
|
|
|
case Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
|
|
|
|
return Diagnostics.Parameter_0_implicitly_has_an_1_type.code;
|
|
|
|
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code:
|
|
|
|
return Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code;
|
|
|
|
case Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_get_accessor_may_be_inferred_from_usage.code:
|
|
|
|
return Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code;
|
|
|
|
case Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage.code:
|
|
|
|
return Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code;
|
|
|
|
case Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_set_accessor_may_be_inferred_from_usage.code:
|
|
|
|
return Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code;
|
|
|
|
case Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
|
|
|
|
return Diagnostics.Member_0_implicitly_has_an_1_type.code;
|
|
|
|
}
|
|
|
|
return errorCode;
|
|
|
|
}
|
|
|
|
|
2020-02-11 02:13:35 +01:00
|
|
|
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker, host: LanguageServiceHost, preferences: UserPreferences): Declaration | undefined {
|
2018-10-09 19:38:46 +02:00
|
|
|
if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken && token.kind !== SyntaxKind.ThisKeyword) {
|
2017-12-07 21:14:18 +01:00
|
|
|
return undefined;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
|
2018-03-14 18:57:38 +01:00
|
|
|
const { parent } = token;
|
2020-02-11 02:13:35 +01:00
|
|
|
const importAdder = createImportAdder(sourceFile, program, preferences, host);
|
2018-10-29 21:23:33 +01:00
|
|
|
errorCode = mapSuggestionDiagnostic(errorCode);
|
2017-10-12 19:14:58 +02:00
|
|
|
switch (errorCode) {
|
|
|
|
// Variable and Property declarations
|
|
|
|
case Diagnostics.Member_0_implicitly_has_an_1_type.code:
|
|
|
|
case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code:
|
2018-05-16 21:28:27 +02:00
|
|
|
if ((isVariableDeclaration(parent) && markSeen(parent)) || isPropertyDeclaration(parent) || isPropertySignature(parent)) { // handle bad location
|
2020-02-11 02:13:35 +01:00
|
|
|
annotateVariableDeclaration(changes, importAdder, sourceFile, parent, program, host, cancellationToken);
|
|
|
|
importAdder.writeFixes(changes);
|
2018-03-14 18:57:38 +01:00
|
|
|
return parent;
|
|
|
|
}
|
2018-10-09 19:38:46 +02:00
|
|
|
if (isPropertyAccessExpression(parent)) {
|
|
|
|
const type = inferTypeForVariableFromUsage(parent.name, program, cancellationToken);
|
2018-10-29 21:23:33 +01:00
|
|
|
const typeNode = getTypeNodeIfAccessible(type, parent, program, host);
|
2018-10-09 19:38:46 +02:00
|
|
|
if (typeNode) {
|
2018-10-25 01:14:52 +02:00
|
|
|
// Note that the codefix will never fire with an existing `@type` tag, so there is no need to merge tags
|
2021-03-23 00:39:35 +01:00
|
|
|
const typeTag = factory.createJSDocTypeTag(/*tagName*/ undefined, factory.createJSDocTypeExpression(typeNode), /*comment*/ undefined);
|
2018-10-25 01:14:52 +02:00
|
|
|
addJSDocTags(changes, sourceFile, cast(parent.parent.parent, isExpressionStatement), [typeTag]);
|
2018-10-09 19:38:46 +02:00
|
|
|
}
|
2020-02-11 02:13:35 +01:00
|
|
|
importAdder.writeFixes(changes);
|
2018-10-09 19:38:46 +02:00
|
|
|
return parent;
|
|
|
|
}
|
2018-03-14 18:57:38 +01:00
|
|
|
return undefined;
|
2017-12-07 21:14:18 +01:00
|
|
|
|
|
|
|
case Diagnostics.Variable_0_implicitly_has_an_1_type.code: {
|
|
|
|
const symbol = program.getTypeChecker().getSymbolAtLocation(token);
|
2018-05-16 21:28:27 +02:00
|
|
|
if (symbol && symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && markSeen(symbol.valueDeclaration)) {
|
2020-02-11 02:13:35 +01:00
|
|
|
annotateVariableDeclaration(changes, importAdder, sourceFile, symbol.valueDeclaration, program, host, cancellationToken);
|
|
|
|
importAdder.writeFixes(changes);
|
2018-03-07 23:40:50 +01:00
|
|
|
return symbol.valueDeclaration;
|
|
|
|
}
|
2018-05-16 21:28:27 +02:00
|
|
|
return undefined;
|
2017-12-07 21:14:18 +01:00
|
|
|
}
|
2017-12-07 23:27:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const containingFunction = getContainingFunction(token);
|
|
|
|
if (containingFunction === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2020-02-11 02:13:35 +01:00
|
|
|
let declaration: Declaration | undefined;
|
2018-02-12 19:34:17 +01:00
|
|
|
switch (errorCode) {
|
2017-10-12 19:14:58 +02:00
|
|
|
// Parameter declarations
|
|
|
|
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
|
2018-10-09 19:38:46 +02:00
|
|
|
if (isSetAccessorDeclaration(containingFunction)) {
|
2020-02-11 02:13:35 +01:00
|
|
|
annotateSetAccessor(changes, importAdder, sourceFile, containingFunction, program, host, cancellationToken);
|
|
|
|
declaration = containingFunction;
|
|
|
|
break;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
2017-12-07 21:14:18 +01:00
|
|
|
// falls through
|
2017-10-12 19:14:58 +02:00
|
|
|
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
|
2018-05-16 21:28:27 +02:00
|
|
|
if (markSeen(containingFunction)) {
|
2018-03-14 18:57:38 +01:00
|
|
|
const param = cast(parent, isParameter);
|
2020-02-11 02:13:35 +01:00
|
|
|
annotateParameters(changes, importAdder, sourceFile, param, containingFunction, program, host, cancellationToken);
|
|
|
|
declaration = param;
|
2018-03-07 23:40:50 +01:00
|
|
|
}
|
2020-02-11 02:13:35 +01:00
|
|
|
break;
|
2017-10-12 19:14:58 +02:00
|
|
|
|
|
|
|
// Get Accessor declarations
|
|
|
|
case Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code:
|
|
|
|
case Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code:
|
2018-10-09 19:38:46 +02:00
|
|
|
if (isGetAccessorDeclaration(containingFunction) && isIdentifier(containingFunction.name)) {
|
2020-02-11 02:13:35 +01:00
|
|
|
annotate(changes, importAdder, sourceFile, containingFunction, inferTypeForVariableFromUsage(containingFunction.name, program, cancellationToken), program, host);
|
|
|
|
declaration = containingFunction;
|
2018-03-07 23:40:50 +01:00
|
|
|
}
|
2020-02-11 02:13:35 +01:00
|
|
|
break;
|
2017-10-12 19:14:58 +02:00
|
|
|
|
|
|
|
// Set Accessor declarations
|
|
|
|
case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code:
|
2018-10-09 19:38:46 +02:00
|
|
|
if (isSetAccessorDeclaration(containingFunction)) {
|
2020-02-11 02:13:35 +01:00
|
|
|
annotateSetAccessor(changes, importAdder, sourceFile, containingFunction, program, host, cancellationToken);
|
|
|
|
declaration = containingFunction;
|
2018-03-07 23:40:50 +01:00
|
|
|
}
|
2020-02-11 02:13:35 +01:00
|
|
|
break;
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2019-06-18 04:00:15 +02:00
|
|
|
// Function 'this'
|
|
|
|
case Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code:
|
|
|
|
if (textChanges.isThisTypeAnnotatable(containingFunction) && markSeen(containingFunction)) {
|
|
|
|
annotateThis(changes, sourceFile, containingFunction, program, host, cancellationToken);
|
2020-02-11 02:13:35 +01:00
|
|
|
declaration = containingFunction;
|
2019-06-18 04:00:15 +02:00
|
|
|
}
|
2020-02-11 02:13:35 +01:00
|
|
|
break;
|
2019-06-18 04:00:15 +02:00
|
|
|
|
2017-12-07 21:14:18 +01:00
|
|
|
default:
|
2018-03-02 19:44:06 +01:00
|
|
|
return Debug.fail(String(errorCode));
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
2020-02-11 02:13:35 +01:00
|
|
|
|
|
|
|
importAdder.writeFixes(changes);
|
|
|
|
return declaration;
|
2017-12-07 21:14:18 +01:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2019-10-14 19:33:00 +02:00
|
|
|
function annotateVariableDeclaration(
|
|
|
|
changes: textChanges.ChangeTracker,
|
2020-02-11 02:13:35 +01:00
|
|
|
importAdder: ImportAdder,
|
2019-10-14 19:33:00 +02:00
|
|
|
sourceFile: SourceFile,
|
|
|
|
declaration: VariableDeclaration | PropertyDeclaration | PropertySignature,
|
|
|
|
program: Program,
|
|
|
|
host: LanguageServiceHost,
|
|
|
|
cancellationToken: CancellationToken,
|
|
|
|
): void {
|
2018-03-07 23:40:50 +01:00
|
|
|
if (isIdentifier(declaration.name)) {
|
2020-02-11 02:13:35 +01:00
|
|
|
annotate(changes, importAdder, sourceFile, declaration, inferTypeForVariableFromUsage(declaration.name, program, cancellationToken), program, host);
|
2018-03-07 23:40:50 +01:00
|
|
|
}
|
2017-12-07 21:14:18 +01:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2019-10-14 19:33:00 +02:00
|
|
|
function annotateParameters(
|
|
|
|
changes: textChanges.ChangeTracker,
|
2020-02-11 02:13:35 +01:00
|
|
|
importAdder: ImportAdder,
|
2019-10-14 19:33:00 +02:00
|
|
|
sourceFile: SourceFile,
|
|
|
|
parameterDeclaration: ParameterDeclaration,
|
2020-10-06 16:51:01 +02:00
|
|
|
containingFunction: SignatureDeclaration,
|
2019-10-14 19:33:00 +02:00
|
|
|
program: Program,
|
|
|
|
host: LanguageServiceHost,
|
|
|
|
cancellationToken: CancellationToken,
|
|
|
|
): void {
|
2018-12-04 18:03:08 +01:00
|
|
|
if (!isIdentifier(parameterDeclaration.name)) {
|
2018-03-07 23:40:50 +01:00
|
|
|
return;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
2019-06-18 04:00:15 +02:00
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
const parameterInferences = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken);
|
2019-08-21 23:22:17 +02:00
|
|
|
Debug.assert(containingFunction.parameters.length === parameterInferences.length, "Parameter count and inference count should match");
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2018-10-09 19:38:46 +02:00
|
|
|
if (isInJSFile(containingFunction)) {
|
|
|
|
annotateJSDocParameters(changes, sourceFile, parameterInferences, program, host);
|
|
|
|
}
|
|
|
|
else {
|
2018-12-04 18:03:08 +01:00
|
|
|
const needParens = isArrowFunction(containingFunction) && !findChildOfKind(containingFunction, SyntaxKind.OpenParenToken, sourceFile);
|
2020-06-17 01:55:03 +02:00
|
|
|
if (needParens) changes.insertNodeBefore(sourceFile, first(containingFunction.parameters), factory.createToken(SyntaxKind.OpenParenToken));
|
2018-10-09 19:38:46 +02:00
|
|
|
for (const { declaration, type } of parameterInferences) {
|
|
|
|
if (declaration && !declaration.type && !declaration.initializer) {
|
2020-02-11 02:13:35 +01:00
|
|
|
annotate(changes, importAdder, sourceFile, declaration, type, program, host);
|
2018-10-09 19:38:46 +02:00
|
|
|
}
|
2018-03-07 23:40:50 +01:00
|
|
|
}
|
2020-06-17 01:55:03 +02:00
|
|
|
if (needParens) changes.insertNodeAfter(sourceFile, last(containingFunction.parameters), factory.createToken(SyntaxKind.CloseParenToken));
|
2018-10-09 19:38:46 +02:00
|
|
|
}
|
2017-12-07 21:14:18 +01:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2019-06-18 04:00:15 +02:00
|
|
|
function annotateThis(changes: textChanges.ChangeTracker, sourceFile: SourceFile, containingFunction: textChanges.ThisTypeAnnotatable, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken) {
|
2019-08-27 18:02:58 +02:00
|
|
|
const references = getFunctionReferences(containingFunction, sourceFile, program, cancellationToken);
|
|
|
|
if (!references || !references.length) {
|
2019-06-18 04:00:15 +02:00
|
|
|
return;
|
|
|
|
}
|
2019-08-27 18:02:58 +02:00
|
|
|
const thisInference = inferTypeFromReferences(program, references, cancellationToken).thisParameter();
|
2019-06-18 04:00:15 +02:00
|
|
|
const typeNode = getTypeNodeIfAccessible(thisInference, containingFunction, program, host);
|
|
|
|
if (!typeNode) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isInJSFile(containingFunction)) {
|
|
|
|
annotateJSDocThis(changes, sourceFile, containingFunction, typeNode);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
changes.tryInsertThisTypeAnnotation(sourceFile, containingFunction, typeNode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-06 16:51:01 +02:00
|
|
|
function annotateJSDocThis(changes: textChanges.ChangeTracker, sourceFile: SourceFile, containingFunction: SignatureDeclaration, typeNode: TypeNode) {
|
2019-06-18 04:00:15 +02:00
|
|
|
addJSDocTags(changes, sourceFile, containingFunction, [
|
2020-06-17 01:55:03 +02:00
|
|
|
factory.createJSDocThisTag(/*tagName*/ undefined, factory.createJSDocTypeExpression(typeNode)),
|
2019-06-18 04:00:15 +02:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2019-10-14 19:33:00 +02:00
|
|
|
function annotateSetAccessor(
|
|
|
|
changes: textChanges.ChangeTracker,
|
2020-02-11 02:13:35 +01:00
|
|
|
importAdder: ImportAdder,
|
2019-10-14 19:33:00 +02:00
|
|
|
sourceFile: SourceFile,
|
|
|
|
setAccessorDeclaration: SetAccessorDeclaration,
|
|
|
|
program: Program,
|
|
|
|
host: LanguageServiceHost,
|
|
|
|
cancellationToken: CancellationToken,
|
2021-05-24 22:01:58 +02:00
|
|
|
|
2019-10-14 19:33:00 +02:00
|
|
|
): void {
|
2018-03-07 23:40:50 +01:00
|
|
|
const param = firstOrUndefined(setAccessorDeclaration.parameters);
|
|
|
|
if (param && isIdentifier(setAccessorDeclaration.name) && isIdentifier(param.name)) {
|
2018-10-29 21:23:33 +01:00
|
|
|
let type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, program, cancellationToken);
|
|
|
|
if (type === program.getTypeChecker().getAnyType()) {
|
|
|
|
type = inferTypeForVariableFromUsage(param.name, program, cancellationToken);
|
|
|
|
}
|
2018-10-09 19:38:46 +02:00
|
|
|
if (isInJSFile(setAccessorDeclaration)) {
|
|
|
|
annotateJSDocParameters(changes, sourceFile, [{ declaration: param, type }], program, host);
|
|
|
|
}
|
|
|
|
else {
|
2020-02-11 02:13:35 +01:00
|
|
|
annotate(changes, importAdder, sourceFile, param, type, program, host);
|
2018-10-09 19:38:46 +02:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
2017-12-07 21:14:18 +01:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2020-02-11 02:13:35 +01:00
|
|
|
function annotate(changes: textChanges.ChangeTracker, importAdder: ImportAdder, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type, program: Program, host: LanguageServiceHost): void {
|
2018-10-29 21:23:33 +01:00
|
|
|
const typeNode = getTypeNodeIfAccessible(type, declaration, program, host);
|
2018-10-09 19:38:46 +02:00
|
|
|
if (typeNode) {
|
|
|
|
if (isInJSFile(sourceFile) && declaration.kind !== SyntaxKind.PropertySignature) {
|
2018-10-25 01:14:52 +02:00
|
|
|
const parent = isVariableDeclaration(declaration) ? tryCast(declaration.parent.parent, isVariableStatement) : declaration;
|
|
|
|
if (!parent) {
|
|
|
|
return;
|
|
|
|
}
|
2020-06-17 01:55:03 +02:00
|
|
|
const typeExpression = factory.createJSDocTypeExpression(typeNode);
|
2021-03-23 00:39:35 +01:00
|
|
|
const typeTag = isGetAccessorDeclaration(declaration) ? factory.createJSDocReturnTag(/*tagName*/ undefined, typeExpression, /*comment*/ undefined) : factory.createJSDocTypeTag(/*tagName*/ undefined, typeExpression, /*comment*/ undefined);
|
2018-10-25 01:14:52 +02:00
|
|
|
addJSDocTags(changes, sourceFile, parent, [typeTag]);
|
2018-10-09 19:38:46 +02:00
|
|
|
}
|
2020-07-07 22:11:23 +02:00
|
|
|
else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, declaration, sourceFile, changes, importAdder, getEmitScriptTarget(program.getCompilerOptions()))) {
|
2018-10-09 19:38:46 +02:00
|
|
|
changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode);
|
|
|
|
}
|
|
|
|
}
|
2017-12-07 21:14:18 +01:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2020-02-11 02:13:35 +01:00
|
|
|
function tryReplaceImportTypeNodeWithAutoImport(
|
|
|
|
typeNode: TypeNode,
|
|
|
|
declaration: textChanges.TypeAnnotatable,
|
|
|
|
sourceFile: SourceFile,
|
|
|
|
changes: textChanges.ChangeTracker,
|
|
|
|
importAdder: ImportAdder,
|
|
|
|
scriptTarget: ScriptTarget
|
|
|
|
): boolean {
|
2020-07-07 22:11:23 +02:00
|
|
|
const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget);
|
|
|
|
if (importableReference && changes.tryInsertTypeAnnotation(sourceFile, declaration, importableReference.typeNode)) {
|
2020-02-11 02:13:35 +01:00
|
|
|
forEach(importableReference.symbols, s => importAdder.addImportFromExportedSymbol(s, /*usageIsTypeOnly*/ true));
|
|
|
|
return true;
|
2019-10-14 19:33:00 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-08-08 20:30:18 +02:00
|
|
|
function annotateJSDocParameters(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parameterInferences: readonly ParameterInference[], program: Program, host: LanguageServiceHost): void {
|
2018-10-25 01:14:52 +02:00
|
|
|
const signature = parameterInferences.length && parameterInferences[0].declaration.parent;
|
|
|
|
if (!signature) {
|
|
|
|
return;
|
|
|
|
}
|
2020-12-05 01:52:12 +01:00
|
|
|
|
|
|
|
const inferences = mapDefined(parameterInferences, inference => {
|
2018-10-09 19:38:46 +02:00
|
|
|
const param = inference.declaration;
|
2018-10-25 01:14:52 +02:00
|
|
|
// only infer parameters that have (1) no type and (2) an accessible inferred type
|
2020-12-05 01:52:12 +01:00
|
|
|
if (param.initializer || getJSDocType(param) || !isIdentifier(param.name)) {
|
|
|
|
return;
|
|
|
|
}
|
2018-10-09 19:38:46 +02:00
|
|
|
const typeNode = inference.type && getTypeNodeIfAccessible(inference.type, param, program, host);
|
2020-12-05 01:52:12 +01:00
|
|
|
if (typeNode) {
|
|
|
|
const name = factory.cloneNode(param.name);
|
|
|
|
setEmitFlags(name, EmitFlags.NoComments | EmitFlags.NoNestedComments);
|
|
|
|
return { name: factory.cloneNode(param.name), param, isOptional: !!inference.isOptional, typeNode };
|
|
|
|
}
|
2018-10-09 19:38:46 +02:00
|
|
|
});
|
2020-12-05 01:52:12 +01:00
|
|
|
|
|
|
|
if (!inferences.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isArrowFunction(signature) || isFunctionExpression(signature)) {
|
|
|
|
const needParens = isArrowFunction(signature) && !findChildOfKind(signature, SyntaxKind.OpenParenToken, sourceFile);
|
|
|
|
if (needParens) {
|
|
|
|
changes.insertNodeBefore(sourceFile, first(signature.parameters), factory.createToken(SyntaxKind.OpenParenToken));
|
|
|
|
}
|
|
|
|
|
|
|
|
forEach(inferences, ({ typeNode, param }) => {
|
|
|
|
const typeTag = factory.createJSDocTypeTag(/*tagName*/ undefined, factory.createJSDocTypeExpression(typeNode));
|
|
|
|
const jsDoc = factory.createJSDocComment(/*comment*/ undefined, [typeTag]);
|
|
|
|
changes.insertNodeAt(sourceFile, param.getStart(sourceFile), jsDoc, { suffix: " " });
|
|
|
|
});
|
|
|
|
|
|
|
|
if (needParens) {
|
|
|
|
changes.insertNodeAfter(sourceFile, last(signature.parameters), factory.createToken(SyntaxKind.CloseParenToken));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
const paramTags = map(inferences, ({ name, typeNode, isOptional }) =>
|
2021-03-23 00:39:35 +01:00
|
|
|
factory.createJSDocParameterTag(/*tagName*/ undefined, name, /*isBracketed*/ !!isOptional, factory.createJSDocTypeExpression(typeNode), /* isNameFirst */ false, /*comment*/ undefined));
|
2020-12-05 01:52:12 +01:00
|
|
|
addJSDocTags(changes, sourceFile, signature, paramTags);
|
|
|
|
}
|
2018-10-25 01:14:52 +02:00
|
|
|
}
|
|
|
|
|
2020-03-17 21:00:14 +01:00
|
|
|
export function addJSDocTags(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parent: HasJSDoc, newTags: readonly JSDocTag[]): void {
|
2021-05-24 22:01:58 +02:00
|
|
|
const comments = flatMap(parent.jsDoc, j => typeof j.comment === "string" ? factory.createJSDocText(j.comment) : j.comment) as JSDocComment[];
|
2018-10-29 19:12:51 +01:00
|
|
|
const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags);
|
2018-10-25 01:14:52 +02:00
|
|
|
const unmergedNewTags = newTags.filter(newTag => !oldTags || !oldTags.some((tag, i) => {
|
|
|
|
const merged = tryMergeJsdocTags(tag, newTag);
|
|
|
|
if (merged) oldTags[i] = merged;
|
|
|
|
return !!merged;
|
|
|
|
}));
|
2021-03-23 00:39:35 +01:00
|
|
|
const tag = factory.createJSDocComment(factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))), factory.createNodeArray([...(oldTags || emptyArray), ...unmergedNewTags]));
|
2019-01-26 16:59:32 +01:00
|
|
|
const jsDocNode = parent.kind === SyntaxKind.ArrowFunction ? getJsDocNodeForArrowFunction(parent) : parent;
|
|
|
|
jsDocNode.jsDoc = parent.jsDoc;
|
|
|
|
jsDocNode.jsDocCache = parent.jsDocCache;
|
|
|
|
changes.insertJsdocCommentBefore(sourceFile, jsDocNode, tag);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getJsDocNodeForArrowFunction(signature: ArrowFunction): HasJSDoc {
|
|
|
|
if (signature.parent.kind === SyntaxKind.PropertyDeclaration) {
|
2021-05-18 15:20:57 +02:00
|
|
|
return signature.parent as HasJSDoc;
|
2019-01-26 16:59:32 +01:00
|
|
|
}
|
2021-05-18 15:20:57 +02:00
|
|
|
return signature.parent.parent as HasJSDoc;
|
2018-10-25 01:14:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function tryMergeJsdocTags(oldTag: JSDocTag, newTag: JSDocTag): JSDocTag | undefined {
|
|
|
|
if (oldTag.kind !== newTag.kind) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
switch (oldTag.kind) {
|
|
|
|
case SyntaxKind.JSDocParameterTag: {
|
|
|
|
const oldParam = oldTag as JSDocParameterTag;
|
|
|
|
const newParam = newTag as JSDocParameterTag;
|
|
|
|
return isIdentifier(oldParam.name) && isIdentifier(newParam.name) && oldParam.name.escapedText === newParam.name.escapedText
|
2020-06-17 01:55:03 +02:00
|
|
|
? factory.createJSDocParameterTag(/*tagName*/ undefined, newParam.name, /*isBracketed*/ false, newParam.typeExpression, newParam.isNameFirst, oldParam.comment)
|
2018-10-25 01:14:52 +02:00
|
|
|
: undefined;
|
|
|
|
}
|
|
|
|
case SyntaxKind.JSDocReturnTag:
|
2020-06-17 01:55:03 +02:00
|
|
|
return factory.createJSDocReturnTag(/*tagName*/ undefined, (newTag as JSDocReturnTag).typeExpression, oldTag.comment);
|
2018-10-25 01:14:52 +02:00
|
|
|
}
|
2018-10-09 19:38:46 +02:00
|
|
|
}
|
|
|
|
|
2019-08-08 20:30:18 +02:00
|
|
|
function getReferences(token: PropertyName | Token<SyntaxKind.ConstructorKeyword>, program: Program, cancellationToken: CancellationToken): readonly Identifier[] {
|
2018-02-07 22:25:36 +01:00
|
|
|
// Position shouldn't matter since token is not a SourceFile.
|
|
|
|
return mapDefined(FindAllReferences.getReferenceEntriesForNode(-1, token, program, program.getSourceFiles(), cancellationToken), entry =>
|
2018-09-27 01:58:01 +02:00
|
|
|
entry.kind !== FindAllReferences.EntryKind.Span ? tryCast(entry.node, isIdentifier) : undefined);
|
2017-12-07 21:14:18 +01:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
|
Private named instance fields (#30829)
* Fix display of private names in language server
Signed-off-by: Joseph Watts <jwatts43@bloomberg.net>
Signed-off-by: Max Heiber <max.heiber@gmail.com>
* Parse private names
Signed-off-by: Max Heiber <max.heiber@gmail.com>
* update parser error recovery tests to use ¬ not #
The intent of the tests seemed to be to
regiment the behavior of the parser
when a weird symbol is encountered.
The `#` is now taken by private identifiers,
so `¬` seems like a good new choice for
keeping the diff small, since it also fits in
16 bits (wide emojis would be treated
as multiple characters, since the scanner
uses ++).
Signed-off-by: Max Heiber <max.heiber@gmail.com>
* Private Name Support in the Checker (#5)
- [x] treat private names as unique:
- case 1: cannot say that a variable is of a class type unless the variable points to an instance of the class
- see [test](https://github.com/mheiber/TypeScript/tree/checker/tests/cases/conformance/classes/members/privateNames/privateNamesUnique.ts)
- case 2: private names in class hierarchies do not conflict
- see [test](https://github.com/mheiber/TypeScript/tree/checker/tests/cases/conformance/classes/members/privateNames/privateNamesNoConflictWhenInheriting.ts)
- [x] `#constructor` is reserved
- see [test](https://github.com/mheiber/TypeScript/tree/checker/tests/cases/conformance/classes/members/privateNames/privateNameConstructorReserved.ts)
- check in `bindWorker`, where every node is visited
- [x] Accessibility modifiers can't be used with private names
- see [test](https://github.com/mheiber/TypeScript/tree/checker/tests/cases/conformance/classes/members/privateNames/privateNamesNoAccessibilityModifiers.ts)
- implemented in `checkAccessibilityModifiers`, using `ModifierFlags.AccessibilityModifier`
- [x] `delete #foo` not allowed
- [x] Private name accesses not allowed outside of the defining class
- see test: https://github.com/mheiber/TypeScript/tree/checker/tests/cases/conformance/classes/members/privateNames/privateNameNotAccessibleOutsideDefiningClass.ts
- see [test](https://github.com/mheiber/TypeScript/tree/checker/tests/cases/conformance/classes/members/privateNames/privateNamesNoDelete.ts)
- implemented in `checkDeleteExpression`
- [x] Do [the right thing](https://gist.github.com/mheiber/b6fc7adb426c2e1cdaceb5d7786fc630) for nested classes
mv private name tests together
more checker tests for private names
update naming and cleanup for check private names
for private name support in the checker:
- make names more consistent
- remove unnecessary checks
- add utility function to public API
- other small cleanup
Move getPropertyNameForUniqueESSymbol to utility
for consistency with other calculation of
special property names (starting with __),
move the calculation of property names for
unique es symbols to `utilities.ts`.
private name tests strict+es6
Update private name tests to use 'strict'
type checking and to target es6 instead of
default. Makes the js output easier to read
and tests more surface area with other
checker features.
error message for private names in obj literals
Disallow decorating private-named properties
because the spec is still in flux.
Signed-off-by: Max Heiber <max.heiber@gmail.com>
* Add private instance field transformation, pr feedback
Implements private instance fields on top of the class properties refactor.
This commit also includes incorporation of PR feedback on the
checker, parser, and transformer in order to make the rebase
manageable.
Co-Authored-By: Max Heiber <max.heiber@gmail.com>
Co-Authored-By: Ron Buckton <ron.buckton@microsoft.com>
Signed-off-by: Joey Watts <jwatts43@bloomberg.net>
Signed-off-by: Max Heiber <max.heiber@gmail.com>
Incorporate PR feedback
Fix checker crash with new block scoped bindings
Add classExpressionInLoop test
Update baselines for private name errors
Apply suggestions from code review
Fix privateNameFieldCallExpression test
Remove unnecessary comment
Fix PropertyAccessEntityNameExpression type
Remove PrivateName from PropertyNameLiteral
Add createPrivateName
Remove PrivateName type from textSourceNode
Add Debug asserts for invalid syntax kinds
Don't output private name syntax when undeclared
Make PrivateName extend Node
Update baselines for public API
Fix completions in language server
Fix fourslash test crash
intern private name descriptions
undo expensive node.name.parent assignment
Back the way things were on `master`: only
assign node.name.parent when we need to
Add tests for private names and JSDoc
Patch hoverOverPrivateName fourslash test
Fix goToDefinition for private-named fields
remove Debug.fail for private name outside class
Remove Debug.fail in binder.ts::`getDeclarationName`.
It turns out this code path *does* get hit (intentionally).
For best error messages, return `undefined` and rely
on the parser generating a good error message
"Private names are not allowed outside class bodies"
Add rbuckton test cases for private names
These test cases specifically exercise where
we needed to use name-mangling. They are
cases where private names have the same
description.
- private names no conflict when inheriting
- private names distinct even when
the two classes have the same name
check dup instance+static private identifiers
class A {
#foo;
static #foo;
}
not allowed because static and private names
share the same lexical scope
https://tc39.es/proposal-class-fields/#prod-ClassBody
refactor getPropertyForPrivateName, rel spans
refactor getPropertyForPrivateName so
it is easier to read (use findAncestor instead
of loop).
Fix bugs in getPropertyForPrivateName:
- make it work with deeply-nested classes with
and without shadowing
- make it catch shadowing when the conflict is
between static and instance
private name descriptions (these can actually
clash)
And add related spans to diagnostics
for getPropertyForPrivateName
catch conflicts between static and instance
private identifiers:
- cannot have an instance and static private identifier
with the same spelling, as there is only one namespace
for private names
rename 'PrivateName' to 'PrivateIdentifier'
to match the change of wording in the spec
prposal:
https://tc39.es/proposal-class-fields/#sec-syntax
The rename in the spec was to avoid confusion
between the AST Node PrivateIdentifier
and the internal spec type PrivateName.
So one uses the [[Description]] of a
PrivateIdentifier to look up the PrivateName
for a given scope.
This corresponds closely to our implementation
in the binder and checker:
- we mangle PrivateIdentifier.escapedText to
get a `key` which we use to get the symbol
for a property
f
getLiteralTypeFromProperty-check privateIdentifier
rename and simplify privateNameAndAny test case
make it clearer that all this test is showing is
that we allow accessing arbitrary private identifiers
on `any`.
Note: we could have something more sound here by
checking that there is a private-named field declaration
in a class scope above the property access.
Discussion:
https://github.com/microsoft/TypeScript/pull/30829/files#r302760015
Fix typo in checker
s/identifer/identifier
remove accidental change
patch fourslash test broken by isPrivateIdentifier
just needed to add a check to see if the symbol
.name is defined
extract reportUnmatchedProperty
per @nsandersn feedback
propertiesRelatedTo was getting to long
pull out the unmatchedProperty reporting into
a seprate function
fix typo in private names test
Fix type soundness with private names
Remove PrivateIdentifier from emit with Expr hint
Fixup helpers and set function name for privates
remove accidentally-committed baselines
These baselines somehow ended up in this pr,
though they have nothing to do with the changes
Revert "getLiteralTypeFromProperty-check privateIdentifier"
This reverts commit bd1155c300bc3517a0543580f4790268f86e473f.
reason: the check for isIdentifier in
getLiteralTypeFromProperty is superfluous because
we do this check in getLiteralTypeFromPropertyName
Update comment in private name uniqueness test 3
I think the comments were not accurate and that we
export the error on `this.#x = child.#x` because:
- private names are lexically scoped: the code in question is not in a
lexical scope under the definition of Child's #x.
- private names are private to their containing class: never inherited
This expected behavior matches that of Chrome Canary and
my understanding of the spec
test private names use before def, circular ref
test private names use before def, circular ref
update diagnosticMessages s/delete/'delete'
per @DanielRosenwasser and @sandersn guidance,
use this style in diagnostic messages:
"operand of a 'delete' operator" (single quotes)
rather than old style:
"operand of a delete operator" (single quotes)
This is for consistency, as we use the new
style in the privateIdentifiers error messages
and it is consistent with our messages about
other operators
incorporate private names early exit feedback
and code style change to use a ternary
instead of if so we can 'const'
require private-named fields to be declared in JS
All fields must be declared in TS files.
In JS files, we typically do not have this requirement.
So special-case private fields, which must always
be declared (even in JS files, per spec)
update debug failure for es2015 private identifier
Co-Authored-By: Ron Buckton <ron.buckton@microsoft.com>
fix checker handling of private name in subclasse
update checker and tests to account for the
following ts features:
- private names do not participate in
the prototype chain, but *are* assigned
in the parent class' constructor. So
parent can access its *own* private fields
on instances of the subclass
Add more tests for private-named fields in JS
add test to hit symbolToExpression w private names
symbolToExpression knows about private names
add a test to exercise this code path
ban private-named static methods (not supported yet)
ban private-named methods (not supported yet)
ban private-named accessors (not supported yet)
fix privateIdentifier fourslash test
change assertion throw to return
Co-Authored-By: Ron Buckton <ron.buckton@microsoft.com>
Update comment in checker.ts re reserved members
Remove case for privateIdentifier in EntityNameExpr
Remove case for privateIdentifier in
EntityNameExpr. That code path is never hit,
and privateIdnetifiers cannot be entity names.
remove unnecessary parentheses
Ban private identifier in enum
As the new test, 'privateNameEnumNoEmit',
shows, the checker now correctly makes
a diagnostic for private identifiers in enums.
However, when noEmit is false we
hit this assertion in the transformer.
This assertion will have to be removed
so that we have a diagnostic here instead
of an assertion error.
When the assertion is removed,
the 'privateNameEnum' baseline
will have to be updated
Fix destructuring assignment, use createCallBinding, remove unneeded helper
Add class private field helpers to external helpers
Remove private identifier enum assertion, use missing identifiers
Fix hash map inefficiency by only using get
Update baselines with empty identifier change
Add privateNameEnum test baselines
Fix private identifier destructuring (array assignment defaults, unique names)
Use createPrivateIdentifierAssignment in destructuring transform
Fix lint error
Separate destructuring target visitor from regular visitor
Fix destructuring assignment with this bindings
Fix destructuring assignment with this bindings
Fix syntax error with destructuring assignment output
Disallow private identifiers in property signatures
remove duplicate test baselines
Add tests for undeclarated private identifiers
remove unnecessary cast
Nicer error message for mis-placed hashbang
Workaround v8 bug with destructured assignments
Optimize private identifier stack lookup
Avoid the cost of performing an array lookup to look at the top of the
private identifier environment stack.
Change function name to be more specific
Changes "getOperatorForCompoundAssignment" to
"getNonAssignmentOperatorForCompoundAssignment" now that this
function is accessible to the entire compiler.
Improve test case for private name assignment
Adds a compound assignment test case for a class with private names
being declared on the left-hand-side of the assignment expression.
Remove extra non-private-field test
Remove isPrivateIdentifierAssignmentExpression helper
Don't transform private names in ESNext target
Preserve private fields initialized to functions
Move function expressions to outer scope for efficiency
Add WeakMap collision check
Modify potential WeakMap collision condition
Fix this property assignment binding with private names
Add correct error message for WeakMap collision
Add error for statements before super with private identifiers
Refactor getPropertyForPrivateIdentifier
Add test for private identifier fields initialized to class exprs
Fix shebang errors
Fix private errors on index signatures
Add codefix for missing private property
Update error messages for unsupported private features
Fix inheritance-related errors
Fixes inheritance-related errors with private identifiers by resolving
properties from base classes. Private identifiers do not show up as
properties on a union type, so those do not type-check.
Add test for interface extending class with private access
Remove debugging log
Remove name assignment from private named functions
Rename to getPrivateIdentifierPropertyOfType
Fix index signature test comment
Fix test target syntax error
Change error messages
patch private identifiers outside class bodies
Add test for private identifier with ooo super
Add test for a class with a private identifier
with a non-preambly (for example, not a comment)
statement before 'super':
should error, saying 'super' must come first
Fix nits
incorporate PR feedback
Incorporate checker feedback
- reorganize if statements in checkFunctionOrConstructorSymbol
- remove overload for getPrivateIdentifierPropertyOfType
reorganize if statements in checkFunctionOrConstructorSymbol
test for private names with JSX
use getPropertyOftype in getPropertyForPrivateIdentifier
getPrivateIdentifierPropertyOfType error on synthetic
make getPrivateIdentifierPropertyOfType error
if given a node that is not from the parse tree
Simplify checkPrivateIdentifierPropertyAccess
use getPropertiesOfType instead of
rehashing that logic
test for private identifiers w decl merging
fix test target for privateNameDeclarationMerging
update baselines
Fix private identifier ++/-- numeric coercion
Remove 'downleveled' from super error
Fix bad comments in helper call emit
Error on private identifiers in JSX tag names
Add more private identifier tests
Add es2015 target for private name destructured binding test
Add privateNameConstructorSignature test
Add test for navigation bar w private identifier
Remove spurious line from test
// in js file
class A {
exports.#foo = 3; // should error
}
The above line failed to produce an error
when run using the test harness.
When using tsc or the language server,
we got the expected error message.
Removing the troublesome line, as it
seems to say more about the test runner
than about the code it is testing.
Fix inefficient constructor generation
dts: don't emit type for private-identified field
Do not emit types for private-identified fields
when generating declaration files.
// example.ts
export class A {
#foo: number;
}
// example.d.ts
export declare class A {
#foo;
}
**This is not ideal!**
The following would be better:
// example.d.ts
export declare unique class A {
#foo;
}
See discussion:
https://github.com/microsoft/TypeScript/pull/33038#issuecomment-530321165
notice when private-identified field unused
Notice when private-identified fields are unused,
and implement the same behavior as for unused
private-modified fields.
This is used in the language server to make things
grayed out.
This case generates an error when --noUnusedLocals
flag is passed to tsc:
- "<name> is declared but never used"
accept baselines
Revert "dts: don't emit type for private-identified field"
This reverts commit e50305df5fb88121486291abad14478f5339a455.
Instead of just excluding the type from private identifier
emit, only emit a single private identifier
per class.
This accomplishes nominality while
hiding implementation detail that
is irrelevant to .d.ts consumers
only emit a single private identifier in dts
In dts emit, emit at most one private identifier,
and rename it to `#private`.
refactor getPrivateIdentifierPropertyOfType
- safer check for wehther is parse tree node
- return undefined when not found (instead of
a Debug.fail)
Incorporate PR feedback
Don't rely on parent pointers in transform
Passes context about whether the postfix unary expression value is
discarded down the tree into the visitPostfixUnaryExpression function.
Remove orphaned test baseline files
remove unreachable if
Check `any`-typed private identified fields
Update private identifier incompatible modifier checks
- disallow 'abstract' with private identifiers
- s/private-named-property/private identifier
Add additional call expression test cases
Fix disallow 'abstract' with private identifier
Static private identifiers not inherited
Including this in the PR for private
instance fields even though static
private identifiers are banned.
Reason: this change
improves quality of error messages,
see test case.
Thanks Ron!
Simplifiy private identifier 'any' type handling
Error on private identifier declarations for ES5/ES3
Bind `this` for private identifier property tagged template literals
Fix target for jsdocPrivateName1 test
Update getPrivateIdentifierPropertyOfType API
Make it easier to use by accepting a string
and location, rather than a PrivateIdentifier.
Thanks Ron!
remove orphaned tests
rename test
remove duplicate tests
Remove unrelated error from test
update diagnostic message 'private identifier'
The nodes for hash private fields are now
called 'private identifier'. Update one last
diagnostic message to use the new terminology.
refine solution for static private identifier fields
- use `continue` instead of `filter` for perf
- better naming
- make it clear the current solution will
need to be altered when we lift the ban on
static private identifier fields, including
a test case that should change in the future
Fix display of private identifiers in lang server
Fix bug where private identifiers in completion
tooltips in the playground were showing up
as the symbol table entries (with underscores and such).
https://github.com/microsoft/TypeScript/pull/30829#issuecomment-534157560
Signed-off-by: Max Heiber <max.heiber@gmail.com>
* fix privateIdentifier w !useDefineForClassFields
Signed-off-by: Max Heiber <max.heiber@gmail.com>
* Disallow PrivateIdentifier in Optional Chains
Signed-off-by: Max Heiber <max.heiber@gmail.com>
* restrict privateIdentifier completions correctly
Don't autocomplete privateIdentifiers in
places where they are not allowed.
Signed-off-by: Max Heiber <max.heiber@gmail.com>
* make PrivateIdentifier not a PropertyNameLiteral
Signed-off-by: Max Heiber <max.heiber@gmail.com>
* Added test.
* Accepted baselines.
* Update symbol serializer to understand private fields in JS `.d.ts` emit.
* Accepted baselines.
* fix for private field no initializer esnext
Signed-off-by: Max Heiber <max.heiber@gmail.com>
* fix private fields .d.ts emit for JS w expando
fix bug where the following in a JS file
would lead to a `#private` in the .d.ts:
```js
class C {
constructor () {
this.a = 3
}
}
```
Co-authored-by: Joey Watts <joey.watts.96@gmail.com>
Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>
2019-12-27 22:07:35 +01:00
|
|
|
function inferTypeForVariableFromUsage(token: Identifier | PrivateIdentifier, program: Program, cancellationToken: CancellationToken): Type {
|
2018-11-05 20:29:16 +01:00
|
|
|
const references = getReferences(token, program, cancellationToken);
|
2019-08-27 18:02:58 +02:00
|
|
|
return inferTypeFromReferences(program, references, cancellationToken).single();
|
2017-12-07 21:14:18 +01:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function inferTypeForParametersFromUsage(func: SignatureDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken) {
|
|
|
|
const references = getFunctionReferences(func, sourceFile, program, cancellationToken);
|
|
|
|
return references && inferTypeFromReferences(program, references, cancellationToken).parameters(func) ||
|
|
|
|
func.parameters.map<ParameterInference>(p => ({
|
|
|
|
declaration: p,
|
|
|
|
type: isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : program.getTypeChecker().getAnyType()
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2020-10-06 16:51:01 +02:00
|
|
|
function getFunctionReferences(containingFunction: SignatureDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): readonly Identifier[] | undefined {
|
2018-11-05 20:29:16 +01:00
|
|
|
let searchToken;
|
2017-12-07 21:14:18 +01:00
|
|
|
switch (containingFunction.kind) {
|
|
|
|
case SyntaxKind.Constructor:
|
2018-11-05 20:29:16 +01:00
|
|
|
searchToken = findChildOfKind<Token<SyntaxKind.ConstructorKeyword>>(containingFunction, SyntaxKind.ConstructorKeyword, sourceFile);
|
|
|
|
break;
|
2018-12-04 18:03:08 +01:00
|
|
|
case SyntaxKind.ArrowFunction:
|
2017-12-07 21:14:18 +01:00
|
|
|
case SyntaxKind.FunctionExpression:
|
2018-11-05 20:29:16 +01:00
|
|
|
const parent = containingFunction.parent;
|
2020-12-15 02:08:52 +01:00
|
|
|
searchToken = (isVariableDeclaration(parent) || isPropertyDeclaration(parent)) && isIdentifier(parent.name) ?
|
2018-11-05 20:29:16 +01:00
|
|
|
parent.name :
|
|
|
|
containingFunction.name;
|
|
|
|
break;
|
2017-12-07 21:14:18 +01:00
|
|
|
case SyntaxKind.FunctionDeclaration:
|
|
|
|
case SyntaxKind.MethodDeclaration:
|
2020-12-07 23:00:46 +01:00
|
|
|
case SyntaxKind.MethodSignature:
|
2018-11-05 20:29:16 +01:00
|
|
|
searchToken = containingFunction.name;
|
|
|
|
break;
|
|
|
|
}
|
2019-06-18 04:00:15 +02:00
|
|
|
|
|
|
|
if (!searchToken) {
|
|
|
|
return undefined;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
2019-06-18 04:00:15 +02:00
|
|
|
|
|
|
|
return getReferences(searchToken, program, cancellationToken);
|
2017-12-07 21:14:18 +01:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
|
2018-10-09 19:38:46 +02:00
|
|
|
interface ParameterInference {
|
2018-10-26 23:44:49 +02:00
|
|
|
readonly declaration: ParameterDeclaration;
|
2018-10-29 21:23:33 +01:00
|
|
|
readonly type: Type;
|
2018-10-26 23:44:49 +02:00
|
|
|
readonly isOptional?: boolean;
|
2018-10-09 19:38:46 +02:00
|
|
|
}
|
|
|
|
|
2019-08-28 09:11:38 +02:00
|
|
|
function inferTypeFromReferences(program: Program, references: readonly Identifier[], cancellationToken: CancellationToken) {
|
2019-08-27 18:02:58 +02:00
|
|
|
const checker = program.getTypeChecker();
|
2019-08-30 22:44:06 +02:00
|
|
|
const builtinConstructors: { [s: string]: (t: Type) => Type } = {
|
|
|
|
string: () => checker.getStringType(),
|
|
|
|
number: () => checker.getNumberType(),
|
|
|
|
Array: t => checker.createArrayType(t),
|
|
|
|
Promise: t => checker.createPromiseType(t),
|
|
|
|
};
|
|
|
|
const builtins = [
|
|
|
|
checker.getStringType(),
|
|
|
|
checker.getNumberType(),
|
|
|
|
checker.createArrayType(checker.getAnyType()),
|
|
|
|
checker.createPromiseType(checker.getAnyType()),
|
|
|
|
];
|
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
return {
|
|
|
|
single,
|
|
|
|
parameters,
|
|
|
|
thisParameter,
|
|
|
|
};
|
|
|
|
|
2019-08-26 22:20:42 +02:00
|
|
|
interface CallUsage {
|
2017-10-12 19:14:58 +02:00
|
|
|
argumentTypes: Type[];
|
2019-08-27 22:13:58 +02:00
|
|
|
return_: Usage;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
|
2019-08-26 22:20:42 +02:00
|
|
|
interface Usage {
|
2019-08-28 23:12:21 +02:00
|
|
|
isNumber: boolean | undefined;
|
|
|
|
isString: boolean | undefined;
|
2018-11-15 17:32:28 +01:00
|
|
|
/** Used ambiguously, eg x + ___ or object[___]; results in string | number if no other evidence exists */
|
2019-08-28 23:12:21 +02:00
|
|
|
isNumberOrString: boolean | undefined;
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
|
2019-08-28 23:12:21 +02:00
|
|
|
candidateTypes: Type[] | undefined;
|
|
|
|
properties: UnderscoreEscapedMap<Usage> | undefined;
|
|
|
|
calls: CallUsage[] | undefined;
|
|
|
|
constructs: CallUsage[] | undefined;
|
|
|
|
numberIndex: Usage | undefined;
|
|
|
|
stringIndex: Usage | undefined;
|
|
|
|
candidateThisTypes: Type[] | undefined;
|
|
|
|
inferredTypes: Type[] | undefined;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
|
2019-09-06 20:18:50 +02:00
|
|
|
function createEmptyUsage(): Usage {
|
|
|
|
return {
|
|
|
|
isNumber: undefined,
|
|
|
|
isString: undefined,
|
|
|
|
isNumberOrString: undefined,
|
|
|
|
candidateTypes: undefined,
|
|
|
|
properties: undefined,
|
|
|
|
calls: undefined,
|
|
|
|
constructs: undefined,
|
|
|
|
numberIndex: undefined,
|
|
|
|
stringIndex: undefined,
|
|
|
|
candidateThisTypes: undefined,
|
|
|
|
inferredTypes: undefined,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function combineUsages(usages: Usage[]): Usage {
|
2020-06-26 01:03:25 +02:00
|
|
|
const combinedProperties = new Map<__String, Usage[]>();
|
2019-09-06 20:18:50 +02:00
|
|
|
for (const u of usages) {
|
|
|
|
if (u.properties) {
|
|
|
|
u.properties.forEach((p, name) => {
|
|
|
|
if (!combinedProperties.has(name)) {
|
|
|
|
combinedProperties.set(name, []);
|
|
|
|
}
|
|
|
|
combinedProperties.get(name)!.push(p);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2020-06-26 01:03:25 +02:00
|
|
|
const properties = new Map<__String, Usage>();
|
2019-09-06 20:18:50 +02:00
|
|
|
combinedProperties.forEach((ps, name) => {
|
|
|
|
properties.set(name, combineUsages(ps));
|
|
|
|
});
|
|
|
|
return {
|
|
|
|
isNumber: usages.some(u => u.isNumber),
|
|
|
|
isString: usages.some(u => u.isString),
|
|
|
|
isNumberOrString: usages.some(u => u.isNumberOrString),
|
|
|
|
candidateTypes: flatMap(usages, u => u.candidateTypes) as Type[],
|
|
|
|
properties,
|
|
|
|
calls: flatMap(usages, u => u.calls) as CallUsage[],
|
|
|
|
constructs: flatMap(usages, u => u.constructs) as CallUsage[],
|
|
|
|
numberIndex: forEach(usages, u => u.numberIndex),
|
|
|
|
stringIndex: forEach(usages, u => u.stringIndex),
|
|
|
|
candidateThisTypes: flatMap(usages, u => u.candidateThisTypes) as Type[],
|
|
|
|
inferredTypes: undefined, // clear type cache
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function single(): Type {
|
2019-09-12 20:30:51 +02:00
|
|
|
return combineTypes(inferTypesFromReferencesSingle(references));
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
|
2020-10-06 16:51:01 +02:00
|
|
|
function parameters(declaration: SignatureDeclaration): ParameterInference[] | undefined {
|
2019-08-27 18:02:58 +02:00
|
|
|
if (references.length === 0 || !declaration.parameters) {
|
2018-02-07 22:25:36 +01:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2019-08-28 23:12:21 +02:00
|
|
|
const usage = createEmptyUsage();
|
2018-02-07 22:25:36 +01:00
|
|
|
for (const reference of references) {
|
|
|
|
cancellationToken.throwIfCancellationRequested();
|
2019-08-27 18:02:58 +02:00
|
|
|
calculateUsageOfNode(reference, usage);
|
2018-02-07 22:25:36 +01:00
|
|
|
}
|
2019-08-26 22:20:42 +02:00
|
|
|
const calls = [...usage.constructs || [], ...usage.calls || []];
|
2018-11-16 18:51:07 +01:00
|
|
|
return declaration.parameters.map((parameter, parameterIndex): ParameterInference => {
|
|
|
|
const types = [];
|
2018-03-01 23:20:18 +01:00
|
|
|
const isRest = isRestParameter(parameter);
|
2018-10-09 19:38:46 +02:00
|
|
|
let isOptional = false;
|
2019-08-26 22:20:42 +02:00
|
|
|
for (const call of calls) {
|
|
|
|
if (call.argumentTypes.length <= parameterIndex) {
|
2018-10-09 19:38:46 +02:00
|
|
|
isOptional = isInJSFile(declaration);
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
types.push(checker.getUndefinedType());
|
2018-02-07 22:25:36 +01:00
|
|
|
}
|
2018-11-05 20:29:16 +01:00
|
|
|
else if (isRest) {
|
2019-08-26 22:20:42 +02:00
|
|
|
for (let i = parameterIndex; i < call.argumentTypes.length; i++) {
|
|
|
|
types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[i]));
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
}
|
2018-02-07 22:25:36 +01:00
|
|
|
else {
|
2019-08-26 22:20:42 +02:00
|
|
|
types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex]));
|
2018-02-07 22:25:36 +01:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
2018-11-05 20:29:16 +01:00
|
|
|
if (isIdentifier(parameter.name)) {
|
2019-08-27 18:02:58 +02:00
|
|
|
const inferred = inferTypesFromReferencesSingle(getReferences(parameter.name, program, cancellationToken));
|
2018-11-16 18:51:07 +01:00
|
|
|
types.push(...(isRest ? mapDefined(inferred, checker.getElementTypeOfArrayType) : inferred));
|
2018-10-30 21:25:24 +01:00
|
|
|
}
|
2019-09-12 20:30:51 +02:00
|
|
|
const type = combineTypes(types);
|
2018-10-09 19:38:46 +02:00
|
|
|
return {
|
|
|
|
type: isRest ? checker.createArrayType(type) : type,
|
|
|
|
isOptional: isOptional && !isRest,
|
|
|
|
declaration: parameter
|
|
|
|
};
|
2018-02-07 22:25:36 +01:00
|
|
|
});
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function thisParameter() {
|
2019-08-28 23:12:21 +02:00
|
|
|
const usage = createEmptyUsage();
|
2019-08-27 18:02:58 +02:00
|
|
|
for (const reference of references) {
|
|
|
|
cancellationToken.throwIfCancellationRequested();
|
|
|
|
calculateUsageOfNode(reference, usage);
|
2019-06-18 04:00:15 +02:00
|
|
|
}
|
|
|
|
|
2019-09-12 20:30:51 +02:00
|
|
|
return combineTypes(usage.candidateThisTypes || emptyArray);
|
2019-08-27 18:02:58 +02:00
|
|
|
}
|
2019-06-18 04:00:15 +02:00
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function inferTypesFromReferencesSingle(references: readonly Identifier[]): Type[] {
|
2019-08-28 23:12:21 +02:00
|
|
|
const usage: Usage = createEmptyUsage();
|
2019-06-18 04:00:15 +02:00
|
|
|
for (const reference of references) {
|
|
|
|
cancellationToken.throwIfCancellationRequested();
|
2019-08-27 18:02:58 +02:00
|
|
|
calculateUsageOfNode(reference, usage);
|
2019-06-18 04:00:15 +02:00
|
|
|
}
|
2019-09-12 20:30:51 +02:00
|
|
|
return inferTypes(usage);
|
2019-06-18 04:00:15 +02:00
|
|
|
}
|
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function calculateUsageOfNode(node: Expression, usage: Usage): void {
|
2017-10-12 19:14:58 +02:00
|
|
|
while (isRightSideOfQualifiedNameOrPropertyAccess(node)) {
|
2021-05-18 15:20:57 +02:00
|
|
|
node = node.parent as Expression;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (node.parent.kind) {
|
2019-09-06 01:16:35 +02:00
|
|
|
case SyntaxKind.ExpressionStatement:
|
2020-04-22 20:28:11 +02:00
|
|
|
inferTypeFromExpressionStatement(node, usage);
|
2019-09-06 01:16:35 +02:00
|
|
|
break;
|
2017-10-12 19:14:58 +02:00
|
|
|
case SyntaxKind.PostfixUnaryExpression:
|
2019-08-26 22:20:42 +02:00
|
|
|
usage.isNumber = true;
|
2017-10-12 19:14:58 +02:00
|
|
|
break;
|
|
|
|
case SyntaxKind.PrefixUnaryExpression:
|
2021-05-18 15:20:57 +02:00
|
|
|
inferTypeFromPrefixUnaryExpression(node.parent as PrefixUnaryExpression, usage);
|
2017-10-12 19:14:58 +02:00
|
|
|
break;
|
|
|
|
case SyntaxKind.BinaryExpression:
|
2021-05-18 15:20:57 +02:00
|
|
|
inferTypeFromBinaryExpression(node, node.parent as BinaryExpression, usage);
|
2017-10-12 19:14:58 +02:00
|
|
|
break;
|
|
|
|
case SyntaxKind.CaseClause:
|
|
|
|
case SyntaxKind.DefaultClause:
|
2021-05-18 15:20:57 +02:00
|
|
|
inferTypeFromSwitchStatementLabel(node.parent as CaseOrDefaultClause, usage);
|
2017-10-12 19:14:58 +02:00
|
|
|
break;
|
|
|
|
case SyntaxKind.CallExpression:
|
|
|
|
case SyntaxKind.NewExpression:
|
2021-05-18 15:20:57 +02:00
|
|
|
if ((node.parent as CallExpression | NewExpression).expression === node) {
|
|
|
|
inferTypeFromCallExpression(node.parent as CallExpression | NewExpression, usage);
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
else {
|
2019-08-27 18:02:58 +02:00
|
|
|
inferTypeFromContextualType(node, usage);
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SyntaxKind.PropertyAccessExpression:
|
2021-05-18 15:20:57 +02:00
|
|
|
inferTypeFromPropertyAccessExpression(node.parent as PropertyAccessExpression, usage);
|
2017-10-12 19:14:58 +02:00
|
|
|
break;
|
|
|
|
case SyntaxKind.ElementAccessExpression:
|
2021-05-18 15:20:57 +02:00
|
|
|
inferTypeFromPropertyElementExpression(node.parent as ElementAccessExpression, node, usage);
|
2017-10-12 19:14:58 +02:00
|
|
|
break;
|
2019-06-18 04:00:15 +02:00
|
|
|
case SyntaxKind.PropertyAssignment:
|
|
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
2021-05-18 15:20:57 +02:00
|
|
|
inferTypeFromPropertyAssignment(node.parent as PropertyAssignment | ShorthandPropertyAssignment, usage);
|
2019-06-18 04:00:15 +02:00
|
|
|
break;
|
|
|
|
case SyntaxKind.PropertyDeclaration:
|
2021-05-18 15:20:57 +02:00
|
|
|
inferTypeFromPropertyDeclaration(node.parent as PropertyDeclaration, usage);
|
2019-06-18 04:00:15 +02:00
|
|
|
break;
|
2018-05-16 21:28:27 +02:00
|
|
|
case SyntaxKind.VariableDeclaration: {
|
|
|
|
const { name, initializer } = node.parent as VariableDeclaration;
|
|
|
|
if (node === name) {
|
|
|
|
if (initializer) { // This can happen for `let x = null;` which still has an implicit-any error.
|
2019-08-26 22:20:42 +02:00
|
|
|
addCandidateType(usage, checker.getTypeAtLocation(initializer));
|
2018-05-16 21:28:27 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-06-24 13:55:58 +02:00
|
|
|
// falls through
|
2017-10-12 19:14:58 +02:00
|
|
|
default:
|
2019-08-27 18:02:58 +02:00
|
|
|
return inferTypeFromContextualType(node, usage);
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function inferTypeFromContextualType(node: Expression, usage: Usage): void {
|
2017-10-31 16:05:39 +01:00
|
|
|
if (isExpressionNode(node)) {
|
2019-08-26 22:20:42 +02:00
|
|
|
addCandidateType(usage, checker.getContextualType(node));
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-22 20:28:11 +02:00
|
|
|
function inferTypeFromExpressionStatement(node: Expression, usage: Usage): void {
|
|
|
|
addCandidateType(usage, isCallExpression(node) ? checker.getVoidType() : checker.getAnyType());
|
|
|
|
}
|
|
|
|
|
2019-08-26 22:20:42 +02:00
|
|
|
function inferTypeFromPrefixUnaryExpression(node: PrefixUnaryExpression, usage: Usage): void {
|
2017-10-12 19:14:58 +02:00
|
|
|
switch (node.operator) {
|
|
|
|
case SyntaxKind.PlusPlusToken:
|
|
|
|
case SyntaxKind.MinusMinusToken:
|
|
|
|
case SyntaxKind.MinusToken:
|
|
|
|
case SyntaxKind.TildeToken:
|
2019-08-26 22:20:42 +02:00
|
|
|
usage.isNumber = true;
|
2017-10-12 19:14:58 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SyntaxKind.PlusToken:
|
2019-08-26 22:20:42 +02:00
|
|
|
usage.isNumberOrString = true;
|
2017-10-12 19:14:58 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
// case SyntaxKind.ExclamationToken:
|
|
|
|
// no inferences here;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function inferTypeFromBinaryExpression(node: Expression, parent: BinaryExpression, usage: Usage): void {
|
2017-10-12 19:14:58 +02:00
|
|
|
switch (parent.operatorToken.kind) {
|
|
|
|
// ExponentiationOperator
|
|
|
|
case SyntaxKind.AsteriskAsteriskToken:
|
|
|
|
|
|
|
|
// MultiplicativeOperator
|
2019-06-21 17:32:22 +02:00
|
|
|
// falls through
|
2017-10-12 19:14:58 +02:00
|
|
|
case SyntaxKind.AsteriskToken:
|
|
|
|
case SyntaxKind.SlashToken:
|
|
|
|
case SyntaxKind.PercentToken:
|
|
|
|
|
|
|
|
// ShiftOperator
|
2019-06-21 17:32:22 +02:00
|
|
|
// falls through
|
2017-10-12 19:14:58 +02:00
|
|
|
case SyntaxKind.LessThanLessThanToken:
|
|
|
|
case SyntaxKind.GreaterThanGreaterThanToken:
|
|
|
|
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
|
|
|
|
|
|
|
|
// BitwiseOperator
|
2019-06-21 17:32:22 +02:00
|
|
|
// falls through
|
2017-10-12 19:14:58 +02:00
|
|
|
case SyntaxKind.AmpersandToken:
|
|
|
|
case SyntaxKind.BarToken:
|
|
|
|
case SyntaxKind.CaretToken:
|
|
|
|
|
|
|
|
// CompoundAssignmentOperator
|
2019-06-21 17:32:22 +02:00
|
|
|
// falls through
|
2017-10-12 19:14:58 +02:00
|
|
|
case SyntaxKind.MinusEqualsToken:
|
|
|
|
case SyntaxKind.AsteriskAsteriskEqualsToken:
|
|
|
|
case SyntaxKind.AsteriskEqualsToken:
|
|
|
|
case SyntaxKind.SlashEqualsToken:
|
|
|
|
case SyntaxKind.PercentEqualsToken:
|
|
|
|
case SyntaxKind.AmpersandEqualsToken:
|
|
|
|
case SyntaxKind.BarEqualsToken:
|
|
|
|
case SyntaxKind.CaretEqualsToken:
|
|
|
|
case SyntaxKind.LessThanLessThanEqualsToken:
|
|
|
|
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
|
|
|
|
case SyntaxKind.GreaterThanGreaterThanEqualsToken:
|
|
|
|
|
|
|
|
// AdditiveOperator
|
2019-06-21 17:32:22 +02:00
|
|
|
// falls through
|
2017-10-12 19:14:58 +02:00
|
|
|
case SyntaxKind.MinusToken:
|
|
|
|
|
|
|
|
// RelationalOperator
|
2019-06-21 17:32:22 +02:00
|
|
|
// falls through
|
2017-10-12 19:14:58 +02:00
|
|
|
case SyntaxKind.LessThanToken:
|
|
|
|
case SyntaxKind.LessThanEqualsToken:
|
|
|
|
case SyntaxKind.GreaterThanToken:
|
|
|
|
case SyntaxKind.GreaterThanEqualsToken:
|
2018-07-12 01:33:56 +02:00
|
|
|
const operandType = checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left);
|
2017-10-12 19:14:58 +02:00
|
|
|
if (operandType.flags & TypeFlags.EnumLike) {
|
2019-08-26 22:20:42 +02:00
|
|
|
addCandidateType(usage, operandType);
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
else {
|
2019-08-26 22:20:42 +02:00
|
|
|
usage.isNumber = true;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SyntaxKind.PlusEqualsToken:
|
|
|
|
case SyntaxKind.PlusToken:
|
2018-07-12 01:33:56 +02:00
|
|
|
const otherOperandType = checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left);
|
2017-10-12 19:14:58 +02:00
|
|
|
if (otherOperandType.flags & TypeFlags.EnumLike) {
|
2019-08-26 22:20:42 +02:00
|
|
|
addCandidateType(usage, otherOperandType);
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
else if (otherOperandType.flags & TypeFlags.NumberLike) {
|
2019-08-26 22:20:42 +02:00
|
|
|
usage.isNumber = true;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
else if (otherOperandType.flags & TypeFlags.StringLike) {
|
2019-08-26 22:20:42 +02:00
|
|
|
usage.isString = true;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
2019-08-29 21:40:33 +02:00
|
|
|
else if (otherOperandType.flags & TypeFlags.Any) {
|
|
|
|
// do nothing, maybe we'll learn something elsewhere
|
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
else {
|
2019-08-26 22:20:42 +02:00
|
|
|
usage.isNumberOrString = true;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
// AssignmentOperators
|
|
|
|
case SyntaxKind.EqualsToken:
|
|
|
|
case SyntaxKind.EqualsEqualsToken:
|
|
|
|
case SyntaxKind.EqualsEqualsEqualsToken:
|
|
|
|
case SyntaxKind.ExclamationEqualsEqualsToken:
|
|
|
|
case SyntaxKind.ExclamationEqualsToken:
|
2019-08-26 22:20:42 +02:00
|
|
|
addCandidateType(usage, checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left));
|
2017-10-12 19:14:58 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SyntaxKind.InKeyword:
|
|
|
|
if (node === parent.left) {
|
2019-08-26 22:20:42 +02:00
|
|
|
usage.isString = true;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2019-10-01 00:33:50 +02:00
|
|
|
// LogicalOperator Or NullishCoalescing
|
2017-10-12 19:14:58 +02:00
|
|
|
case SyntaxKind.BarBarToken:
|
2019-10-01 00:33:50 +02:00
|
|
|
case SyntaxKind.QuestionQuestionToken:
|
2017-10-12 19:14:58 +02:00
|
|
|
if (node === parent.left &&
|
|
|
|
(node.parent.parent.kind === SyntaxKind.VariableDeclaration || isAssignmentExpression(node.parent.parent, /*excludeCompoundAssignment*/ true))) {
|
|
|
|
// var x = x || {};
|
|
|
|
// TODO: use getFalsyflagsOfType
|
2019-08-26 22:20:42 +02:00
|
|
|
addCandidateType(usage, checker.getTypeAtLocation(parent.right));
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SyntaxKind.AmpersandAmpersandToken:
|
|
|
|
case SyntaxKind.CommaToken:
|
|
|
|
case SyntaxKind.InstanceOfKeyword:
|
|
|
|
// nothing to infer here
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function inferTypeFromSwitchStatementLabel(parent: CaseOrDefaultClause, usage: Usage): void {
|
2019-08-26 22:20:42 +02:00
|
|
|
addCandidateType(usage, checker.getTypeAtLocation(parent.parent.parent.expression));
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function inferTypeFromCallExpression(parent: CallExpression | NewExpression, usage: Usage): void {
|
2019-08-26 22:20:42 +02:00
|
|
|
const call: CallUsage = {
|
2017-10-12 19:14:58 +02:00
|
|
|
argumentTypes: [],
|
2019-08-28 23:12:21 +02:00
|
|
|
return_: createEmptyUsage()
|
2017-10-12 19:14:58 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
if (parent.arguments) {
|
|
|
|
for (const argument of parent.arguments) {
|
2019-08-26 22:20:42 +02:00
|
|
|
call.argumentTypes.push(checker.getTypeAtLocation(argument));
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-27 22:13:58 +02:00
|
|
|
calculateUsageOfNode(parent, call.return_);
|
2017-10-12 19:14:58 +02:00
|
|
|
if (parent.kind === SyntaxKind.CallExpression) {
|
2019-08-26 22:20:42 +02:00
|
|
|
(usage.calls || (usage.calls = [])).push(call);
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
else {
|
2019-08-26 22:20:42 +02:00
|
|
|
(usage.constructs || (usage.constructs = [])).push(call);
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function inferTypeFromPropertyAccessExpression(parent: PropertyAccessExpression, usage: Usage): void {
|
2017-10-12 19:14:58 +02:00
|
|
|
const name = escapeLeadingUnderscores(parent.name.text);
|
2019-08-26 22:20:42 +02:00
|
|
|
if (!usage.properties) {
|
2020-06-26 01:03:25 +02:00
|
|
|
usage.properties = new Map();
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
2019-08-28 23:12:21 +02:00
|
|
|
const propertyUsage = usage.properties.get(name) || createEmptyUsage();
|
2019-08-27 18:02:58 +02:00
|
|
|
calculateUsageOfNode(parent, propertyUsage);
|
2019-08-26 22:20:42 +02:00
|
|
|
usage.properties.set(name, propertyUsage);
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function inferTypeFromPropertyElementExpression(parent: ElementAccessExpression, node: Expression, usage: Usage): void {
|
2017-10-12 19:14:58 +02:00
|
|
|
if (node === parent.argumentExpression) {
|
2019-08-26 22:20:42 +02:00
|
|
|
usage.isNumberOrString = true;
|
2017-10-12 19:14:58 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
2018-07-25 19:51:11 +02:00
|
|
|
const indexType = checker.getTypeAtLocation(parent.argumentExpression);
|
2019-08-28 23:12:21 +02:00
|
|
|
const indexUsage = createEmptyUsage();
|
2019-08-27 18:02:58 +02:00
|
|
|
calculateUsageOfNode(parent, indexUsage);
|
2017-10-12 19:14:58 +02:00
|
|
|
if (indexType.flags & TypeFlags.NumberLike) {
|
2019-08-26 22:20:42 +02:00
|
|
|
usage.numberIndex = indexUsage;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
else {
|
2019-08-26 22:20:42 +02:00
|
|
|
usage.stringIndex = indexUsage;
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function inferTypeFromPropertyAssignment(assignment: PropertyAssignment | ShorthandPropertyAssignment, usage: Usage) {
|
2019-08-26 19:42:17 +02:00
|
|
|
const nodeWithRealType = isVariableDeclaration(assignment.parent.parent) ?
|
|
|
|
assignment.parent.parent :
|
|
|
|
assignment.parent;
|
2019-08-26 22:20:42 +02:00
|
|
|
addCandidateThisType(usage, checker.getTypeAtLocation(nodeWithRealType));
|
2019-06-18 04:00:15 +02:00
|
|
|
}
|
|
|
|
|
2019-08-27 18:02:58 +02:00
|
|
|
function inferTypeFromPropertyDeclaration(declaration: PropertyDeclaration, usage: Usage) {
|
2019-08-26 22:20:42 +02:00
|
|
|
addCandidateThisType(usage, checker.getTypeAtLocation(declaration.parent));
|
2019-06-18 04:00:15 +02:00
|
|
|
}
|
|
|
|
|
2018-11-15 17:32:28 +01:00
|
|
|
interface Priority {
|
|
|
|
high: (t: Type) => boolean;
|
|
|
|
low: (t: Type) => boolean;
|
|
|
|
}
|
|
|
|
|
2019-08-08 20:30:18 +02:00
|
|
|
function removeLowPriorityInferences(inferences: readonly Type[], priorities: Priority[]): Type[] {
|
2018-11-15 17:32:28 +01:00
|
|
|
const toRemove: ((t: Type) => boolean)[] = [];
|
|
|
|
for (const i of inferences) {
|
|
|
|
for (const { high, low } of priorities) {
|
|
|
|
if (high(i)) {
|
2019-08-21 23:22:17 +02:00
|
|
|
Debug.assert(!low(i), "Priority can't have both low and high");
|
2018-11-15 17:32:28 +01:00
|
|
|
toRemove.push(low);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return inferences.filter(i => toRemove.every(f => !f(i)));
|
|
|
|
}
|
|
|
|
|
2019-09-12 20:30:51 +02:00
|
|
|
function combineFromUsage(usage: Usage) {
|
|
|
|
return combineTypes(inferTypes(usage));
|
2019-09-06 20:18:50 +02:00
|
|
|
}
|
|
|
|
|
2019-09-12 20:30:51 +02:00
|
|
|
function combineTypes(inferences: readonly Type[]): Type {
|
2019-09-06 01:21:08 +02:00
|
|
|
if (!inferences.length) return checker.getAnyType();
|
2018-11-15 17:32:28 +01:00
|
|
|
|
|
|
|
// 1. string or number individually override string | number
|
|
|
|
// 2. non-any, non-void overrides any or void
|
|
|
|
// 3. non-nullable, non-any, non-void, non-anonymous overrides anonymous types
|
|
|
|
const stringNumber = checker.getUnionType([checker.getStringType(), checker.getNumberType()]);
|
|
|
|
const priorities: Priority[] = [
|
|
|
|
{
|
|
|
|
high: t => t === checker.getStringType() || t === checker.getNumberType(),
|
|
|
|
low: t => t === stringNumber
|
|
|
|
},
|
|
|
|
{
|
|
|
|
high: t => !(t.flags & (TypeFlags.Any | TypeFlags.Void)),
|
|
|
|
low: t => !!(t.flags & (TypeFlags.Any | TypeFlags.Void))
|
|
|
|
},
|
|
|
|
{
|
2019-12-02 22:44:25 +01:00
|
|
|
high: t => !(t.flags & (TypeFlags.Nullable | TypeFlags.Any | TypeFlags.Void)) && !(getObjectFlags(t) & ObjectFlags.Anonymous),
|
|
|
|
low: t => !!(getObjectFlags(t) & ObjectFlags.Anonymous)
|
2018-11-15 17:32:28 +01:00
|
|
|
}];
|
|
|
|
let good = removeLowPriorityInferences(inferences, priorities);
|
2019-12-02 22:44:25 +01:00
|
|
|
const anons = good.filter(i => getObjectFlags(i) & ObjectFlags.Anonymous) as AnonymousType[];
|
2018-11-15 17:32:28 +01:00
|
|
|
if (anons.length) {
|
2019-12-02 22:44:25 +01:00
|
|
|
good = good.filter(i => !(getObjectFlags(i) & ObjectFlags.Anonymous));
|
2019-09-12 20:30:51 +02:00
|
|
|
good.push(combineAnonymousTypes(anons));
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
2019-08-30 22:44:06 +02:00
|
|
|
return checker.getWidenedType(checker.getUnionType(good.map(checker.getBaseTypeOfLiteralType), UnionReduction.Subtype));
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
}
|
|
|
|
|
2019-09-12 20:30:51 +02:00
|
|
|
function combineAnonymousTypes(anons: AnonymousType[]) {
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
if (anons.length === 1) {
|
|
|
|
return anons[0];
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
const calls = [];
|
|
|
|
const constructs = [];
|
|
|
|
const stringIndices = [];
|
|
|
|
const numberIndices = [];
|
|
|
|
let stringIndexReadonly = false;
|
|
|
|
let numberIndexReadonly = false;
|
|
|
|
const props = createMultiMap<Type>();
|
|
|
|
for (const anon of anons) {
|
|
|
|
for (const p of checker.getPropertiesOfType(anon)) {
|
2021-03-02 21:36:26 +01:00
|
|
|
props.add(p.name, p.valueDeclaration ? checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration) : checker.getAnyType());
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
}
|
|
|
|
calls.push(...checker.getSignaturesOfType(anon, SignatureKind.Call));
|
|
|
|
constructs.push(...checker.getSignaturesOfType(anon, SignatureKind.Construct));
|
2021-06-21 20:25:42 +02:00
|
|
|
const stringIndexInfo = checker.getIndexInfoOfType(anon, IndexKind.String);
|
|
|
|
if (stringIndexInfo) {
|
|
|
|
stringIndices.push(stringIndexInfo.type);
|
|
|
|
stringIndexReadonly = stringIndexReadonly || stringIndexInfo.isReadonly;
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
}
|
2021-06-21 20:25:42 +02:00
|
|
|
const numberIndexInfo = checker.getIndexInfoOfType(anon, IndexKind.Number);
|
|
|
|
if (numberIndexInfo) {
|
|
|
|
numberIndices.push(numberIndexInfo.type);
|
|
|
|
numberIndexReadonly = numberIndexReadonly || numberIndexInfo.isReadonly;
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
const members = mapEntries(props, (name, types) => {
|
|
|
|
const isOptional = types.length < anons.length ? SymbolFlags.Optional : 0;
|
|
|
|
const s = checker.createSymbol(SymbolFlags.Property | isOptional, name as __String);
|
|
|
|
s.type = checker.getUnionType(types);
|
|
|
|
return [name, s];
|
|
|
|
});
|
2021-06-21 20:25:42 +02:00
|
|
|
const indexInfos = [];
|
|
|
|
if (stringIndices.length) indexInfos.push(checker.createIndexInfo(checker.getStringType(), checker.getUnionType(stringIndices), stringIndexReadonly));
|
|
|
|
if (numberIndices.length) indexInfos.push(checker.createIndexInfo(checker.getNumberType(), checker.getUnionType(numberIndices), numberIndexReadonly));
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
return checker.createAnonymousType(
|
|
|
|
anons[0].symbol,
|
|
|
|
members as UnderscoreEscapedMap<TransientSymbol>,
|
|
|
|
calls,
|
|
|
|
constructs,
|
2021-06-21 20:25:42 +02:00
|
|
|
indexInfos);
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
}
|
|
|
|
|
2019-09-12 20:30:51 +02:00
|
|
|
function inferTypes(usage: Usage): Type[] {
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
const types = [];
|
2018-11-15 17:32:28 +01:00
|
|
|
|
2019-08-26 22:20:42 +02:00
|
|
|
if (usage.isNumber) {
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
types.push(checker.getNumberType());
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
2019-08-26 22:20:42 +02:00
|
|
|
if (usage.isString) {
|
infer from usage's unification uses multiple passes (#28244)
* infer from usage's unification uses multiple passes
Previously, the unification step of infer-from-usage codefix would stop
as soon an answer was found. Now it continues if the result is
*incomplete*, with the idea that later passes may provide a better
inference.
Currently, an *incomplete* inference is
1. The type any.
2. The empty object type `{}` or a union or intersection that contains
`{}`.
In the checker, any takes priority over other types since it basically
shuts down type checking. For type inference, however, any is one of the least
useful inferences.
`{}` is not a good inference for a similar reason; as a parameter
inference, it doesn't tell the caller much about what is expected, and
it doesn't allow the function author to use an object as expected. But
currently it's inferred whenever there's an initialisation with the
value `{}`. With this change, subsequent property assignments to the
same parameter will replace the `{}` with a specific anonymous type. For
example:
```js
function C(config) {
if (config === undefined) config = {};
this.x = config.x;
this.y = config.y;
this.z = config.z;
}
```
* Unify all passes of inference from usage
In the previous commit, I changed inference from usage to continue
inference if a the result was *incomplete*. This commit now runs all 4
inference passes and combines them in a unification step. Currently the
unification step is simple, it:
1. Gathers all inferences in a list.
2. Makes properties of anonymous types optional if there is an empty
object in the inference list.
3. Removes *vacuous* inferences.
4. Combines the type in a union.
An inference is *vacuous* if it:
1. Is any or void, when a non-any, non-void type is also inferred.
2. Is the empty object type, when an object type that is not empty is
also inferred.
3. Is an anonymous type, when a non-nullable, non-any, non-void,
non-anonymous type is also inferred.
I think I might eventually want a concept of priorities, like the
compiler's type parameter inference, but I don't have enough examples to
be sure yet.
Eventually, unification should have an additional step that examines the
whole inference list to see if its contents are collectively
meaningless. A good example is `null | undefined`, which is not useful.
* Remove isNumberOrString
* Unify anonymous types
@andy-ms pointed out that my empty object code was a special case of
merging all anonymous types from an inference and making properties
optional that are not in all the anonymous type. So I did that instead.
* Use getTypeOfSymbolAtLocation instead of Symbol.type!
* Unify parameter call-site inferences too
Because they still have a separate code path, they didn't use the new
unification code.
Also some cleanup from PR comments.
* Add object type unification test
Also remove dead code.
* Only use fallback if no inferences were found
Instead of relying on the unification code to remove the fallback.
2018-11-02 17:07:32 +01:00
|
|
|
types.push(checker.getStringType());
|
|
|
|
}
|
2019-08-26 22:20:42 +02:00
|
|
|
if (usage.isNumberOrString) {
|
2018-11-15 17:32:28 +01:00
|
|
|
types.push(checker.getUnionType([checker.getStringType(), checker.getNumberType()]));
|
|
|
|
}
|
2019-08-26 22:20:42 +02:00
|
|
|
if (usage.numberIndex) {
|
2019-09-12 20:30:51 +02:00
|
|
|
types.push(checker.createArrayType(combineFromUsage(usage.numberIndex)));
|
2019-09-06 20:18:50 +02:00
|
|
|
}
|
2020-04-22 20:28:11 +02:00
|
|
|
if (usage.properties?.size || usage.calls?.length || usage.constructs?.length || usage.stringIndex) {
|
2019-09-07 00:15:19 +02:00
|
|
|
types.push(inferStructuralType(usage));
|
2018-08-29 20:37:31 +02:00
|
|
|
}
|
2019-09-07 00:15:19 +02:00
|
|
|
|
|
|
|
types.push(...(usage.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t)));
|
|
|
|
types.push(...inferNamedTypesFromProperties(usage));
|
|
|
|
|
2019-09-06 20:18:50 +02:00
|
|
|
return types;
|
|
|
|
}
|
|
|
|
|
|
|
|
function inferStructuralType(usage: Usage) {
|
2020-06-26 01:03:25 +02:00
|
|
|
const members = new Map<__String, Symbol>();
|
2019-09-07 00:15:19 +02:00
|
|
|
if (usage.properties) {
|
|
|
|
usage.properties.forEach((u, name) => {
|
|
|
|
const symbol = checker.createSymbol(SymbolFlags.Property, name);
|
2019-09-12 20:30:51 +02:00
|
|
|
symbol.type = combineFromUsage(u);
|
2019-09-07 00:15:19 +02:00
|
|
|
members.set(name, symbol);
|
|
|
|
});
|
2018-08-29 20:37:31 +02:00
|
|
|
}
|
2019-09-07 00:15:19 +02:00
|
|
|
const callSignatures: Signature[] = usage.calls ? [getSignatureFromCalls(usage.calls)] : [];
|
|
|
|
const constructSignatures: Signature[] = usage.constructs ? [getSignatureFromCalls(usage.constructs)] : [];
|
2021-06-21 20:25:42 +02:00
|
|
|
const indexInfos = usage.stringIndex ? [checker.createIndexInfo(checker.getStringType(), combineFromUsage(usage.stringIndex), /*isReadonly*/ false)] : [];
|
|
|
|
return checker.createAnonymousType(/*symbol*/ undefined, members, callSignatures, constructSignatures, indexInfos);
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
|
2019-09-06 20:18:50 +02:00
|
|
|
function inferNamedTypesFromProperties(usage: Usage): Type[] {
|
2019-08-29 21:40:33 +02:00
|
|
|
if (!usage.properties || !usage.properties.size) return [];
|
2019-09-07 00:15:19 +02:00
|
|
|
const types = builtins.filter(t => allPropertiesAreAssignableToUsage(t, usage));
|
|
|
|
if (0 < types.length && types.length < 3) {
|
|
|
|
return types.map(t => inferInstantiationFromUsage(t, usage));
|
2019-08-27 22:13:58 +02:00
|
|
|
}
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2019-09-06 20:18:50 +02:00
|
|
|
function allPropertiesAreAssignableToUsage(type: Type, usage: Usage) {
|
2019-08-27 22:13:58 +02:00
|
|
|
if (!usage.properties) return false;
|
2019-09-24 00:57:32 +02:00
|
|
|
return !forEachEntry(usage.properties, (propUsage, name) => {
|
2019-08-29 21:40:33 +02:00
|
|
|
const source = checker.getTypeOfPropertyOfType(type, name as string);
|
|
|
|
if (!source) {
|
2019-09-24 00:57:32 +02:00
|
|
|
return true;
|
2019-08-29 21:40:33 +02:00
|
|
|
}
|
|
|
|
if (propUsage.calls) {
|
2019-09-06 20:18:50 +02:00
|
|
|
const sigs = checker.getSignaturesOfType(source, SignatureKind.Call);
|
2019-09-24 00:57:32 +02:00
|
|
|
return !sigs.length || !checker.isTypeAssignableTo(source, getFunctionFromCalls(propUsage.calls));
|
2019-08-27 22:13:58 +02:00
|
|
|
}
|
|
|
|
else {
|
2019-09-24 00:57:32 +02:00
|
|
|
return !checker.isTypeAssignableTo(source, combineFromUsage(propUsage));
|
2019-08-27 22:13:58 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-09-06 20:18:50 +02:00
|
|
|
/**
|
|
|
|
* inference is limited to
|
|
|
|
* 1. generic types with a single parameter
|
|
|
|
* 2. inference to/from calls with a single signature
|
|
|
|
*/
|
|
|
|
function inferInstantiationFromUsage(type: Type, usage: Usage) {
|
|
|
|
if (!(getObjectFlags(type) & ObjectFlags.Reference) || !usage.properties) {
|
|
|
|
return type;
|
|
|
|
}
|
2019-08-30 22:44:06 +02:00
|
|
|
const generic = (type as TypeReference).target;
|
|
|
|
const singleTypeParameter = singleOrUndefined(generic.typeParameters);
|
|
|
|
if (!singleTypeParameter) return type;
|
2019-08-27 22:13:58 +02:00
|
|
|
|
2019-08-30 22:44:06 +02:00
|
|
|
const types: Type[] = [];
|
|
|
|
usage.properties.forEach((propUsage, name) => {
|
2019-09-06 20:18:50 +02:00
|
|
|
const genericPropertyType = checker.getTypeOfPropertyOfType(generic, name as string);
|
|
|
|
Debug.assert(!!genericPropertyType, "generic should have all the properties of its reference.");
|
2020-02-25 03:20:58 +01:00
|
|
|
types.push(...inferTypeParameters(genericPropertyType, combineFromUsage(propUsage), singleTypeParameter));
|
2019-08-30 22:44:06 +02:00
|
|
|
});
|
2019-09-12 20:30:51 +02:00
|
|
|
return builtinConstructors[type.symbol.escapedName as string](combineTypes(types));
|
2019-08-27 22:13:58 +02:00
|
|
|
}
|
|
|
|
|
2019-09-12 20:30:51 +02:00
|
|
|
function inferTypeParameters(genericType: Type, usageType: Type, typeParameter: Type): readonly Type[] {
|
2019-09-06 20:27:34 +02:00
|
|
|
if (genericType === typeParameter) {
|
|
|
|
return [usageType];
|
2019-08-30 22:44:06 +02:00
|
|
|
}
|
2019-09-06 20:27:34 +02:00
|
|
|
else if (genericType.flags & TypeFlags.UnionOrIntersection) {
|
2019-09-12 20:30:51 +02:00
|
|
|
return flatMap((genericType as UnionOrIntersectionType).types, t => inferTypeParameters(t, usageType, typeParameter));
|
2019-08-30 22:44:06 +02:00
|
|
|
}
|
2019-09-06 20:27:34 +02:00
|
|
|
else if (getObjectFlags(genericType) & ObjectFlags.Reference && getObjectFlags(usageType) & ObjectFlags.Reference) {
|
2019-08-30 22:44:06 +02:00
|
|
|
// this is wrong because we need a reference to the targetType to, so we can check that it's also a reference
|
2019-09-24 20:53:49 +02:00
|
|
|
const genericArgs = checker.getTypeArguments(genericType as TypeReference);
|
|
|
|
const usageArgs = checker.getTypeArguments(usageType as TypeReference);
|
2019-08-30 22:44:06 +02:00
|
|
|
const types = [];
|
2019-09-06 20:27:34 +02:00
|
|
|
if (genericArgs && usageArgs) {
|
|
|
|
for (let i = 0; i < genericArgs.length; i++) {
|
|
|
|
if (usageArgs[i]) {
|
2019-09-12 20:30:51 +02:00
|
|
|
types.push(...inferTypeParameters(genericArgs[i], usageArgs[i], typeParameter));
|
2019-08-30 22:44:06 +02:00
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
}
|
2019-08-30 22:44:06 +02:00
|
|
|
return types;
|
|
|
|
}
|
2019-09-06 20:27:34 +02:00
|
|
|
const genericSigs = checker.getSignaturesOfType(genericType, SignatureKind.Call);
|
|
|
|
const usageSigs = checker.getSignaturesOfType(usageType, SignatureKind.Call);
|
|
|
|
if (genericSigs.length === 1 && usageSigs.length === 1) {
|
|
|
|
return inferFromSignatures(genericSigs[0], usageSigs[0], typeParameter);
|
2019-08-30 22:44:06 +02:00
|
|
|
}
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2019-09-06 20:27:34 +02:00
|
|
|
function inferFromSignatures(genericSig: Signature, usageSig: Signature, typeParameter: Type) {
|
2019-08-30 22:44:06 +02:00
|
|
|
const types = [];
|
2019-09-06 20:27:34 +02:00
|
|
|
for (let i = 0; i < genericSig.parameters.length; i++) {
|
|
|
|
const genericParam = genericSig.parameters[i];
|
|
|
|
const usageParam = usageSig.parameters[i];
|
|
|
|
const isRest = genericSig.declaration && isRestParameter(genericSig.declaration.parameters[i]);
|
|
|
|
if (!usageParam) {
|
2019-08-30 22:44:06 +02:00
|
|
|
break;
|
|
|
|
}
|
2021-03-02 21:36:26 +01:00
|
|
|
let genericParamType = genericParam.valueDeclaration ? checker.getTypeOfSymbolAtLocation(genericParam, genericParam.valueDeclaration) : checker.getAnyType();
|
2019-09-06 20:27:34 +02:00
|
|
|
const elementType = isRest && checker.getElementTypeOfArrayType(genericParamType);
|
2019-08-30 22:44:06 +02:00
|
|
|
if (elementType) {
|
2019-09-06 20:27:34 +02:00
|
|
|
genericParamType = elementType;
|
2019-08-30 22:44:06 +02:00
|
|
|
}
|
2021-03-02 21:36:26 +01:00
|
|
|
const targetType = (usageParam as SymbolLinks).type
|
|
|
|
|| (usageParam.valueDeclaration ? checker.getTypeOfSymbolAtLocation(usageParam, usageParam.valueDeclaration) : checker.getAnyType());
|
2019-09-12 20:30:51 +02:00
|
|
|
types.push(...inferTypeParameters(genericParamType, targetType, typeParameter));
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
2019-09-06 20:27:34 +02:00
|
|
|
const genericReturn = checker.getReturnTypeOfSignature(genericSig);
|
|
|
|
const usageReturn = checker.getReturnTypeOfSignature(usageSig);
|
2019-09-12 20:30:51 +02:00
|
|
|
types.push(...inferTypeParameters(genericReturn, usageReturn, typeParameter));
|
2019-08-30 22:44:06 +02:00
|
|
|
return types;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getFunctionFromCalls(calls: CallUsage[]) {
|
2021-06-21 20:25:42 +02:00
|
|
|
return checker.createAnonymousType(/*symbol*/ undefined, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, emptyArray);
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
|
2019-09-06 01:21:08 +02:00
|
|
|
function getSignatureFromCalls(calls: CallUsage[]): Signature {
|
2017-10-12 19:14:58 +02:00
|
|
|
const parameters: Symbol[] = [];
|
2019-08-27 22:13:58 +02:00
|
|
|
const length = Math.max(...calls.map(c => c.argumentTypes.length));
|
|
|
|
for (let i = 0; i < length; i++) {
|
2017-10-12 19:14:58 +02:00
|
|
|
const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`arg${i}`));
|
2019-09-12 20:30:51 +02:00
|
|
|
symbol.type = combineTypes(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType()));
|
2019-08-27 22:13:58 +02:00
|
|
|
if (calls.some(call => call.argumentTypes[i] === undefined)) {
|
|
|
|
symbol.flags |= SymbolFlags.Optional;
|
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
parameters.push(symbol);
|
|
|
|
}
|
2019-09-12 20:30:51 +02:00
|
|
|
const returnType = combineFromUsage(combineUsages(calls.map(call => call.return_)));
|
2021-05-24 15:50:20 +02:00
|
|
|
return checker.createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, SignatureFlags.None);
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
|
2019-08-26 22:20:42 +02:00
|
|
|
function addCandidateType(usage: Usage, type: Type | undefined) {
|
2017-10-12 19:14:58 +02:00
|
|
|
if (type && !(type.flags & TypeFlags.Any) && !(type.flags & TypeFlags.Never)) {
|
2019-08-26 22:20:42 +02:00
|
|
|
(usage.candidateTypes || (usage.candidateTypes = [])).push(type);
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-26 22:20:42 +02:00
|
|
|
function addCandidateThisType(usage: Usage, type: Type | undefined) {
|
2019-06-18 04:00:15 +02:00
|
|
|
if (type && !(type.flags & TypeFlags.Any) && !(type.flags & TypeFlags.Never)) {
|
2019-08-26 22:20:42 +02:00
|
|
|
(usage.candidateThisTypes || (usage.candidateThisTypes = [])).push(type);
|
2019-06-18 04:00:15 +02:00
|
|
|
}
|
|
|
|
}
|
2017-10-12 19:14:58 +02:00
|
|
|
}
|
|
|
|
}
|