fix(52664): Assertion failure on completions for derived class with computed base property name (#52673)

This commit is contained in:
Oleksandr T 2023-02-18 03:36:52 +02:00 committed by GitHub
parent 04637662f4
commit bbb98cf797
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 314 additions and 27 deletions

View File

@ -42817,17 +42817,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* Checks a member declaration node to see if has a missing or invalid `override` modifier.
* @param node Class-like node where the member is declared.
* @param member Member declaration node.
* @param memberSymbol Member symbol.
* Note: `member` can be a synthetic node without a parent.
*/
function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus {
function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement, memberSymbol: Symbol): MemberOverrideStatus {
if (!member.name) {
return MemberOverrideStatus.Ok;
}
const symbol = getSymbolOfDeclaration(node);
const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType;
const classSymbol = getSymbolOfDeclaration(node);
const type = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType;
const typeWithThis = getTypeWithThisArgument(type);
const staticType = getTypeOfSymbol(symbol) as ObjectType;
const staticType = getTypeOfSymbol(classSymbol) as ObjectType;
const baseTypeNode = getEffectiveBaseTypeNode(node);
const baseTypes = baseTypeNode && getBaseTypes(type);
@ -42838,8 +42839,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
? hasOverrideModifier(member)
: hasSyntacticModifier(member, ModifierFlags.Override);
const memberName = unescapeLeadingUnderscores(getTextOfPropertyName(member.name));
return checkMemberForOverrideModifier(
node,
staticType,
@ -42851,7 +42850,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
hasAbstractModifier(member),
isStatic(member),
/* memberIsParameterProperty */ false,
memberName,
symbolName(memberSymbol),
);
}

View File

@ -5238,7 +5238,7 @@ export interface TypeChecker {
/** @internal */ isDeclarationVisible(node: Declaration | AnyImportSyntax): boolean;
/** @internal */ isPropertyAccessible(node: Node, isSuper: boolean, isWrite: boolean, containingType: Type, property: Symbol): boolean;
/** @internal */ getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyAliasDeclaration | undefined;
/** @internal */ getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus;
/** @internal */ getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement, memberSymbol: Symbol): MemberOverrideStatus;
/** @internal */ isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node): boolean;
}

View File

@ -62,6 +62,7 @@ import {
first,
firstDefined,
flatMap,
forEach,
formatting,
FunctionLikeDeclaration,
getAllSuperTypeNodes,
@ -427,15 +428,16 @@ export enum CompletionSource {
/** @internal */
export const enum SymbolOriginInfoKind {
ThisType = 1 << 0,
SymbolMember = 1 << 1,
Export = 1 << 2,
Promise = 1 << 3,
Nullable = 1 << 4,
ResolvedExport = 1 << 5,
TypeOnlyAlias = 1 << 6,
ObjectLiteralMethod = 1 << 7,
Ignore = 1 << 8,
ThisType = 1 << 0,
SymbolMember = 1 << 1,
Export = 1 << 2,
Promise = 1 << 3,
Nullable = 1 << 4,
ResolvedExport = 1 << 5,
TypeOnlyAlias = 1 << 6,
ObjectLiteralMethod = 1 << 7,
Ignore = 1 << 8,
ComputedPropertyName = 1 << 9,
SymbolMemberNoExport = SymbolMember,
SymbolMemberExport = SymbolMember | Export,
@ -475,6 +477,10 @@ interface SymbolOriginInfoObjectLiteralMethod extends SymbolOriginInfo {
isSnippet?: true,
}
interface SymbolOriginInfoComputedPropertyName extends SymbolOriginInfo {
symbolName: string;
}
function originIsThisType(origin: SymbolOriginInfo): boolean {
return !!(origin.kind & SymbolOriginInfoKind.ThisType);
}
@ -491,8 +497,8 @@ function originIsResolvedExport(origin: SymbolOriginInfo | undefined): origin is
return !!(origin && origin.kind === SymbolOriginInfoKind.ResolvedExport);
}
function originIncludesSymbolName(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport | SymbolOriginInfoResolvedExport {
return originIsExport(origin) || originIsResolvedExport(origin);
function originIncludesSymbolName(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport | SymbolOriginInfoResolvedExport | SymbolOriginInfoComputedPropertyName {
return originIsExport(origin) || originIsResolvedExport(origin) || originIsComputedPropertyName(origin);
}
function originIsPackageJsonImport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport {
@ -519,6 +525,10 @@ function originIsIgnore(origin: SymbolOriginInfo | undefined): boolean {
return !!(origin && origin.kind & SymbolOriginInfoKind.Ignore);
}
function originIsComputedPropertyName(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoComputedPropertyName {
return !!(origin && origin.kind & SymbolOriginInfoKind.ComputedPropertyName);
}
/** @internal */
export interface UniqueNameSet {
add(name: string): void;
@ -1552,10 +1562,9 @@ function getEntryForMemberCompletion(
requiredModifiers |= ModifierFlags.Abstract;
}
if (isClassElement(node)
&& checker.getMemberOverrideModifierStatus(classLikeDeclaration, node) === MemberOverrideStatus.NeedsOverride) {
&& checker.getMemberOverrideModifierStatus(classLikeDeclaration, node, symbol) === MemberOverrideStatus.NeedsOverride) {
requiredModifiers |= ModifierFlags.Override;
}
if (!completionNodes.length) {
// Keep track of added missing required modifiers and modifiers already present.
// This is needed when we have overloaded signatures,
@ -2322,7 +2331,8 @@ export function getCompletionEntryDetails(
case "symbol": {
const { symbol, location, contextToken, origin, previousToken } = symbolCompletion;
const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(name, location, contextToken, origin, symbol, program, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences, data, source, cancellationToken);
return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217
const symbolName = originIsComputedPropertyName(origin) ? origin.symbolName : symbol.name;
return createCompletionDetailsForSymbol(symbol, symbolName, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217
}
case "literal": {
const { literal } = symbolCompletion;
@ -2374,12 +2384,12 @@ function createSimpleDetails(name: string, kind: ScriptElementKind, kind2: Symbo
}
/** @internal */
export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails {
export function createCompletionDetailsForSymbol(symbol: Symbol, name: string, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails {
const { displayParts, documentation, symbolKind, tags } =
checker.runWithCancellationToken(cancellationToken, checker =>
SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All)
);
return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay);
return createCompletionDetails(name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay);
}
/** @internal */
@ -3963,8 +3973,17 @@ function getCompletionData(
type && typeChecker.getPropertiesOfType(type);
});
symbols = concatenate(symbols, filterClassMembersList(baseSymbols, decl.members, classElementModifierFlags));
forEach(symbols, (symbol, index) => {
const declaration = symbol?.valueDeclaration;
if (declaration && isClassElement(declaration) && declaration.name && isComputedPropertyName(declaration.name)) {
const origin: SymbolOriginInfoComputedPropertyName = {
kind: SymbolOriginInfoKind.ComputedPropertyName,
symbolName: typeChecker.symbolToString(symbol),
};
symbolToOriginInfoMap[index] = origin;
}
});
}
return GlobalsSearch.Success;
}
@ -4545,7 +4564,7 @@ function getCompletionEntryDisplayNameForSymbol(
}
switch (kind) {
case CompletionKind.MemberLike:
return undefined;
return originIsComputedPropertyName(origin) ? { name: origin.symbolName, needsConvertPropertyAccess: false } : undefined;
case CompletionKind.ObjectPropertyDeclaration:
// TODO: GH#18169
return { name: JSON.stringify(name), needsConvertPropertyAccess: false };

View File

@ -287,7 +287,7 @@ function stringLiteralCompletionDetails(name: string, location: Node, completion
}
case StringLiteralCompletionKind.Properties: {
const match = find(completion.symbols, s => s.name === name);
return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location, cancellationToken);
return match && createCompletionDetailsForSymbol(match, match.name, checker, sourceFile, location, cancellationToken);
}
case StringLiteralCompletionKind.Types:
return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.string, [textPart(name)]) : undefined;

View File

@ -0,0 +1,254 @@
=== /tests/cases/fourslash/completionsClassMembers5.ts ===
// export const SOME_CONSTANT = 'SOME_TEXT';
// export class Base {
// [SOME_CONSTANT]: boolean;
// }
// export class Derived extends Base {
//
// ^
// | ----------------------------------------------------------------------
// | abstract
// | accessor
// | async
// | constructor
// | declare
// | get
// | override
// | private
// | protected
// | public
// | readonly
// | set
// | static
// | (property) Base[SOME_CONSTANT]: boolean
// | ----------------------------------------------------------------------
// }
[
{
"marker": {
"fileName": "/tests/cases/fourslash/completionsClassMembers5.ts",
"position": 134,
"name": ""
},
"item": {
"flags": 0,
"isGlobalCompletion": false,
"isMemberCompletion": true,
"isNewIdentifierLocation": true,
"entries": [
{
"name": "abstract",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "abstract",
"kind": "keyword"
}
]
},
{
"name": "accessor",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "accessor",
"kind": "keyword"
}
]
},
{
"name": "async",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "async",
"kind": "keyword"
}
]
},
{
"name": "constructor",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "constructor",
"kind": "keyword"
}
]
},
{
"name": "declare",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "declare",
"kind": "keyword"
}
]
},
{
"name": "get",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "get",
"kind": "keyword"
}
]
},
{
"name": "override",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "override",
"kind": "keyword"
}
]
},
{
"name": "private",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "private",
"kind": "keyword"
}
]
},
{
"name": "protected",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "protected",
"kind": "keyword"
}
]
},
{
"name": "public",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "public",
"kind": "keyword"
}
]
},
{
"name": "readonly",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "readonly",
"kind": "keyword"
}
]
},
{
"name": "set",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "set",
"kind": "keyword"
}
]
},
{
"name": "static",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "static",
"kind": "keyword"
}
]
},
{
"name": "[SOME_CONSTANT]",
"kind": "property",
"kindModifiers": "",
"sortText": "17",
"insertText": "[SOME_CONSTANT]: boolean;",
"isSnippet": true,
"displayParts": [
{
"text": "(",
"kind": "punctuation"
},
{
"text": "property",
"kind": "text"
},
{
"text": ")",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "Base",
"kind": "className"
},
{
"text": "[",
"kind": "punctuation"
},
{
"text": "SOME_CONSTANT",
"kind": "propertyName"
},
{
"text": "]",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "boolean",
"kind": "keyword"
}
],
"documentation": []
}
]
}
}
]

View File

@ -0,0 +1,15 @@
/// <reference path="fourslash.ts" />
////export const SOME_CONSTANT = 'SOME_TEXT';
////export class Base {
//// [SOME_CONSTANT]: boolean;
////}
////export class Derived extends Base {
//// /**/
////}
verify.baselineCompletions({
includeCompletionsWithInsertText: true,
includeCompletionsWithSnippetText: true,
includeCompletionsWithClassMemberSnippets: true,
});