From 38f49c73b223c25f0be9db43824f4ccd787478cc Mon Sep 17 00:00:00 2001 From: davu Date: Sun, 12 Mar 2023 19:16:34 +0100 Subject: [PATCH 1/9] moving utils to dsymbol --- dsymbol/src/dsymbol/utils.d | 155 ++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 dsymbol/src/dsymbol/utils.d diff --git a/dsymbol/src/dsymbol/utils.d b/dsymbol/src/dsymbol/utils.d new file mode 100644 index 00000000..518166cc --- /dev/null +++ b/dsymbol/src/dsymbol/utils.d @@ -0,0 +1,155 @@ +module dsymbol.utils; +import dparse.lexer : tok, IdType; + +enum TYPE_IDENT_CASES = q{ + case tok!"int": + case tok!"uint": + case tok!"long": + case tok!"ulong": + case tok!"char": + case tok!"wchar": + case tok!"dchar": + case tok!"bool": + case tok!"byte": + case tok!"ubyte": + case tok!"short": + case tok!"ushort": + case tok!"cent": + case tok!"ucent": + case tok!"float": + case tok!"ifloat": + case tok!"cfloat": + case tok!"idouble": + case tok!"cdouble": + case tok!"double": + case tok!"real": + case tok!"ireal": + case tok!"creal": + case tok!"this": + case tok!"super": + case tok!"identifier": +}; + +enum STRING_LITERAL_CASES = q{ + case tok!"stringLiteral": + case tok!"wstringLiteral": + case tok!"dstringLiteral": +}; + +enum TYPE_IDENT_AND_LITERAL_CASES = TYPE_IDENT_CASES ~ STRING_LITERAL_CASES; + +/** + * Skips blocks of parentheses until the starting block has been closed + */ +void skipParen(T)(T tokenSlice, ref size_t i, IdType open, IdType close) +{ + if (i >= tokenSlice.length || tokenSlice.length <= 0) + return; + int depth = 1; + while (depth != 0 && i + 1 != tokenSlice.length) + { + i++; + if (tokenSlice[i].type == open) + depth++; + else if (tokenSlice[i].type == close) + depth--; + } +} + + +/** + * Skips blocks of parentheses in reverse until the starting block has been opened + */ +size_t skipParenReverse(T)(T beforeTokens, size_t i, IdType open, IdType close) +{ + if (i == 0) + return 0; + int depth = 1; + while (depth != 0 && i != 0) + { + i--; + if (beforeTokens[i].type == open) + depth++; + else if (beforeTokens[i].type == close) + depth--; + } + return i; +} + + + +size_t skipParenReverseBefore(T)(T beforeTokens, size_t i, IdType open, IdType close) +{ + i = skipParenReverse(beforeTokens, i, open, close); + if (i != 0) + i--; + return i; +} + + +/** + * Traverses a token slice in reverse to find the opening parentheses or square bracket + * that begins the block the last token is in. + */ +size_t goBackToOpenParen(T)(T beforeTokens) +in +{ + assert (beforeTokens.length > 0); +} +do +{ + size_t i = beforeTokens.length - 1; + while (true) switch (beforeTokens[i].type) + { + case tok!",": + case tok!".": + case tok!"*": + case tok!"&": + case tok!"doubleLiteral": + case tok!"floatLiteral": + case tok!"idoubleLiteral": + case tok!"ifloatLiteral": + case tok!"intLiteral": + case tok!"longLiteral": + case tok!"realLiteral": + case tok!"irealLiteral": + case tok!"uintLiteral": + case tok!"ulongLiteral": + case tok!"characterLiteral": + mixin(TYPE_IDENT_AND_LITERAL_CASES); + if (i == 0) + return size_t.max; + else + i--; + break; + case tok!"(": + case tok!"[": + return i + 1; + case tok!")": + i = beforeTokens.skipParenReverseBefore(i, tok!")", tok!"("); + break; + case tok!"}": + i = beforeTokens.skipParenReverseBefore(i, tok!"}", tok!"{"); + break; + case tok!"]": + i = beforeTokens.skipParenReverseBefore(i, tok!"]", tok!"["); + break; + default: + return size_t.max; + } +} + +///Testing skipping +unittest +{ + Token[] t = [ + Token(tok!"identifier"), Token(tok!"identifier"), Token(tok!"("), + Token(tok!"identifier"), Token(tok!"("), Token(tok!")"), Token(tok!",") + ]; + size_t i = t.length - 1; + i = skipParenReverse(t, i, tok!")", tok!"("); + assert(i == 2); + i = t.length - 1; + i = skipParenReverseBefore(t, i, tok!")", tok!"("); + assert(i == 1); +} From 2590c0ce59584995275218841624847fc8898139 Mon Sep 17 00:00:00 2001 From: davu Date: Sun, 12 Mar 2023 19:17:52 +0100 Subject: [PATCH 2/9] updating dcd tests --- tests/tc_calltip_in_func/file.d | 14 ++++++++++++++ tests/tc_calltip_in_func/run.sh | 2 +- tests/tc_erroneous_body_content/expected1.txt | 1 + tests/tc_incomplete_switch/expected1.txt | 1 + tests/tc_ufcs_calltip_in_func/file.d | 2 ++ tests/tc_ufcs_calltip_in_func/run.sh | 2 +- 6 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 tests/tc_calltip_in_func/file.d diff --git a/tests/tc_calltip_in_func/file.d b/tests/tc_calltip_in_func/file.d new file mode 100644 index 00000000..4b62f81a --- /dev/null +++ b/tests/tc_calltip_in_func/file.d @@ -0,0 +1,14 @@ +class Bar +{ + void fun(A param) + { + } +} + +class Foo +{ + void foo(Bar bar) + { + bar.fun( + } +} diff --git a/tests/tc_calltip_in_func/run.sh b/tests/tc_calltip_in_func/run.sh index b72bb146..ba2553c3 100755 --- a/tests/tc_calltip_in_func/run.sh +++ b/tests/tc_calltip_in_func/run.sh @@ -1,5 +1,5 @@ set -e set -u -../../bin/dcd-client $1 -c66 <<< "class Bar{void fun(A param){}}class Foo{void foo(Bar bar){bar.fun(}}" > actual.txt +../../bin/dcd-client $1 file.d -c84 > actual.txt diff actual.txt expected.txt --strip-trailing-cr diff --git a/tests/tc_erroneous_body_content/expected1.txt b/tests/tc_erroneous_body_content/expected1.txt index 12a38bbd..6dd7ccf4 100644 --- a/tests/tc_erroneous_body_content/expected1.txt +++ b/tests/tc_erroneous_body_content/expected1.txt @@ -1,6 +1,7 @@ identifiers alignof k b v +bar F init k mangleof k sizeof k diff --git a/tests/tc_incomplete_switch/expected1.txt b/tests/tc_incomplete_switch/expected1.txt index 5d30ee55..de86c558 100644 --- a/tests/tc_incomplete_switch/expected1.txt +++ b/tests/tc_incomplete_switch/expected1.txt @@ -2,6 +2,7 @@ identifiers __monitor v __vptr v alignof k +bar F classinfo v e v init k diff --git a/tests/tc_ufcs_calltip_in_func/file.d b/tests/tc_ufcs_calltip_in_func/file.d index 5299951b..8de68ba2 100644 --- a/tests/tc_ufcs_calltip_in_func/file.d +++ b/tests/tc_ufcs_calltip_in_func/file.d @@ -2,6 +2,8 @@ void showSomething(int x, string message, bool ok){} void showSomething(int x, string message, bool ok, float percentage){} void showSomething(string x, string message, bool ok){} void showSomething(){} +void showSomething(){} +void showNothing(int x){} int showSomething; struct showSomething { int x; } diff --git a/tests/tc_ufcs_calltip_in_func/run.sh b/tests/tc_ufcs_calltip_in_func/run.sh index a885d86f..f3b83682 100755 --- a/tests/tc_ufcs_calltip_in_func/run.sh +++ b/tests/tc_ufcs_calltip_in_func/run.sh @@ -1,5 +1,5 @@ set -e set -u -../../bin/dcd-client $1 -c293 file.d > actual.txt +../../bin/dcd-client $1 -c342 file.d > actual.txt diff actual.txt expected.txt --strip-trailing-cr From c4b8a7499133f1b715af4dbd5b044979f7db1306 Mon Sep 17 00:00:00 2001 From: davu Date: Mon, 13 Mar 2023 01:25:12 +0100 Subject: [PATCH 3/9] dsymbol deduces ufcs --- dsymbol/src/dsymbol/conversion/package.d | 6 +- dsymbol/src/dsymbol/tests.d | 68 ++++-- dsymbol/src/dsymbol/ufcs.d | 261 +++++++++++++++++------ dsymbol/src/dsymbol/utils.d | 2 +- src/dcd/server/autocomplete/complete.d | 10 +- src/dcd/server/autocomplete/util.d | 167 +-------------- 6 files changed, 266 insertions(+), 248 deletions(-) diff --git a/dsymbol/src/dsymbol/conversion/package.d b/dsymbol/src/dsymbol/conversion/package.d index e145cdca..e2aea8d2 100644 --- a/dsymbol/src/dsymbol/conversion/package.d +++ b/dsymbol/src/dsymbol/conversion/package.d @@ -31,6 +31,7 @@ import dsymbol.scope_; import dsymbol.semantic; import dsymbol.string_interning; import dsymbol.symbol; +import dsymbol.ufcs; import std.algorithm; import std.experimental.allocator; import containers.hashset; @@ -52,9 +53,11 @@ ScopeSymbolPair generateAutocompleteTrees(const(Token)[] tokens, thirdPass(first.moduleScope, cache, cursorPosition); + auto ufcsSymbols = getUFCSSymbolsForCursor(first.moduleScope, tokens, cursorPosition); + auto r = move(first.rootSymbol.acSymbol); typeid(SemanticSymbol).destroy(first.rootSymbol); - return ScopeSymbolPair(r, move(first.moduleScope)); + return ScopeSymbolPair(r, move(first.moduleScope), ufcsSymbols); } struct ScopeSymbolPair @@ -67,6 +70,7 @@ struct ScopeSymbolPair DSymbol* symbol; Scope* scope_; + DSymbol*[] ufcsSymbols; } /** diff --git a/dsymbol/src/dsymbol/tests.d b/dsymbol/src/dsymbol/tests.d index 1f8a946f..0b23aace 100644 --- a/dsymbol/src/dsymbol/tests.d +++ b/dsymbol/src/dsymbol/tests.d @@ -3,10 +3,11 @@ module dsymbol.tests; import std.experimental.allocator; import dparse.ast, dparse.parser, dparse.lexer, dparse.rollback_allocator; import dsymbol.cache_entry, dsymbol.modulecache, dsymbol.symbol; -import dsymbol.conversion, dsymbol.conversion.first, dsymbol.conversion.second; +import dsymbol.conversion, dsymbol.conversion.first, dsymbol.conversion.second, dsymbol.conversion.third; import dsymbol.semantic, dsymbol.string_interning, dsymbol.builtin.names; import std.file, std.path, std.format; import std.stdio : writeln, stdout; +import dsymbol.ufcs; /** * Parses `source`, caches its symbols and compares the the cache content @@ -413,7 +414,7 @@ unittest writeln("Running template type parameters tests..."); { auto source = q{ struct Foo(T : int){} struct Bar(T : Foo){} }; - auto pair = generateAutocompleteTrees(source, "", 0, cache); + auto pair = generateAutocompleteTreesProd(source, "", 0, cache); DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo")); DSymbol* T2 = T1.getFirstPartNamed(internString("T")); assert(T2.type.name == "int"); @@ -424,7 +425,7 @@ unittest } { auto source = q{ struct Foo(T){ }}; - auto pair = generateAutocompleteTrees(source, "", 0, cache); + auto pair = generateAutocompleteTreesProd(source, "", 0, cache); DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo")); assert(T1); DSymbol* T2 = T1.getFirstPartNamed(internString("T")); @@ -439,7 +440,7 @@ unittest writeln("Running template variadic parameters tests..."); auto source = q{ struct Foo(T...){ }}; - auto pair = generateAutocompleteTrees(source, "", 0, cache); + auto pair = generateAutocompleteTreesProd(source, "", 0, cache); DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo")); assert(T1); DSymbol* T2 = T1.getFirstPartNamed(internString("T")); @@ -528,15 +529,14 @@ unittest writeln("Testing protection scopes"); auto source = q{version(all) { private: } struct Foo{ }}; - auto pair = generateAutocompleteTrees(source, "", 0, cache); + auto pair = generateAutocompleteTreesProd(source, "", 0, cache); DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo")); assert(T1); assert(T1.protection != tok!"private"); } // check for memory leaks on thread termination (in static constructors) -version (linux) -unittest +version (linux) unittest { import core.memory : GC; import core.thread : Thread; @@ -584,6 +584,7 @@ static this() { stringCache = StringCache(StringCache.defaultBucketCount); } + static ~this() { destroy(stringCache); @@ -598,6 +599,7 @@ const(Token)[] lex(string source, string filename) { import dparse.lexer : getTokensForParser; import std.string : representation; + LexerConfig config; config.fileName = filename; return getTokensForParser(source.dup.representation, config, &stringCache); @@ -626,7 +628,7 @@ ScopeSymbolPair generateAutocompleteTrees(string source, ref ModuleCache cache) return generateAutocompleteTrees(source, randomDFilename, cache); } -ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref ModuleCache cache) +ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref ModuleCache cache, size_t cursorPosition = -1) { auto tokens = lex(source); RollbackAllocator rba; @@ -635,21 +637,57 @@ ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref Mo scope first = new FirstPass(m, internString(filename), &cache); first.run(); + secondPass(first.rootSymbol, first.moduleScope, cache); + thirdPass(first.moduleScope, cache, cursorPosition); + auto ufcsSymbols = getUFCSSymbolsForCursor(first.moduleScope, tokens, cursorPosition); auto r = first.rootSymbol.acSymbol; typeid(SemanticSymbol).destroy(first.rootSymbol); - return ScopeSymbolPair(r, first.moduleScope); + return ScopeSymbolPair(r, first.moduleScope, ufcsSymbols); } -ScopeSymbolPair generateAutocompleteTrees(string source, size_t cursorPosition, ref ModuleCache cache) -{ - return generateAutocompleteTrees(source, null, cache); -} - -ScopeSymbolPair generateAutocompleteTrees(string source, string filename, size_t cursorPosition, ref ModuleCache cache) +ScopeSymbolPair generateAutocompleteTreesProd(string source, string filename, size_t cursorPosition, ref ModuleCache cache) { auto tokens = lex(source); RollbackAllocator rba; return dsymbol.conversion.generateAutocompleteTrees( tokens, &rba, cursorPosition, cache); } + + + +version (linux) +{ + import std.string; + enum string ufcsExampleCode = +q{class Incrementer +{ + int run(int x) + { + return x++; + } +} +int increment(int x) +{ + return x++; +} +void doIncrement() +{ + int life = 42; + life. +}}; + + unittest + { + import dsymbol.ufcs; + + writeln("Getting UFCS Symbols For life"); + ModuleCache cache; + // position of variable life + size_t cursorPos = 139; + auto pair = generateAutocompleteTreesProd(ufcsExampleCode, randomDFilename, cursorPos, cache); + assert(pair.ufcsSymbols.length > 0); + assert(pair.ufcsSymbols[0].name == "increment"); + + } +} diff --git a/dsymbol/src/dsymbol/ufcs.d b/dsymbol/src/dsymbol/ufcs.d index 68092905..3079f755 100644 --- a/dsymbol/src/dsymbol/ufcs.d +++ b/dsymbol/src/dsymbol/ufcs.d @@ -2,15 +2,31 @@ module dsymbol.ufcs; import dsymbol.symbol; import dsymbol.scope_; +import dsymbol.builtin.names; +import dsymbol.utils; +import dparse.lexer : tok, Token; import std.functional : unaryFun; import std.algorithm; import std.array; import std.range; -import dsymbol.builtin.names; import std.string; -import dparse.lexer : tok; import std.regex; import containers.hashset : HashSet; +import std.experimental.logger; + +enum UFCSCompletionContext +{ + DotCompletion, + ParenCompletion, + UnknownCompletion +} + +struct TokenCursorResult +{ + UFCSCompletionContext completionContext = UFCSCompletionContext.UnknownCompletion; + istring functionName; + istring symbolIdentifierName; +} // https://dlang.org/spec/type.html#implicit-conversions enum string[string] INTEGER_PROMOTIONS = [ @@ -26,43 +42,107 @@ enum string[string] INTEGER_PROMOTIONS = [ enum MAX_RECURSION_DEPTH = 50; +private DSymbol* deduceSymbolType(DSymbol* symbol) +{ + DSymbol* symbolType = symbol.type; + while (symbolType !is null && (symbolType.qualifier == SymbolQualifier.func + || symbolType.kind == CompletionKind.functionName + || symbolType.kind == CompletionKind.importSymbol + || symbolType.kind == CompletionKind.aliasName)) + { + if (symbolType.type is null || symbolType.type is symbolType) + { + break; + } + //look at next type to deduce + symbolType = symbolType.type; + } + return symbolType; + +} + // Check if beforeDotSymbol is null or void -bool isInvalidForUFCSCompletion(const(DSymbol)* beforeDotSymbol) +private bool isInvalidForUFCSCompletion(const(DSymbol)* beforeDotSymbol) { return beforeDotSymbol is null + || beforeDotSymbol.kind == CompletionKind.templateName || beforeDotSymbol.name is getBuiltinTypeName(tok!"void") || (beforeDotSymbol.type !is null && beforeDotSymbol.type.name is getBuiltinTypeName( tok!"void")); } -/** - * Get symbols suitable for UFCS. - * - * a symbol is suitable for UFCS if it satisfies the following: - * $(UL - * $(LI is global or imported) - * $(LI is callable with $(D beforeDotSymbol) as it's first argument) - * ) - * - * Params: - * completionScope = current scope - * beforeDotSymbol = the symbol before the dot (implicit first argument to UFCS function) - * cursorPosition = current position - * Returns: - * callable an array of symbols suitable for UFCS at $(D cursorPosition) - */ -DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSymbol, size_t cursorPosition) + +private TokenCursorResult getCursorToken(const(Token)[] tokens, size_t cursorPosition) { - if (beforeDotSymbol.isInvalidForUFCSCompletion) + auto sortedTokens = assumeSorted(tokens); + auto sortedBeforeTokens = sortedTokens.lowerBound(cursorPosition); + + TokenCursorResult tokenCursorResult; + + if (sortedBeforeTokens.empty) { + return tokenCursorResult; + } + + if (sortedBeforeTokens.length >= 2 + && sortedBeforeTokens[$ - 1].type is tok!"." + && sortedBeforeTokens[$ - 2].type is tok!"identifier") { - return null; + // Check if it's UFCS dot completion + tokenCursorResult.completionContext = UFCSCompletionContext.DotCompletion; + tokenCursorResult.symbolIdentifierName = istring(sortedBeforeTokens[$ - 2].text); + return tokenCursorResult; + } + else + { + // Check if it's UFCS paren completion + size_t index = goBackToOpenParen(sortedBeforeTokens); + + if (index == size_t.max) + { + return tokenCursorResult; + } + + auto slicedAtParen = sortedBeforeTokens[0 .. index]; + if (slicedAtParen.length >= 4 + && slicedAtParen[$ - 4].type is tok!"identifier" + && slicedAtParen[$ - 3].type is tok!"." + && slicedAtParen[$ - 2].type is tok!"identifier" + && slicedAtParen[$ - 1].type is tok!"(") + { + tokenCursorResult.completionContext = UFCSCompletionContext.ParenCompletion; + tokenCursorResult.symbolIdentifierName = istring(slicedAtParen[$ - 4].text); + tokenCursorResult.functionName = istring(slicedAtParen[$ - 2].text); + return tokenCursorResult; + } + } + // if none then it's unknown + return tokenCursorResult; +} + +private void getUFCSSymbols(T, Y)(ref T localAppender, ref Y globalAppender, Scope* completionScope, size_t cursorPosition) + +{ Scope* currentScope = completionScope.getScopeByCursor(cursorPosition); - assert(currentScope); - HashSet!size_t visited; + if (currentScope is null) + { + return; + } - // local appender - FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol), DSymbol*[]) localAppender; + DSymbol*[] cursorSymbols = currentScope.getSymbolsInCursorScope(cursorPosition); + if (cursorSymbols.empty) + { + return; + } + + auto filteredSymbols = cursorSymbols.filter!(s => s.kind == CompletionKind.functionName).array; + + foreach (DSymbol* sym; filteredSymbols) + { + globalAppender.put(sym); + } + + HashSet!size_t visited; while (currentScope !is null && currentScope.parent !is null) { @@ -74,18 +154,19 @@ DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSy if (sym.qualifier == SymbolQualifier.selectiveImport) localAppender.put(sym.type); else - sym.type.getParts(internString(null), localAppender, visited); + sym.type.getParts(istring(null), localAppender, visited); } currentScope = currentScope.parent; } - // global appender - FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol, true), DSymbol*[]) globalAppender; - - // global symbols and global imports + if (currentScope is null) + { + return; + } assert(currentScope !is null); assert(currentScope.parent is null); + foreach (sym; currentScope.symbols) { if (sym.kind != CompletionKind.importSymbol) @@ -100,10 +181,86 @@ DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSy } } } - return localAppender.opSlice ~ globalAppender.opSlice; } -bool willImplicitBeUpcasted(string from, string to) +DSymbol*[] getUFCSSymbolsForCursor(Scope* completionScope, ref const(Token)[] tokens, size_t cursorPosition) +{ + DSymbol* cursorSymbol; + DSymbol* cursorSymbolType; + + TokenCursorResult tokenCursorResult = getCursorToken(tokens, cursorPosition); + + if (tokenCursorResult.completionContext is UFCSCompletionContext.UnknownCompletion) + { + trace("Is not a valid UFCS completion"); + return []; + } + + cursorSymbol = completionScope.getFirstSymbolByNameAndCursor( + tokenCursorResult.symbolIdentifierName, cursorPosition); + + if (cursorSymbol is null) + { + warning("Coudn't find symbol ", tokenCursorResult.symbolIdentifierName); + return []; + } + + if (cursorSymbol.isInvalidForUFCSCompletion) + { + trace("CursorSymbol is invalid"); + return []; + } + + cursorSymbolType = deduceSymbolType(cursorSymbol); + + if (cursorSymbolType is null) + { + return []; + } + + if (cursorSymbolType.isInvalidForUFCSCompletion) + { + trace("CursorSymbolType isn't valid for UFCS completion"); + return []; + } + + if (tokenCursorResult.completionContext == UFCSCompletionContext.ParenCompletion) + { + return getUFCSSymbolsForParenCompletion(cursorSymbolType, completionScope, tokenCursorResult.functionName, cursorPosition); + } + else + { + return getUFCSSymbolsForDotCompletion(cursorSymbolType, completionScope, cursorPosition); + } + +} + +private DSymbol*[] getUFCSSymbolsForDotCompletion(DSymbol* symbolType, Scope* completionScope, size_t cursorPosition) +{ + // local appender + FilteredAppender!(a => a.isCallableWithArg(symbolType), DSymbol*[]) localAppender; + // global appender + FilteredAppender!(a => a.isCallableWithArg(symbolType, true), DSymbol*[]) globalAppender; + + getUFCSSymbols(localAppender, globalAppender, completionScope, cursorPosition); + + return localAppender.data ~ globalAppender.data; +} + +DSymbol*[] getUFCSSymbolsForParenCompletion(DSymbol* symbolType, Scope* completionScope, istring searchWord, size_t cursorPosition) +{ + // local appender + FilteredAppender!(a => a.isCallableWithArg(symbolType) && a.name.among(searchWord), DSymbol*[]) localAppender; + // global appender + FilteredAppender!(a => a.isCallableWithArg(symbolType, true) && a.name.among(searchWord), DSymbol*[]) globalAppender; + + getUFCSSymbols(localAppender, globalAppender, completionScope, cursorPosition); + + return localAppender.data ~ globalAppender.data; + +} + +private bool willImplicitBeUpcasted(string from, string to) { string* found = from in INTEGER_PROMOTIONS; if (!found) @@ -114,7 +271,7 @@ bool willImplicitBeUpcasted(string from, string to) return INTEGER_PROMOTIONS[from] == to; } -bool matchAliasThis(const(DSymbol)* beforeDotType, const(DSymbol)* incomingSymbol, int recursionDepth) +private bool matchAliasThis(const(DSymbol)* beforeDotType, DSymbol* incomingSymbol, int recursionDepth) { // For now we are only resolving the first alias this symbol // when multiple alias this are supported, we can rethink another solution @@ -138,7 +295,7 @@ bool matchAliasThis(const(DSymbol)* beforeDotType, const(DSymbol)* incomingSymbo * `true` if `incomingSymbols`' first parameter matches `beforeDotType` * `false` otherwise */ -bool isCallableWithArg(const(DSymbol)* incomingSymbol, const(DSymbol)* beforeDotType, bool isGlobalScope = false, int recursionDepth = 0) +bool isCallableWithArg(DSymbol* incomingSymbol, const(DSymbol)* beforeDotType, bool isGlobalScope = false, int recursionDepth = 0) { if (!incomingSymbol || !beforeDotType || (isGlobalScope && incomingSymbol.protection == tok!"private") || recursionDepth > MAX_RECURSION_DEPTH) @@ -149,13 +306,16 @@ bool isCallableWithArg(const(DSymbol)* incomingSymbol, const(DSymbol)* beforeDot if (incomingSymbol.kind == CompletionKind.functionName && !incomingSymbol .functionParameters.empty) { - return beforeDotType is incomingSymbol.functionParameters.front.type + if (beforeDotType is incomingSymbol.functionParameters.front.type || willImplicitBeUpcasted(beforeDotType.name, incomingSymbol - .functionParameters.front.type.name) - || matchAliasThis(beforeDotType, incomingSymbol, recursionDepth); + .functionParameters.front.type.name) + || matchAliasThis(beforeDotType, incomingSymbol, recursionDepth)) + { + // incomingSymbol.kind = CompletionKind.ufcsName; + return true; + } } - return false; } @@ -193,29 +353,6 @@ struct FilteredAppender(alias predicate, T: assert(app.data == [1, 3, 5, 7, 9]); } -void getUFCSParenCompletion(ref DSymbol*[] symbols, Scope* completionScope, istring firstToken, istring nextToken, size_t cursorPosition) -{ - DSymbol* firstSymbol = completionScope.getFirstSymbolByNameAndCursor( - firstToken, cursorPosition); - - if (firstSymbol is null) - return; - - DSymbol*[] possibleUFCSSymbol = completionScope.getSymbolsByNameAndCursor( - nextToken, cursorPosition); - foreach (nextSymbol; possibleUFCSSymbol) - { - if (nextSymbol && nextSymbol.functionParameters) - { - if (nextSymbol.isCallableWithArg(firstSymbol.type)) - { - nextSymbol.kind = CompletionKind.ufcsName; - symbols ~= nextSymbol; - } - } - } -} - unittest { assert(!willImplicitBeUpcasted("A", "B")); diff --git a/dsymbol/src/dsymbol/utils.d b/dsymbol/src/dsymbol/utils.d index 518166cc..b2c88d8d 100644 --- a/dsymbol/src/dsymbol/utils.d +++ b/dsymbol/src/dsymbol/utils.d @@ -1,5 +1,5 @@ module dsymbol.utils; -import dparse.lexer : tok, IdType; +import dparse.lexer : tok, IdType, Token; enum TYPE_IDENT_CASES = q{ case tok!"int": diff --git a/src/dcd/server/autocomplete/complete.d b/src/dcd/server/autocomplete/complete.d index aa86763b..c6727e8a 100644 --- a/src/dcd/server/autocomplete/complete.d +++ b/src/dcd/server/autocomplete/complete.d @@ -42,6 +42,7 @@ import dsymbol.scope_; import dsymbol.string_interning; import dsymbol.symbol; import dsymbol.ufcs; +import dsymbol.utils; import dcd.common.constants; import dcd.common.messages; @@ -220,6 +221,7 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray, scope(exit) pair.destroy(); response.setCompletions(pair.scope_, getExpression(beforeTokens), cursorPosition, CompletionType.identifiers, false, partial); + response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, CompletionKind.ufcsName)).array; break; // these tokens before a "." mean "Module Scope Operator" case tok!":": @@ -306,6 +308,8 @@ AutocompleteResponse parenCompletion(T)(T beforeTokens, auto expression = getExpression(beforeTokens[0 .. $ - 1]); response.setCompletions(pair.scope_, expression, cursorPosition, CompletionType.calltips, beforeTokens[$ - 1] == tok!"["); + response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, char.init)).array; + response.completionType = CompletionType.calltips; break; default: break; @@ -559,9 +563,8 @@ void setCompletions(T)(ref AutocompleteResponse response, cursorPosition, completionType); if (tokens.length > 2 && tokens[1] == tok!".") - { - symbols.getUFCSParenCompletion(completionScope, stringToken(tokens[0]), stringToken( - tokens[2]), cursorPosition); + { + } if (symbols.length == 0) @@ -581,7 +584,6 @@ void setCompletions(T)(ref AutocompleteResponse response, } addSymToResponse(symbols[0], response, partial, completionScope); response.completionType = CompletionType.identifiers; - lookupUFCS(completionScope, symbols[0], cursorPosition, response); } else if (completionType == CompletionType.calltips) { diff --git a/src/dcd/server/autocomplete/util.d b/src/dcd/server/autocomplete/util.d index c0405e03..e8e435d8 100644 --- a/src/dcd/server/autocomplete/util.d +++ b/src/dcd/server/autocomplete/util.d @@ -38,6 +38,7 @@ import dsymbol.scope_; import dsymbol.string_interning; import dsymbol.symbol; import dsymbol.ufcs; +import dsymbol.utils; enum ImportKind : ubyte { @@ -420,43 +421,6 @@ DSymbol*[] getSymbolsByTokenChain(T)(Scope* completionScope, return symbols; } -enum TYPE_IDENT_CASES = q{ - case tok!"int": - case tok!"uint": - case tok!"long": - case tok!"ulong": - case tok!"char": - case tok!"wchar": - case tok!"dchar": - case tok!"bool": - case tok!"byte": - case tok!"ubyte": - case tok!"short": - case tok!"ushort": - case tok!"cent": - case tok!"ucent": - case tok!"float": - case tok!"ifloat": - case tok!"cfloat": - case tok!"idouble": - case tok!"cdouble": - case tok!"double": - case tok!"real": - case tok!"ireal": - case tok!"creal": - case tok!"this": - case tok!"super": - case tok!"identifier": -}; - -enum STRING_LITERAL_CASES = q{ - case tok!"stringLiteral": - case tok!"wstringLiteral": - case tok!"dstringLiteral": -}; - -enum TYPE_IDENT_AND_LITERAL_CASES = TYPE_IDENT_CASES ~ STRING_LITERAL_CASES; - /** * */ @@ -647,122 +611,10 @@ bool isUdaExpression(T)(ref T tokens) return result; } -/** - * Traverses a token slice in reverse to find the opening parentheses or square bracket - * that begins the block the last token is in. - */ -size_t goBackToOpenParen(T)(T beforeTokens) -in -{ - assert (beforeTokens.length > 0); -} -do -{ - size_t i = beforeTokens.length - 1; - while (true) switch (beforeTokens[i].type) - { - case tok!",": - case tok!".": - case tok!"*": - case tok!"&": - case tok!"doubleLiteral": - case tok!"floatLiteral": - case tok!"idoubleLiteral": - case tok!"ifloatLiteral": - case tok!"intLiteral": - case tok!"longLiteral": - case tok!"realLiteral": - case tok!"irealLiteral": - case tok!"uintLiteral": - case tok!"ulongLiteral": - case tok!"characterLiteral": - mixin(TYPE_IDENT_AND_LITERAL_CASES); - if (i == 0) - return size_t.max; - else - i--; - break; - case tok!"(": - case tok!"[": - return i + 1; - case tok!")": - i = beforeTokens.skipParenReverseBefore(i, tok!")", tok!"("); - break; - case tok!"}": - i = beforeTokens.skipParenReverseBefore(i, tok!"}", tok!"{"); - break; - case tok!"]": - i = beforeTokens.skipParenReverseBefore(i, tok!"]", tok!"["); - break; - default: - return size_t.max; - } -} - -/** - * Skips blocks of parentheses until the starting block has been closed - */ -void skipParen(T)(T tokenSlice, ref size_t i, IdType open, IdType close) -{ - if (i >= tokenSlice.length || tokenSlice.length <= 0) - return; - int depth = 1; - while (depth != 0 && i + 1 != tokenSlice.length) - { - i++; - if (tokenSlice[i].type == open) - depth++; - else if (tokenSlice[i].type == close) - depth--; - } -} - -/** - * Skips blocks of parentheses in reverse until the starting block has been opened - */ -size_t skipParenReverse(T)(T beforeTokens, size_t i, IdType open, IdType close) -{ - if (i == 0) - return 0; - int depth = 1; - while (depth != 0 && i != 0) - { - i--; - if (beforeTokens[i].type == open) - depth++; - else if (beforeTokens[i].type == close) - depth--; - } - return i; -} - -size_t skipParenReverseBefore(T)(T beforeTokens, size_t i, IdType open, IdType close) -{ - i = skipParenReverse(beforeTokens, i, open, close); - if (i != 0) - i--; - return i; -} - -/// -unittest -{ - Token[] t = [ - Token(tok!"identifier"), Token(tok!"identifier"), Token(tok!"("), - Token(tok!"identifier"), Token(tok!"("), Token(tok!")"), Token(tok!",") - ]; - size_t i = t.length - 1; - i = skipParenReverse(t, i, tok!")", tok!"("); - assert(i == 2); - i = t.length - 1; - i = skipParenReverseBefore(t, i, tok!")", tok!"("); - assert(i == 1); -} - AutocompleteResponse.Completion makeSymbolCompletionInfo(const DSymbol* symbol, char kind) { string definition; - if ((kind == CompletionKind.variableName || kind == CompletionKind.memberVariableName) && symbol.type) + if ((kind == CompletionKind.variableName || kind == CompletionKind.ufcsName || kind == CompletionKind.memberVariableName) && symbol.type) definition = symbol.type.name ~ ' ' ~ symbol.name; else if (kind == CompletionKind.enumMember) definition = symbol.name; // TODO: add enum value to definition string @@ -773,21 +625,6 @@ AutocompleteResponse.Completion makeSymbolCompletionInfo(const DSymbol* symbol, symbol.symbolFile, symbol.location, symbol.doc); } -void lookupUFCS(Scope* completionScope, DSymbol* beforeDotSymbol, size_t cursorPosition, ref AutocompleteResponse response) -{ - // UFCS completion - DSymbol*[] ufcsSymbols = getSymbolsForUFCS(completionScope, beforeDotSymbol, cursorPosition); - response.completions ~= map!(s => createCompletionForUFCS(s))(ufcsSymbols).array; -} - -AutocompleteResponse.Completion createCompletionForUFCS(const DSymbol* symbol) -{ - return AutocompleteResponse.Completion(symbol.name, CompletionKind.ufcsName, symbol.callTip, symbol - .symbolFile, symbol - .location, symbol - .doc); -} - bool doUFCSSearch(string beforeToken, string lastToken) { // we do the search if they are different from eachother From 11db3911fae337af56d250fb1fac90b4ae5746bb Mon Sep 17 00:00:00 2001 From: davu Date: Mon, 13 Mar 2023 19:17:33 +0100 Subject: [PATCH 4/9] Adding non contrainted templates into ufcs completion --- dsymbol/src/dsymbol/tests.d | 27 ++++++++++++++++++++++++++- dsymbol/src/dsymbol/ufcs.d | 3 +-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/dsymbol/src/dsymbol/tests.d b/dsymbol/src/dsymbol/tests.d index 0b23aace..ffb6e353 100644 --- a/dsymbol/src/dsymbol/tests.d +++ b/dsymbol/src/dsymbol/tests.d @@ -658,7 +658,6 @@ ScopeSymbolPair generateAutocompleteTreesProd(string source, string filename, si version (linux) { - import std.string; enum string ufcsExampleCode = q{class Incrementer { @@ -690,4 +689,30 @@ void doIncrement() assert(pair.ufcsSymbols[0].name == "increment"); } + +enum string ufcsTemplateExampleCode = +q{int increment(T)(T x) +{ + return x++; +} +void doIncrement() +{ + int life = 42; + life. +}}; + +unittest + { + import dsymbol.ufcs; + + writeln("Getting Templated UFCS Symbols For life"); + ModuleCache cache; + // position of variable life + size_t cursorPos = 82; + auto pair = generateAutocompleteTreesProd(ufcsTemplateExampleCode, randomDFilename, cursorPos, cache); + assert(pair.ufcsSymbols.length > 0); + assert(pair.ufcsSymbols[0].name == "increment"); + + } + } diff --git a/dsymbol/src/dsymbol/ufcs.d b/dsymbol/src/dsymbol/ufcs.d index 3079f755..51ecce6f 100644 --- a/dsymbol/src/dsymbol/ufcs.d +++ b/dsymbol/src/dsymbol/ufcs.d @@ -65,7 +65,6 @@ private DSymbol* deduceSymbolType(DSymbol* symbol) private bool isInvalidForUFCSCompletion(const(DSymbol)* beforeDotSymbol) { return beforeDotSymbol is null - || beforeDotSymbol.kind == CompletionKind.templateName || beforeDotSymbol.name is getBuiltinTypeName(tok!"void") || (beforeDotSymbol.type !is null && beforeDotSymbol.type.name is getBuiltinTypeName( tok!"void")); @@ -307,11 +306,11 @@ bool isCallableWithArg(DSymbol* incomingSymbol, const(DSymbol)* beforeDotType, b .functionParameters.empty) { if (beforeDotType is incomingSymbol.functionParameters.front.type + || incomingSymbol.functionParameters.front.type.kind is CompletionKind.typeTmpParam // non constrained template || willImplicitBeUpcasted(beforeDotType.name, incomingSymbol .functionParameters.front.type.name) || matchAliasThis(beforeDotType, incomingSymbol, recursionDepth)) { - // incomingSymbol.kind = CompletionKind.ufcsName; return true; } From 7627b8affcd4fa0a81631cd3e1a798b8706a9596 Mon Sep 17 00:00:00 2001 From: davu Date: Tue, 14 Mar 2023 15:52:52 +0100 Subject: [PATCH 5/9] refixing ufcs calltips --- dsymbol/src/dsymbol/ufcs.d | 1 + src/dcd/server/autocomplete/complete.d | 4 ++-- src/dcd/server/autocomplete/util.d | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dsymbol/src/dsymbol/ufcs.d b/dsymbol/src/dsymbol/ufcs.d index 51ecce6f..3425acc1 100644 --- a/dsymbol/src/dsymbol/ufcs.d +++ b/dsymbol/src/dsymbol/ufcs.d @@ -311,6 +311,7 @@ bool isCallableWithArg(DSymbol* incomingSymbol, const(DSymbol)* beforeDotType, b .functionParameters.front.type.name) || matchAliasThis(beforeDotType, incomingSymbol, recursionDepth)) { + incomingSymbol.kind = CompletionKind.ufcsName; return true; } diff --git a/src/dcd/server/autocomplete/complete.d b/src/dcd/server/autocomplete/complete.d index c6727e8a..4baee265 100644 --- a/src/dcd/server/autocomplete/complete.d +++ b/src/dcd/server/autocomplete/complete.d @@ -221,7 +221,7 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray, scope(exit) pair.destroy(); response.setCompletions(pair.scope_, getExpression(beforeTokens), cursorPosition, CompletionType.identifiers, false, partial); - response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, CompletionKind.ufcsName)).array; + response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, s.kind)).array; break; // these tokens before a "." mean "Module Scope Operator" case tok!":": @@ -308,7 +308,7 @@ AutocompleteResponse parenCompletion(T)(T beforeTokens, auto expression = getExpression(beforeTokens[0 .. $ - 1]); response.setCompletions(pair.scope_, expression, cursorPosition, CompletionType.calltips, beforeTokens[$ - 1] == tok!"["); - response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, char.init)).array; + response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, s.kind)).array; response.completionType = CompletionType.calltips; break; default: diff --git a/src/dcd/server/autocomplete/util.d b/src/dcd/server/autocomplete/util.d index e8e435d8..8bb84d3a 100644 --- a/src/dcd/server/autocomplete/util.d +++ b/src/dcd/server/autocomplete/util.d @@ -614,7 +614,7 @@ bool isUdaExpression(T)(ref T tokens) AutocompleteResponse.Completion makeSymbolCompletionInfo(const DSymbol* symbol, char kind) { string definition; - if ((kind == CompletionKind.variableName || kind == CompletionKind.ufcsName || kind == CompletionKind.memberVariableName) && symbol.type) + if ((kind == CompletionKind.variableName || kind == CompletionKind.memberVariableName) && symbol.type) definition = symbol.type.name ~ ' ' ~ symbol.name; else if (kind == CompletionKind.enumMember) definition = symbol.name; // TODO: add enum value to definition string From d59b8efbf0cacdd74ca227d829a0447f5533cb34 Mon Sep 17 00:00:00 2001 From: davu Date: Thu, 16 Mar 2023 01:58:49 +0100 Subject: [PATCH 6/9] PR changes round 1 --- dsymbol/src/dsymbol/conversion/package.d | 3 + dsymbol/src/dsymbol/ufcs.d | 75 ++++++++++--------- src/dcd/server/autocomplete/complete.d | 14 ++-- tests/tc_erroneous_body_content/expected1.txt | 1 - tests/tc_incomplete_switch/expected1.txt | 1 - 5 files changed, 48 insertions(+), 46 deletions(-) diff --git a/dsymbol/src/dsymbol/conversion/package.d b/dsymbol/src/dsymbol/conversion/package.d index e2aea8d2..2bc274f3 100644 --- a/dsymbol/src/dsymbol/conversion/package.d +++ b/dsymbol/src/dsymbol/conversion/package.d @@ -66,6 +66,9 @@ struct ScopeSymbolPair { typeid(DSymbol).destroy(symbol); typeid(Scope).destroy(scope_); + foreach(ufcsSym; ufcsSymbols){ + typeid(DSymbol).destroy(ufcsSym); + } } DSymbol* symbol; diff --git a/dsymbol/src/dsymbol/ufcs.d b/dsymbol/src/dsymbol/ufcs.d index 3425acc1..595ac1da 100644 --- a/dsymbol/src/dsymbol/ufcs.d +++ b/dsymbol/src/dsymbol/ufcs.d @@ -14,16 +14,16 @@ import std.regex; import containers.hashset : HashSet; import std.experimental.logger; -enum UFCSCompletionContext +enum CompletionContext { + UnknownCompletion, DotCompletion, ParenCompletion, - UnknownCompletion } struct TokenCursorResult { - UFCSCompletionContext completionContext = UFCSCompletionContext.UnknownCompletion; + CompletionContext completionContext; istring functionName; istring symbolIdentifierName; } @@ -86,7 +86,7 @@ private TokenCursorResult getCursorToken(const(Token)[] tokens, size_t cursorPos && sortedBeforeTokens[$ - 2].type is tok!"identifier") { // Check if it's UFCS dot completion - tokenCursorResult.completionContext = UFCSCompletionContext.DotCompletion; + tokenCursorResult.completionContext = CompletionContext.DotCompletion; tokenCursorResult.symbolIdentifierName = istring(sortedBeforeTokens[$ - 2].text); return tokenCursorResult; } @@ -107,7 +107,7 @@ private TokenCursorResult getCursorToken(const(Token)[] tokens, size_t cursorPos && slicedAtParen[$ - 2].type is tok!"identifier" && slicedAtParen[$ - 1].type is tok!"(") { - tokenCursorResult.completionContext = UFCSCompletionContext.ParenCompletion; + tokenCursorResult.completionContext = CompletionContext.ParenCompletion; tokenCursorResult.symbolIdentifierName = istring(slicedAtParen[$ - 4].text); tokenCursorResult.functionName = istring(slicedAtParen[$ - 2].text); return tokenCursorResult; @@ -119,7 +119,6 @@ private TokenCursorResult getCursorToken(const(Token)[] tokens, size_t cursorPos } private void getUFCSSymbols(T, Y)(ref T localAppender, ref Y globalAppender, Scope* completionScope, size_t cursorPosition) - { Scope* currentScope = completionScope.getScopeByCursor(cursorPosition); @@ -134,13 +133,6 @@ private void getUFCSSymbols(T, Y)(ref T localAppender, ref Y globalAppender, Sco return; } - auto filteredSymbols = cursorSymbols.filter!(s => s.kind == CompletionKind.functionName).array; - - foreach (DSymbol* sym; filteredSymbols) - { - globalAppender.put(sym); - } - HashSet!size_t visited; while (currentScope !is null && currentScope.parent !is null) @@ -189,7 +181,7 @@ DSymbol*[] getUFCSSymbolsForCursor(Scope* completionScope, ref const(Token)[] to TokenCursorResult tokenCursorResult = getCursorToken(tokens, cursorPosition); - if (tokenCursorResult.completionContext is UFCSCompletionContext.UnknownCompletion) + if (tokenCursorResult.completionContext is CompletionContext.UnknownCompletion) { trace("Is not a valid UFCS completion"); return []; @@ -206,7 +198,7 @@ DSymbol*[] getUFCSSymbolsForCursor(Scope* completionScope, ref const(Token)[] to if (cursorSymbol.isInvalidForUFCSCompletion) { - trace("CursorSymbol is invalid"); + trace("CursorSymbol is invalid for UFCS"); return []; } @@ -223,7 +215,7 @@ DSymbol*[] getUFCSSymbolsForCursor(Scope* completionScope, ref const(Token)[] to return []; } - if (tokenCursorResult.completionContext == UFCSCompletionContext.ParenCompletion) + if (tokenCursorResult.completionContext == CompletionContext.ParenCompletion) { return getUFCSSymbolsForParenCompletion(cursorSymbolType, completionScope, tokenCursorResult.functionName, cursorPosition); } @@ -246,7 +238,7 @@ private DSymbol*[] getUFCSSymbolsForDotCompletion(DSymbol* symbolType, Scope* co return localAppender.data ~ globalAppender.data; } -DSymbol*[] getUFCSSymbolsForParenCompletion(DSymbol* symbolType, Scope* completionScope, istring searchWord, size_t cursorPosition) +private DSymbol*[] getUFCSSymbolsForParenCompletion(DSymbol* symbolType, Scope* completionScope, istring searchWord, size_t cursorPosition) { // local appender FilteredAppender!(a => a.isCallableWithArg(symbolType) && a.name.among(searchWord), DSymbol*[]) localAppender; @@ -259,10 +251,21 @@ DSymbol*[] getUFCSSymbolsForParenCompletion(DSymbol* symbolType, Scope* completi } -private bool willImplicitBeUpcasted(string from, string to) +private bool willImplicitBeUpcasted(const(DSymbol)* from, const(DSymbol)* to) { + if (from is null || to is null || to.functionParameters.empty || to.functionParameters.front.type is null) { + return false; + } + + string fromTypeName = from.name.data; + string toTypeName = to.functionParameters.front.type.name.data; + + return typeWillBeUpcastedTo(fromTypeName, toTypeName); +} + +private bool typeWillBeUpcastedTo(string from, string to) { string* found = from in INTEGER_PROMOTIONS; - if (!found) + if (found is null) { return false; } @@ -270,7 +273,7 @@ private bool willImplicitBeUpcasted(string from, string to) return INTEGER_PROMOTIONS[from] == to; } -private bool matchAliasThis(const(DSymbol)* beforeDotType, DSymbol* incomingSymbol, int recursionDepth) +private bool matchAliasThis(const(DSymbol)* beforeDotType, const(DSymbol)* incomingSymbol, int recursionDepth) { // For now we are only resolving the first alias this symbol // when multiple alias this are supported, we can rethink another solution @@ -285,6 +288,10 @@ private bool matchAliasThis(const(DSymbol)* beforeDotType, DSymbol* incomingSymb return isCallableWithArg(incomingSymbol, beforeDotType.aliasThisSymbols.front.type, false, recursionDepth); } +bool isNonConstrainedTemplate(const(DSymbol)* incomingSymbol){ + return incomingSymbol.functionParameters.front.type !is null && incomingSymbol.functionParameters.front.type.kind is CompletionKind.typeTmpParam; +} + /** * Params: * incomingSymbol = the function symbol to check if it is valid for UFCS with `beforeDotType`. @@ -294,26 +301,22 @@ private bool matchAliasThis(const(DSymbol)* beforeDotType, DSymbol* incomingSymb * `true` if `incomingSymbols`' first parameter matches `beforeDotType` * `false` otherwise */ -bool isCallableWithArg(DSymbol* incomingSymbol, const(DSymbol)* beforeDotType, bool isGlobalScope = false, int recursionDepth = 0) +bool isCallableWithArg(const(DSymbol)* incomingSymbol, const(DSymbol)* beforeDotType, bool isGlobalScope = false, int recursionDepth = 0) { - if (!incomingSymbol || !beforeDotType - || (isGlobalScope && incomingSymbol.protection == tok!"private") || recursionDepth > MAX_RECURSION_DEPTH) + if (incomingSymbol is null + || beforeDotType is null + || isGlobalScope && incomingSymbol.protection is tok!"private" // don't show private functions if we are in global scope + || recursionDepth > MAX_RECURSION_DEPTH) { return false; } - if (incomingSymbol.kind == CompletionKind.functionName && !incomingSymbol - .functionParameters.empty) + if (incomingSymbol.kind is CompletionKind.functionName && !incomingSymbol.functionParameters.empty && incomingSymbol.functionParameters.front.type) { - if (beforeDotType is incomingSymbol.functionParameters.front.type - || incomingSymbol.functionParameters.front.type.kind is CompletionKind.typeTmpParam // non constrained template - || willImplicitBeUpcasted(beforeDotType.name, incomingSymbol - .functionParameters.front.type.name) - || matchAliasThis(beforeDotType, incomingSymbol, recursionDepth)) - { - incomingSymbol.kind = CompletionKind.ufcsName; - return true; - } + return beforeDotType is incomingSymbol.functionParameters.front.type + || isNonConstrainedTemplate(incomingSymbol) + || willImplicitBeUpcasted(beforeDotType, incomingSymbol) + || matchAliasThis(beforeDotType, incomingSymbol, recursionDepth); } return false; @@ -355,6 +358,6 @@ struct FilteredAppender(alias predicate, T: unittest { - assert(!willImplicitBeUpcasted("A", "B")); - assert(willImplicitBeUpcasted("bool", "int")); + assert(!typeWillBeUpcastedTo("A", "B")); + assert(typeWillBeUpcastedTo("bool", "int")); } diff --git a/src/dcd/server/autocomplete/complete.d b/src/dcd/server/autocomplete/complete.d index 4baee265..17b8fb3f 100644 --- a/src/dcd/server/autocomplete/complete.d +++ b/src/dcd/server/autocomplete/complete.d @@ -221,7 +221,7 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray, scope(exit) pair.destroy(); response.setCompletions(pair.scope_, getExpression(beforeTokens), cursorPosition, CompletionType.identifiers, false, partial); - response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, s.kind)).array; + response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, CompletionKind.ufcsName)).array; break; // these tokens before a "." mean "Module Scope Operator" case tok!":": @@ -308,8 +308,11 @@ AutocompleteResponse parenCompletion(T)(T beforeTokens, auto expression = getExpression(beforeTokens[0 .. $ - 1]); response.setCompletions(pair.scope_, expression, cursorPosition, CompletionType.calltips, beforeTokens[$ - 1] == tok!"["); - response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, s.kind)).array; - response.completionType = CompletionType.calltips; + if (!pair.ufcsSymbols.empty) { + response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, CompletionKind.ufcsName)).array; + // Setting CompletionType in case of none symbols are found via setCompletions, but we have UFCS symbols. + response.completionType = CompletionType.calltips; + } break; default: break; @@ -562,11 +565,6 @@ void setCompletions(T)(ref AutocompleteResponse response, DSymbol*[] symbols = getSymbolsByTokenChain(completionScope, tokens, cursorPosition, completionType); - if (tokens.length > 2 && tokens[1] == tok!".") - { - - } - if (symbols.length == 0) return; diff --git a/tests/tc_erroneous_body_content/expected1.txt b/tests/tc_erroneous_body_content/expected1.txt index 6dd7ccf4..12a38bbd 100644 --- a/tests/tc_erroneous_body_content/expected1.txt +++ b/tests/tc_erroneous_body_content/expected1.txt @@ -1,7 +1,6 @@ identifiers alignof k b v -bar F init k mangleof k sizeof k diff --git a/tests/tc_incomplete_switch/expected1.txt b/tests/tc_incomplete_switch/expected1.txt index de86c558..5d30ee55 100644 --- a/tests/tc_incomplete_switch/expected1.txt +++ b/tests/tc_incomplete_switch/expected1.txt @@ -2,7 +2,6 @@ identifiers __monitor v __vptr v alignof k -bar F classinfo v e v init k From 34369422cdb593232a975e74990c7ad7b00e72e2 Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Thu, 16 Mar 2023 15:41:20 +0100 Subject: [PATCH 7/9] improve code style in utility function --- dsymbol/src/dsymbol/ufcs.d | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/dsymbol/src/dsymbol/ufcs.d b/dsymbol/src/dsymbol/ufcs.d index 595ac1da..003eac32 100644 --- a/dsymbol/src/dsymbol/ufcs.d +++ b/dsymbol/src/dsymbol/ufcs.d @@ -264,13 +264,10 @@ private bool willImplicitBeUpcasted(const(DSymbol)* from, const(DSymbol)* to) } private bool typeWillBeUpcastedTo(string from, string to) { - string* found = from in INTEGER_PROMOTIONS; - if (found is null) - { - return false; - } + if (auto promotionType = from in INTEGER_PROMOTIONS) + return *promotionType == to; - return INTEGER_PROMOTIONS[from] == to; + return false; } private bool matchAliasThis(const(DSymbol)* beforeDotType, const(DSymbol)* incomingSymbol, int recursionDepth) From 115b1e7f06b0a3f7caa40c06bd94fe88d2ef05b1 Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Thu, 16 Mar 2023 15:43:42 +0100 Subject: [PATCH 8/9] fix destroying of unowned symbols --- dsymbol/src/dsymbol/conversion/package.d | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dsymbol/src/dsymbol/conversion/package.d b/dsymbol/src/dsymbol/conversion/package.d index 2bc274f3..d2774af4 100644 --- a/dsymbol/src/dsymbol/conversion/package.d +++ b/dsymbol/src/dsymbol/conversion/package.d @@ -66,9 +66,8 @@ struct ScopeSymbolPair { typeid(DSymbol).destroy(symbol); typeid(Scope).destroy(scope_); - foreach(ufcsSym; ufcsSymbols){ - typeid(DSymbol).destroy(ufcsSym); - } + // don't destroy ufcsSymbols contents since we don't own the values + // array itself is GC-allocated, so we just let it live } DSymbol* symbol; From 1a658f5175dbffbbcaccb9e28e852f017bb73cb0 Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Thu, 16 Mar 2023 15:51:51 +0100 Subject: [PATCH 9/9] remove PR introduced unused variable --- dsymbol/src/dsymbol/ufcs.d | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dsymbol/src/dsymbol/ufcs.d b/dsymbol/src/dsymbol/ufcs.d index 003eac32..12a49c1d 100644 --- a/dsymbol/src/dsymbol/ufcs.d +++ b/dsymbol/src/dsymbol/ufcs.d @@ -127,12 +127,6 @@ private void getUFCSSymbols(T, Y)(ref T localAppender, ref Y globalAppender, Sco return; } - DSymbol*[] cursorSymbols = currentScope.getSymbolsInCursorScope(cursorPosition); - if (cursorSymbols.empty) - { - return; - } - HashSet!size_t visited; while (currentScope !is null && currentScope.parent !is null)