TypeScript/src/services/codefixes/inferFromUsage.ts

1161 lines
59 KiB
TypeScript
Raw Normal View History

/* @internal */
namespace ts.codefix {
const fixId = "inferFromUsage";
const errorCodes = [
// Variable declarations
Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code,
// Variable uses
Diagnostics.Variable_0_implicitly_has_an_1_type.code,
// Parameter declarations
Diagnostics.Parameter_0_implicitly_has_an_1_type.code,
Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code,
// 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,
// Set Accessor declarations
Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code,
// Property declarations
Diagnostics.Member_0_implicitly_has_an_1_type.code,
//// 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,
// Function expressions and declarations
Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code,
];
registerCodeFix({
errorCodes,
getCodeActions(context) {
const { sourceFile, program, span: { start }, errorCode, cancellationToken, host, preferences } = context;
const token = getTokenAtPosition(sourceFile, start);
let declaration: Declaration | undefined;
const changes = textChanges.ChangeTracker.with(context, changes => {
declaration = doChange(changes, sourceFile, token, errorCode, program, cancellationToken, /*markSeen*/ returnTrue, host, preferences);
});
const name = declaration && getNameOfDeclaration(declaration);
return !name || changes.length === 0 ? undefined
: [createCodeFixAction(fixId, changes, [getDiagnostic(errorCode, token), name.getText(sourceFile)], fixId, Diagnostics.Infer_all_types_from_usage)];
},
fixIds: [fixId],
getAllCodeActions(context) {
const { sourceFile, program, cancellationToken, host, preferences } = context;
const markSeen = nodeSeenTracker();
return codeFixAll(context, errorCodes, (changes, err) => {
doChange(changes, sourceFile, getTokenAtPosition(err.file, err.start), err.code, program, cancellationToken, markSeen, host, preferences);
});
},
});
function getDiagnostic(errorCode: number, token: Node): DiagnosticMessage {
switch (errorCode) {
case Diagnostics.Parameter_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 isSetAccessorDeclaration(getContainingFunction(token)!) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; // TODO: GH#18217
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code:
return Diagnostics.Infer_parameter_types_from_usage;
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;
default:
return Diagnostics.Infer_type_of_0_from_usage;
}
}
/** 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;
}
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker, host: LanguageServiceHost, preferences: UserPreferences): Declaration | undefined {
if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken && token.kind !== SyntaxKind.ThisKeyword) {
return undefined;
}
const { parent } = token;
const importAdder = createImportAdder(sourceFile, program, preferences, host);
errorCode = mapSuggestionDiagnostic(errorCode);
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:
if ((isVariableDeclaration(parent) && markSeen(parent)) || isPropertyDeclaration(parent) || isPropertySignature(parent)) { // handle bad location
annotateVariableDeclaration(changes, importAdder, sourceFile, parent, program, host, cancellationToken);
importAdder.writeFixes(changes);
return parent;
}
if (isPropertyAccessExpression(parent)) {
const type = inferTypeForVariableFromUsage(parent.name, program, cancellationToken);
const typeNode = getTypeNodeIfAccessible(type, parent, program, host);
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
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]);
}
importAdder.writeFixes(changes);
return parent;
}
return undefined;
case Diagnostics.Variable_0_implicitly_has_an_1_type.code: {
const symbol = program.getTypeChecker().getSymbolAtLocation(token);
if (symbol && symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && markSeen(symbol.valueDeclaration)) {
annotateVariableDeclaration(changes, importAdder, sourceFile, symbol.valueDeclaration, program, host, cancellationToken);
importAdder.writeFixes(changes);
return symbol.valueDeclaration;
}
return undefined;
}
}
const containingFunction = getContainingFunction(token);
if (containingFunction === undefined) {
return undefined;
}
let declaration: Declaration | undefined;
switch (errorCode) {
// Parameter declarations
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
if (isSetAccessorDeclaration(containingFunction)) {
annotateSetAccessor(changes, importAdder, sourceFile, containingFunction, program, host, cancellationToken);
declaration = containingFunction;
break;
}
// falls through
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
if (markSeen(containingFunction)) {
const param = cast(parent, isParameter);
annotateParameters(changes, importAdder, sourceFile, param, containingFunction, program, host, cancellationToken);
declaration = param;
}
break;
// 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:
if (isGetAccessorDeclaration(containingFunction) && isIdentifier(containingFunction.name)) {
annotate(changes, importAdder, sourceFile, containingFunction, inferTypeForVariableFromUsage(containingFunction.name, program, cancellationToken), program, host);
declaration = containingFunction;
}
break;
// Set Accessor declarations
case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code:
if (isSetAccessorDeclaration(containingFunction)) {
annotateSetAccessor(changes, importAdder, sourceFile, containingFunction, program, host, cancellationToken);
declaration = containingFunction;
}
break;
// 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);
declaration = containingFunction;
}
break;
default:
return Debug.fail(String(errorCode));
}
importAdder.writeFixes(changes);
return declaration;
}
function annotateVariableDeclaration(
changes: textChanges.ChangeTracker,
importAdder: ImportAdder,
sourceFile: SourceFile,
declaration: VariableDeclaration | PropertyDeclaration | PropertySignature,
program: Program,
host: LanguageServiceHost,
cancellationToken: CancellationToken,
): void {
if (isIdentifier(declaration.name)) {
annotate(changes, importAdder, sourceFile, declaration, inferTypeForVariableFromUsage(declaration.name, program, cancellationToken), program, host);
}
}
function annotateParameters(
changes: textChanges.ChangeTracker,
importAdder: ImportAdder,
sourceFile: SourceFile,
parameterDeclaration: ParameterDeclaration,
containingFunction: SignatureDeclaration,
program: Program,
host: LanguageServiceHost,
cancellationToken: CancellationToken,
): void {
if (!isIdentifier(parameterDeclaration.name)) {
return;
}
const parameterInferences = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken);
Debug.assert(containingFunction.parameters.length === parameterInferences.length, "Parameter count and inference count should match");
if (isInJSFile(containingFunction)) {
annotateJSDocParameters(changes, sourceFile, parameterInferences, program, host);
}
else {
const needParens = isArrowFunction(containingFunction) && !findChildOfKind(containingFunction, SyntaxKind.OpenParenToken, sourceFile);
if (needParens) changes.insertNodeBefore(sourceFile, first(containingFunction.parameters), factory.createToken(SyntaxKind.OpenParenToken));
for (const { declaration, type } of parameterInferences) {
if (declaration && !declaration.type && !declaration.initializer) {
annotate(changes, importAdder, sourceFile, declaration, type, program, host);
}
}
if (needParens) changes.insertNodeAfter(sourceFile, last(containingFunction.parameters), factory.createToken(SyntaxKind.CloseParenToken));
}
}
function annotateThis(changes: textChanges.ChangeTracker, sourceFile: SourceFile, containingFunction: textChanges.ThisTypeAnnotatable, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken) {
const references = getFunctionReferences(containingFunction, sourceFile, program, cancellationToken);
if (!references || !references.length) {
return;
}
const thisInference = inferTypeFromReferences(program, references, cancellationToken).thisParameter();
const typeNode = getTypeNodeIfAccessible(thisInference, containingFunction, program, host);
if (!typeNode) {
return;
}
if (isInJSFile(containingFunction)) {
annotateJSDocThis(changes, sourceFile, containingFunction, typeNode);
}
else {
changes.tryInsertThisTypeAnnotation(sourceFile, containingFunction, typeNode);
}
}
function annotateJSDocThis(changes: textChanges.ChangeTracker, sourceFile: SourceFile, containingFunction: SignatureDeclaration, typeNode: TypeNode) {
addJSDocTags(changes, sourceFile, containingFunction, [
factory.createJSDocThisTag(/*tagName*/ undefined, factory.createJSDocTypeExpression(typeNode)),
]);
}
function annotateSetAccessor(
changes: textChanges.ChangeTracker,
importAdder: ImportAdder,
sourceFile: SourceFile,
setAccessorDeclaration: SetAccessorDeclaration,
program: Program,
host: LanguageServiceHost,
cancellationToken: CancellationToken,
): void {
const param = firstOrUndefined(setAccessorDeclaration.parameters);
if (param && isIdentifier(setAccessorDeclaration.name) && isIdentifier(param.name)) {
let type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, program, cancellationToken);
if (type === program.getTypeChecker().getAnyType()) {
type = inferTypeForVariableFromUsage(param.name, program, cancellationToken);
}
if (isInJSFile(setAccessorDeclaration)) {
annotateJSDocParameters(changes, sourceFile, [{ declaration: param, type }], program, host);
}
else {
annotate(changes, importAdder, sourceFile, param, type, program, host);
}
}
}
function annotate(changes: textChanges.ChangeTracker, importAdder: ImportAdder, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type, program: Program, host: LanguageServiceHost): void {
const typeNode = getTypeNodeIfAccessible(type, declaration, program, host);
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;
}
const typeExpression = factory.createJSDocTypeExpression(typeNode);
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]);
}
else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, declaration, sourceFile, changes, importAdder, getEmitScriptTarget(program.getCompilerOptions()))) {
changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode);
}
}
}
function tryReplaceImportTypeNodeWithAutoImport(
typeNode: TypeNode,
declaration: textChanges.TypeAnnotatable,
sourceFile: SourceFile,
changes: textChanges.ChangeTracker,
importAdder: ImportAdder,
scriptTarget: ScriptTarget
): boolean {
const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget);
if (importableReference && changes.tryInsertTypeAnnotation(sourceFile, declaration, importableReference.typeNode)) {
forEach(importableReference.symbols, s => importAdder.addImportFromExportedSymbol(s, /*usageIsTypeOnly*/ true));
return true;
}
return false;
}
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;
}
const inferences = mapDefined(parameterInferences, inference => {
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
if (param.initializer || getJSDocType(param) || !isIdentifier(param.name)) {
return;
}
const typeNode = inference.type && getTypeNodeIfAccessible(inference.type, param, program, host);
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 };
}
});
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 }) =>
factory.createJSDocParameterTag(/*tagName*/ undefined, name, /*isBracketed*/ !!isOptional, factory.createJSDocTypeExpression(typeNode), /* isNameFirst */ false, /*comment*/ undefined));
addJSDocTags(changes, sourceFile, signature, paramTags);
}
2018-10-25 01:14:52 +02:00
}
export function addJSDocTags(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parent: HasJSDoc, newTags: readonly JSDocTag[]): void {
const comments = flatMap(parent.jsDoc, j => typeof j.comment === "string" ? factory.createJSDocText(j.comment) : j.comment) as JSDocComment[];
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;
}));
const tag = factory.createJSDocComment(factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))), factory.createNodeArray([...(oldTags || emptyArray), ...unmergedNewTags]));
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) {
return signature.parent as HasJSDoc;
}
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
? 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:
return factory.createJSDocReturnTag(/*tagName*/ undefined, (newTag as JSDocReturnTag).typeExpression, oldTag.comment);
2018-10-25 01:14:52 +02:00
}
}
function getReferences(token: PropertyName | Token<SyntaxKind.ConstructorKeyword>, program: Program, cancellationToken: CancellationToken): readonly Identifier[] {
// Position shouldn't matter since token is not a SourceFile.
return mapDefined(FindAllReferences.getReferenceEntriesForNode(-1, token, program, program.getSourceFiles(), cancellationToken), entry =>
entry.kind !== FindAllReferences.EntryKind.Span ? tryCast(entry.node, isIdentifier) : undefined);
}
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 {
const references = getReferences(token, program, cancellationToken);
return inferTypeFromReferences(program, references, cancellationToken).single();
}
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()
}));
}
function getFunctionReferences(containingFunction: SignatureDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): readonly Identifier[] | undefined {
let searchToken;
switch (containingFunction.kind) {
case SyntaxKind.Constructor:
searchToken = findChildOfKind<Token<SyntaxKind.ConstructorKeyword>>(containingFunction, SyntaxKind.ConstructorKeyword, sourceFile);
break;
case SyntaxKind.ArrowFunction:
case SyntaxKind.FunctionExpression:
const parent = containingFunction.parent;
searchToken = (isVariableDeclaration(parent) || isPropertyDeclaration(parent)) && isIdentifier(parent.name) ?
parent.name :
containingFunction.name;
break;
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
searchToken = containingFunction.name;
break;
}
if (!searchToken) {
return undefined;
}
return getReferences(searchToken, program, cancellationToken);
}
interface ParameterInference {
readonly declaration: ParameterDeclaration;
readonly type: Type;
readonly isOptional?: boolean;
}
function inferTypeFromReferences(program: Program, references: readonly Identifier[], cancellationToken: CancellationToken) {
const checker = program.getTypeChecker();
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()),
];
return {
single,
parameters,
thisParameter,
};
interface CallUsage {
argumentTypes: Type[];
return_: Usage;
}
interface Usage {
isNumber: boolean | undefined;
isString: boolean | undefined;
/** Used ambiguously, eg x + ___ or object[___]; results in string | number if no other evidence exists */
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
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;
}
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
};
}
function single(): Type {
2019-09-12 20:30:51 +02:00
return combineTypes(inferTypesFromReferencesSingle(references));
}
function parameters(declaration: SignatureDeclaration): ParameterInference[] | undefined {
if (references.length === 0 || !declaration.parameters) {
return undefined;
}
const usage = createEmptyUsage();
for (const reference of references) {
cancellationToken.throwIfCancellationRequested();
calculateUsageOfNode(reference, usage);
}
const calls = [...usage.constructs || [], ...usage.calls || []];
return declaration.parameters.map((parameter, parameterIndex): ParameterInference => {
const types = [];
const isRest = isRestParameter(parameter);
let isOptional = false;
for (const call of calls) {
if (call.argumentTypes.length <= parameterIndex) {
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());
}
else if (isRest) {
for (let i = parameterIndex; i < call.argumentTypes.length; i++) {
types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[i]));
}
}
else {
types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex]));
}
}
if (isIdentifier(parameter.name)) {
const inferred = inferTypesFromReferencesSingle(getReferences(parameter.name, program, cancellationToken));
types.push(...(isRest ? mapDefined(inferred, checker.getElementTypeOfArrayType) : inferred));
}
2019-09-12 20:30:51 +02:00
const type = combineTypes(types);
return {
type: isRest ? checker.createArrayType(type) : type,
isOptional: isOptional && !isRest,
declaration: parameter
};
});
}
function thisParameter() {
const usage = createEmptyUsage();
for (const reference of references) {
cancellationToken.throwIfCancellationRequested();
calculateUsageOfNode(reference, usage);
}
2019-09-12 20:30:51 +02:00
return combineTypes(usage.candidateThisTypes || emptyArray);
}
function inferTypesFromReferencesSingle(references: readonly Identifier[]): Type[] {
const usage: Usage = createEmptyUsage();
for (const reference of references) {
cancellationToken.throwIfCancellationRequested();
calculateUsageOfNode(reference, usage);
}
2019-09-12 20:30:51 +02:00
return inferTypes(usage);
}
function calculateUsageOfNode(node: Expression, usage: Usage): void {
while (isRightSideOfQualifiedNameOrPropertyAccess(node)) {
node = node.parent as Expression;
}
switch (node.parent.kind) {
case SyntaxKind.ExpressionStatement:
inferTypeFromExpressionStatement(node, usage);
break;
case SyntaxKind.PostfixUnaryExpression:
usage.isNumber = true;
break;
case SyntaxKind.PrefixUnaryExpression:
inferTypeFromPrefixUnaryExpression(node.parent as PrefixUnaryExpression, usage);
break;
case SyntaxKind.BinaryExpression:
inferTypeFromBinaryExpression(node, node.parent as BinaryExpression, usage);
break;
case SyntaxKind.CaseClause:
case SyntaxKind.DefaultClause:
inferTypeFromSwitchStatementLabel(node.parent as CaseOrDefaultClause, usage);
break;
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
if ((node.parent as CallExpression | NewExpression).expression === node) {
inferTypeFromCallExpression(node.parent as CallExpression | NewExpression, usage);
}
else {
inferTypeFromContextualType(node, usage);
}
break;
case SyntaxKind.PropertyAccessExpression:
inferTypeFromPropertyAccessExpression(node.parent as PropertyAccessExpression, usage);
break;
case SyntaxKind.ElementAccessExpression:
inferTypeFromPropertyElementExpression(node.parent as ElementAccessExpression, node, usage);
break;
case SyntaxKind.PropertyAssignment:
case SyntaxKind.ShorthandPropertyAssignment:
inferTypeFromPropertyAssignment(node.parent as PropertyAssignment | ShorthandPropertyAssignment, usage);
break;
case SyntaxKind.PropertyDeclaration:
inferTypeFromPropertyDeclaration(node.parent as PropertyDeclaration, usage);
break;
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.
addCandidateType(usage, checker.getTypeAtLocation(initializer));
}
break;
}
}
2019-06-24 13:55:58 +02:00
// falls through
default:
return inferTypeFromContextualType(node, usage);
}
}
function inferTypeFromContextualType(node: Expression, usage: Usage): void {
2017-10-31 16:05:39 +01:00
if (isExpressionNode(node)) {
addCandidateType(usage, checker.getContextualType(node));
}
}
function inferTypeFromExpressionStatement(node: Expression, usage: Usage): void {
addCandidateType(usage, isCallExpression(node) ? checker.getVoidType() : checker.getAnyType());
}
function inferTypeFromPrefixUnaryExpression(node: PrefixUnaryExpression, usage: Usage): void {
switch (node.operator) {
case SyntaxKind.PlusPlusToken:
case SyntaxKind.MinusMinusToken:
case SyntaxKind.MinusToken:
case SyntaxKind.TildeToken:
usage.isNumber = true;
break;
case SyntaxKind.PlusToken:
usage.isNumberOrString = true;
break;
// case SyntaxKind.ExclamationToken:
// no inferences here;
}
}
function inferTypeFromBinaryExpression(node: Expression, parent: BinaryExpression, usage: Usage): void {
switch (parent.operatorToken.kind) {
// ExponentiationOperator
case SyntaxKind.AsteriskAsteriskToken:
// MultiplicativeOperator
2019-06-21 17:32:22 +02:00
// falls through
case SyntaxKind.AsteriskToken:
case SyntaxKind.SlashToken:
case SyntaxKind.PercentToken:
// ShiftOperator
2019-06-21 17:32:22 +02:00
// falls through
case SyntaxKind.LessThanLessThanToken:
case SyntaxKind.GreaterThanGreaterThanToken:
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
// BitwiseOperator
2019-06-21 17:32:22 +02:00
// falls through
case SyntaxKind.AmpersandToken:
case SyntaxKind.BarToken:
case SyntaxKind.CaretToken:
// CompoundAssignmentOperator
2019-06-21 17:32:22 +02:00
// falls through
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
case SyntaxKind.MinusToken:
// RelationalOperator
2019-06-21 17:32:22 +02:00
// falls through
case SyntaxKind.LessThanToken:
case SyntaxKind.LessThanEqualsToken:
case SyntaxKind.GreaterThanToken:
case SyntaxKind.GreaterThanEqualsToken:
const operandType = checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left);
if (operandType.flags & TypeFlags.EnumLike) {
addCandidateType(usage, operandType);
}
else {
usage.isNumber = true;
}
break;
case SyntaxKind.PlusEqualsToken:
case SyntaxKind.PlusToken:
const otherOperandType = checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left);
if (otherOperandType.flags & TypeFlags.EnumLike) {
addCandidateType(usage, otherOperandType);
}
else if (otherOperandType.flags & TypeFlags.NumberLike) {
usage.isNumber = true;
}
else if (otherOperandType.flags & TypeFlags.StringLike) {
usage.isString = true;
}
else if (otherOperandType.flags & TypeFlags.Any) {
// do nothing, maybe we'll learn something elsewhere
}
else {
usage.isNumberOrString = true;
}
break;
// AssignmentOperators
case SyntaxKind.EqualsToken:
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
addCandidateType(usage, checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left));
break;
case SyntaxKind.InKeyword:
if (node === parent.left) {
usage.isString = true;
}
break;
// LogicalOperator Or NullishCoalescing
case SyntaxKind.BarBarToken:
case SyntaxKind.QuestionQuestionToken:
if (node === parent.left &&
(node.parent.parent.kind === SyntaxKind.VariableDeclaration || isAssignmentExpression(node.parent.parent, /*excludeCompoundAssignment*/ true))) {
// var x = x || {};
// TODO: use getFalsyflagsOfType
addCandidateType(usage, checker.getTypeAtLocation(parent.right));
}
break;
case SyntaxKind.AmpersandAmpersandToken:
case SyntaxKind.CommaToken:
case SyntaxKind.InstanceOfKeyword:
// nothing to infer here
break;
}
}
function inferTypeFromSwitchStatementLabel(parent: CaseOrDefaultClause, usage: Usage): void {
addCandidateType(usage, checker.getTypeAtLocation(parent.parent.parent.expression));
}
function inferTypeFromCallExpression(parent: CallExpression | NewExpression, usage: Usage): void {
const call: CallUsage = {
argumentTypes: [],
return_: createEmptyUsage()
};
if (parent.arguments) {
for (const argument of parent.arguments) {
call.argumentTypes.push(checker.getTypeAtLocation(argument));
}
}
calculateUsageOfNode(parent, call.return_);
if (parent.kind === SyntaxKind.CallExpression) {
(usage.calls || (usage.calls = [])).push(call);
}
else {
(usage.constructs || (usage.constructs = [])).push(call);
}
}
function inferTypeFromPropertyAccessExpression(parent: PropertyAccessExpression, usage: Usage): void {
const name = escapeLeadingUnderscores(parent.name.text);
if (!usage.properties) {
2020-06-26 01:03:25 +02:00
usage.properties = new Map();
}
const propertyUsage = usage.properties.get(name) || createEmptyUsage();
calculateUsageOfNode(parent, propertyUsage);
usage.properties.set(name, propertyUsage);
}
function inferTypeFromPropertyElementExpression(parent: ElementAccessExpression, node: Expression, usage: Usage): void {
if (node === parent.argumentExpression) {
usage.isNumberOrString = true;
return;
}
else {
const indexType = checker.getTypeAtLocation(parent.argumentExpression);
const indexUsage = createEmptyUsage();
calculateUsageOfNode(parent, indexUsage);
if (indexType.flags & TypeFlags.NumberLike) {
usage.numberIndex = indexUsage;
}
else {
usage.stringIndex = indexUsage;
}
}
}
function inferTypeFromPropertyAssignment(assignment: PropertyAssignment | ShorthandPropertyAssignment, usage: Usage) {
const nodeWithRealType = isVariableDeclaration(assignment.parent.parent) ?
assignment.parent.parent :
assignment.parent;
addCandidateThisType(usage, checker.getTypeAtLocation(nodeWithRealType));
}
function inferTypeFromPropertyDeclaration(declaration: PropertyDeclaration, usage: Usage) {
addCandidateThisType(usage, checker.getTypeAtLocation(declaration.parent));
}
interface Priority {
high: (t: Type) => boolean;
low: (t: Type) => boolean;
}
function removeLowPriorityInferences(inferences: readonly Type[], priorities: Priority[]): Type[] {
const toRemove: ((t: Type) => boolean)[] = [];
for (const i of inferences) {
for (const { high, low } of priorities) {
if (high(i)) {
Debug.assert(!low(i), "Priority can't have both low and high");
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 {
if (!inferences.length) return checker.getAnyType();
// 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))
},
{
high: t => !(t.flags & (TypeFlags.Nullable | TypeFlags.Any | TypeFlags.Void)) && !(getObjectFlags(t) & ObjectFlags.Anonymous),
low: t => !!(getObjectFlags(t) & ObjectFlags.Anonymous)
}];
let good = removeLowPriorityInferences(inferences, priorities);
const anons = good.filter(i => getObjectFlags(i) & ObjectFlags.Anonymous) as AnonymousType[];
if (anons.length) {
good = good.filter(i => !(getObjectFlags(i) & ObjectFlags.Anonymous));
2019-09-12 20:30:51 +02:00
good.push(combineAnonymousTypes(anons));
}
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];
}
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)) {
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));
Index signatures for symbols and template literal strings (#44512) * Switch index signature storage to 'indexInfos: IndexInfo[]' property * Accept new baselines * Remove another usage of IndexKind enum * Update getIndexedAccessType and resolveMappedTypeMembers * Accept new baselines * Update grammar checking for index signatures * Accept new baselines * Consider all index signatures in mapped types and union types * Accept new baselines * Update getIndexType * Accept new baselines * Intersect multiple applicable index signatures * Use getApplicableIndexInfo instead of hardwired string/number handling * Update index signature relationship checking * Report type for which index signature is missing * Report type for which index signature is missing * Accept new baselines * Make 'number' index signatures consistently apply to numeric strings * Accept new baselines * Update fourslash test * Revise index constraint checking * Accept new baselines * Update error messages * Accept new baselines * Update type inference from index signatures * Update isKnownProperty * Update contextual typing based on index signatures * Accept new baselines * Support union types in index signature declarations * Accept new baselines * Check duplicate index signatures / remove redundant template literals from unions with string * Accept new baselines * Include key type in diagnostic / check symbol-named properties * Accept new baselines * Minor fix * Add tests * Accept new baselines * Add optimized findApplicableIndexInfoForName * Accept new baselines * Another place we don't need to obtain literal type for property name * Accept new baselines * Don't create literal types that are going to be discarded * Individual maps for string, number, bigint, and enum literal types * Remove ineffective optimizations * Accept new baselines * Permit intersections as key types in index signatures * Index expression in element access is template literal context * Add tests * Accept new baselines * Symbol index signatures from object literals with computed symbol properties * Accept new baselines * Add more tests * Accept new baselines * Implement Go To Definition for all applicable index signatures * Add fourslash test * Accept new API baselines
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
}
Index signatures for symbols and template literal strings (#44512) * Switch index signature storage to 'indexInfos: IndexInfo[]' property * Accept new baselines * Remove another usage of IndexKind enum * Update getIndexedAccessType and resolveMappedTypeMembers * Accept new baselines * Update grammar checking for index signatures * Accept new baselines * Consider all index signatures in mapped types and union types * Accept new baselines * Update getIndexType * Accept new baselines * Intersect multiple applicable index signatures * Use getApplicableIndexInfo instead of hardwired string/number handling * Update index signature relationship checking * Report type for which index signature is missing * Report type for which index signature is missing * Accept new baselines * Make 'number' index signatures consistently apply to numeric strings * Accept new baselines * Update fourslash test * Revise index constraint checking * Accept new baselines * Update error messages * Accept new baselines * Update type inference from index signatures * Update isKnownProperty * Update contextual typing based on index signatures * Accept new baselines * Support union types in index signature declarations * Accept new baselines * Check duplicate index signatures / remove redundant template literals from unions with string * Accept new baselines * Include key type in diagnostic / check symbol-named properties * Accept new baselines * Minor fix * Add tests * Accept new baselines * Add optimized findApplicableIndexInfoForName * Accept new baselines * Another place we don't need to obtain literal type for property name * Accept new baselines * Don't create literal types that are going to be discarded * Individual maps for string, number, bigint, and enum literal types * Remove ineffective optimizations * Accept new baselines * Permit intersections as key types in index signatures * Index expression in element access is template literal context * Add tests * Accept new baselines * Symbol index signatures from object literals with computed symbol properties * Accept new baselines * Add more tests * Accept new baselines * Implement Go To Definition for all applicable index signatures * Add fourslash test * Accept new API baselines
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
}
}
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];
});
Index signatures for symbols and template literal strings (#44512) * Switch index signature storage to 'indexInfos: IndexInfo[]' property * Accept new baselines * Remove another usage of IndexKind enum * Update getIndexedAccessType and resolveMappedTypeMembers * Accept new baselines * Update grammar checking for index signatures * Accept new baselines * Consider all index signatures in mapped types and union types * Accept new baselines * Update getIndexType * Accept new baselines * Intersect multiple applicable index signatures * Use getApplicableIndexInfo instead of hardwired string/number handling * Update index signature relationship checking * Report type for which index signature is missing * Report type for which index signature is missing * Accept new baselines * Make 'number' index signatures consistently apply to numeric strings * Accept new baselines * Update fourslash test * Revise index constraint checking * Accept new baselines * Update error messages * Accept new baselines * Update type inference from index signatures * Update isKnownProperty * Update contextual typing based on index signatures * Accept new baselines * Support union types in index signature declarations * Accept new baselines * Check duplicate index signatures / remove redundant template literals from unions with string * Accept new baselines * Include key type in diagnostic / check symbol-named properties * Accept new baselines * Minor fix * Add tests * Accept new baselines * Add optimized findApplicableIndexInfoForName * Accept new baselines * Another place we don't need to obtain literal type for property name * Accept new baselines * Don't create literal types that are going to be discarded * Individual maps for string, number, bigint, and enum literal types * Remove ineffective optimizations * Accept new baselines * Permit intersections as key types in index signatures * Index expression in element access is template literal context * Add tests * Accept new baselines * Symbol index signatures from object literals with computed symbol properties * Accept new baselines * Add more tests * Accept new baselines * Implement Go To Definition for all applicable index signatures * Add fourslash test * Accept new API baselines
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,
Index signatures for symbols and template literal strings (#44512) * Switch index signature storage to 'indexInfos: IndexInfo[]' property * Accept new baselines * Remove another usage of IndexKind enum * Update getIndexedAccessType and resolveMappedTypeMembers * Accept new baselines * Update grammar checking for index signatures * Accept new baselines * Consider all index signatures in mapped types and union types * Accept new baselines * Update getIndexType * Accept new baselines * Intersect multiple applicable index signatures * Use getApplicableIndexInfo instead of hardwired string/number handling * Update index signature relationship checking * Report type for which index signature is missing * Report type for which index signature is missing * Accept new baselines * Make 'number' index signatures consistently apply to numeric strings * Accept new baselines * Update fourslash test * Revise index constraint checking * Accept new baselines * Update error messages * Accept new baselines * Update type inference from index signatures * Update isKnownProperty * Update contextual typing based on index signatures * Accept new baselines * Support union types in index signature declarations * Accept new baselines * Check duplicate index signatures / remove redundant template literals from unions with string * Accept new baselines * Include key type in diagnostic / check symbol-named properties * Accept new baselines * Minor fix * Add tests * Accept new baselines * Add optimized findApplicableIndexInfoForName * Accept new baselines * Another place we don't need to obtain literal type for property name * Accept new baselines * Don't create literal types that are going to be discarded * Individual maps for string, number, bigint, and enum literal types * Remove ineffective optimizations * Accept new baselines * Permit intersections as key types in index signatures * Index expression in element access is template literal context * Add tests * Accept new baselines * Symbol index signatures from object literals with computed symbol properties * Accept new baselines * Add more tests * Accept new baselines * Implement Go To Definition for all applicable index signatures * Add fourslash test * Accept new API baselines
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 = [];
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());
}
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());
}
if (usage.isNumberOrString) {
types.push(checker.getUnionType([checker.getStringType(), checker.getNumberType()]));
}
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
}
if (usage.properties?.size || usage.calls?.length || usage.constructs?.length || usage.stringIndex) {
2019-09-07 00:15:19 +02:00
types.push(inferStructuralType(usage));
}
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);
});
}
2019-09-07 00:15:19 +02:00
const callSignatures: Signature[] = usage.calls ? [getSignatureFromCalls(usage.calls)] : [];
const constructSignatures: Signature[] = usage.constructs ? [getSignatureFromCalls(usage.constructs)] : [];
Index signatures for symbols and template literal strings (#44512) * Switch index signature storage to 'indexInfos: IndexInfo[]' property * Accept new baselines * Remove another usage of IndexKind enum * Update getIndexedAccessType and resolveMappedTypeMembers * Accept new baselines * Update grammar checking for index signatures * Accept new baselines * Consider all index signatures in mapped types and union types * Accept new baselines * Update getIndexType * Accept new baselines * Intersect multiple applicable index signatures * Use getApplicableIndexInfo instead of hardwired string/number handling * Update index signature relationship checking * Report type for which index signature is missing * Report type for which index signature is missing * Accept new baselines * Make 'number' index signatures consistently apply to numeric strings * Accept new baselines * Update fourslash test * Revise index constraint checking * Accept new baselines * Update error messages * Accept new baselines * Update type inference from index signatures * Update isKnownProperty * Update contextual typing based on index signatures * Accept new baselines * Support union types in index signature declarations * Accept new baselines * Check duplicate index signatures / remove redundant template literals from unions with string * Accept new baselines * Include key type in diagnostic / check symbol-named properties * Accept new baselines * Minor fix * Add tests * Accept new baselines * Add optimized findApplicableIndexInfoForName * Accept new baselines * Another place we don't need to obtain literal type for property name * Accept new baselines * Don't create literal types that are going to be discarded * Individual maps for string, number, bigint, and enum literal types * Remove ineffective optimizations * Accept new baselines * Permit intersections as key types in index signatures * Index expression in element access is template literal context * Add tests * Accept new baselines * Symbol index signatures from object literals with computed symbol properties * Accept new baselines * Add more tests * Accept new baselines * Implement Go To Definition for all applicable index signatures * Add fourslash test * Accept new API baselines
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);
}
2019-09-06 20:18:50 +02:00
function inferNamedTypesFromProperties(usage: Usage): Type[] {
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));
}
return [];
}
2019-09-06 20:18:50 +02:00
function allPropertiesAreAssignableToUsage(type: Type, usage: Usage) {
if (!usage.properties) return false;
2019-09-24 00:57:32 +02:00
return !forEachEntry(usage.properties, (propUsage, name) => {
const source = checker.getTypeOfPropertyOfType(type, name as string);
if (!source) {
2019-09-24 00:57:32 +02:00
return true;
}
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));
}
else {
2019-09-24 00:57:32 +02:00
return !checker.isTypeAssignableTo(source, combineFromUsage(propUsage));
}
});
}
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;
}
const generic = (type as TypeReference).target;
const singleTypeParameter = singleOrUndefined(generic.typeParameters);
if (!singleTypeParameter) return type;
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.");
types.push(...inferTypeParameters(genericPropertyType, combineFromUsage(propUsage), singleTypeParameter));
});
2019-09-12 20:30:51 +02:00
return builtinConstructors[type.symbol.escapedName as string](combineTypes(types));
}
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-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-09-06 20:27:34 +02:00
else if (getObjectFlags(genericType) & ObjectFlags.Reference && getObjectFlags(usageType) & ObjectFlags.Reference) {
// this is wrong because we need a reference to the targetType to, so we can check that it's also a reference
const genericArgs = checker.getTypeArguments(genericType as TypeReference);
const usageArgs = checker.getTypeArguments(usageType as TypeReference);
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));
}
}
}
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);
}
return [];
}
2019-09-06 20:27:34 +02:00
function inferFromSignatures(genericSig: Signature, usageSig: Signature, typeParameter: Type) {
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) {
break;
}
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);
if (elementType) {
2019-09-06 20:27:34 +02:00
genericParamType = elementType;
}
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));
}
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));
return types;
}
function getFunctionFromCalls(calls: CallUsage[]) {
Index signatures for symbols and template literal strings (#44512) * Switch index signature storage to 'indexInfos: IndexInfo[]' property * Accept new baselines * Remove another usage of IndexKind enum * Update getIndexedAccessType and resolveMappedTypeMembers * Accept new baselines * Update grammar checking for index signatures * Accept new baselines * Consider all index signatures in mapped types and union types * Accept new baselines * Update getIndexType * Accept new baselines * Intersect multiple applicable index signatures * Use getApplicableIndexInfo instead of hardwired string/number handling * Update index signature relationship checking * Report type for which index signature is missing * Report type for which index signature is missing * Accept new baselines * Make 'number' index signatures consistently apply to numeric strings * Accept new baselines * Update fourslash test * Revise index constraint checking * Accept new baselines * Update error messages * Accept new baselines * Update type inference from index signatures * Update isKnownProperty * Update contextual typing based on index signatures * Accept new baselines * Support union types in index signature declarations * Accept new baselines * Check duplicate index signatures / remove redundant template literals from unions with string * Accept new baselines * Include key type in diagnostic / check symbol-named properties * Accept new baselines * Minor fix * Add tests * Accept new baselines * Add optimized findApplicableIndexInfoForName * Accept new baselines * Another place we don't need to obtain literal type for property name * Accept new baselines * Don't create literal types that are going to be discarded * Individual maps for string, number, bigint, and enum literal types * Remove ineffective optimizations * Accept new baselines * Permit intersections as key types in index signatures * Index expression in element access is template literal context * Add tests * Accept new baselines * Symbol index signatures from object literals with computed symbol properties * Accept new baselines * Add more tests * Accept new baselines * Implement Go To Definition for all applicable index signatures * Add fourslash test * Accept new API baselines
2021-06-21 20:25:42 +02:00
return checker.createAnonymousType(/*symbol*/ undefined, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, emptyArray);
}
function getSignatureFromCalls(calls: CallUsage[]): Signature {
const parameters: Symbol[] = [];
const length = Math.max(...calls.map(c => c.argumentTypes.length));
for (let i = 0; i < length; i++) {
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()));
if (calls.some(call => call.argumentTypes[i] === undefined)) {
symbol.flags |= SymbolFlags.Optional;
}
parameters.push(symbol);
}
2019-09-12 20:30:51 +02:00
const returnType = combineFromUsage(combineUsages(calls.map(call => call.return_)));
return checker.createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, SignatureFlags.None);
}
function addCandidateType(usage: Usage, type: Type | undefined) {
if (type && !(type.flags & TypeFlags.Any) && !(type.flags & TypeFlags.Never)) {
(usage.candidateTypes || (usage.candidateTypes = [])).push(type);
}
}
function addCandidateThisType(usage: Usage, type: Type | undefined) {
if (type && !(type.flags & TypeFlags.Any) && !(type.flags & TypeFlags.Never)) {
(usage.candidateThisTypes || (usage.candidateThisTypes = [])).push(type);
}
}
}
}