From ef27f455d0bfaf3fb773fc74179524525263c882 Mon Sep 17 00:00:00 2001 From: davu Date: Fri, 24 Mar 2023 22:41:35 +0100 Subject: [PATCH] added ufcs completion for string and string literal --- dsymbol/src/dsymbol/ufcs.d | 66 +++++++++++-------- src/dcd/server/autocomplete/complete.d | 17 ++++- .../expected_string_literal_test.txt | 13 ++++ .../expected_string_test.txt | 4 ++ .../file.d | 6 ++ .../run.sh | 15 +++++ 6 files changed, 90 insertions(+), 31 deletions(-) create mode 100644 tests/tc_ufcs_string_and_string_literal_completion/expected_string_literal_test.txt create mode 100644 tests/tc_ufcs_string_and_string_literal_completion/expected_string_test.txt create mode 100644 tests/tc_ufcs_string_and_string_literal_completion/file.d create mode 100755 tests/tc_ufcs_string_and_string_literal_completion/run.sh diff --git a/dsymbol/src/dsymbol/ufcs.d b/dsymbol/src/dsymbol/ufcs.d index bdb8bad..aaa5856 100644 --- a/dsymbol/src/dsymbol/ufcs.d +++ b/dsymbol/src/dsymbol/ufcs.d @@ -21,14 +21,20 @@ enum CompletionContext ParenCompletion, } + struct TokenCursorResult { CompletionContext completionContext; istring functionName; - istring symbolIdentifierName; + Token significantToken; string partialIdentifier; } +struct DeducedSymbolTypeResult{ + const(DSymbol)* deducedSymbolType; + bool success = false; +} + // https://dlang.org/spec/type.html#implicit-conversions enum string[string] INTEGER_PROMOTIONS = [ "bool": "int", @@ -43,15 +49,31 @@ enum string[string] INTEGER_PROMOTIONS = [ enum MAX_NUMBER_OF_MATCHING_RUNS = 50; -private const(DSymbol)* deduceSymbolType(const(DSymbol)* symbol) +private DeducedSymbolTypeResult deduceSymbolTypeByToken(Scope* completionScope, scope ref const(Token) significantToken, size_t cursorPosition) { + DeducedSymbolTypeResult result; + //Literal type deduction + if (significantToken.type is tok!"stringLiteral"){ + result.deducedSymbolType = completionScope.getFirstSymbolByNameAndCursor(istring("string"), cursorPosition); + result.success = true; + return result; + } + + const(DSymbol)* symbol = completionScope.getFirstSymbolByNameAndCursor(istring(significantToken.text), cursorPosition); + + if (symbol is null) { + return result; + } + const(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) + if (symbolType.type is null + || symbolType.type is symbolType + || symbolType.name.data == "string") // special case for string { break; } @@ -59,7 +81,10 @@ private const(DSymbol)* deduceSymbolType(const(DSymbol)* symbol) symbolType = symbolType.type; } - return symbolType; + + result.deducedSymbolType = symbolType; + result.success = true; + return result; } @@ -90,13 +115,13 @@ private TokenCursorResult getCursorToken(const(Token)[] tokens, size_t cursorPos sortedBeforeTokens = sortedBeforeTokens[0 .. $ - 1]; } - if (sortedBeforeTokens.length >= 2 + if (sortedBeforeTokens.length >= 2 && sortedBeforeTokens[$ - 1].type is tok!"." - && sortedBeforeTokens[$ - 2].type is tok!"identifier") + && (sortedBeforeTokens[$ - 2].type is tok!"identifier" || sortedBeforeTokens[$ - 2].type is tok!"stringLiteral")) { // Check if it's UFCS dot completion tokenCursorResult.completionContext = CompletionContext.DotCompletion; - tokenCursorResult.symbolIdentifierName = istring(sortedBeforeTokens[$ - 2].text); + tokenCursorResult.significantToken = sortedBeforeTokens[$ - 2]; return tokenCursorResult; } else if (!tokenCursorResult.partialIdentifier.length) @@ -117,7 +142,7 @@ private TokenCursorResult getCursorToken(const(Token)[] tokens, size_t cursorPos && slicedAtParen[$ - 1].type is tok!"(") { tokenCursorResult.completionContext = CompletionContext.ParenCompletion; - tokenCursorResult.symbolIdentifierName = istring(slicedAtParen[$ - 4].text); + tokenCursorResult.significantToken = slicedAtParen[$ - 4]; tokenCursorResult.functionName = istring(slicedAtParen[$ - 2].text); return tokenCursorResult; } @@ -187,29 +212,14 @@ DSymbol*[] getUFCSSymbolsForCursor(Scope* completionScope, scope ref const(Token return []; } - const(DSymbol)* cursorSymbol = completionScope.getFirstSymbolByNameAndCursor( - tokenCursorResult.symbolIdentifierName, cursorPosition); + DeducedSymbolTypeResult deducedSymbolTypeResult = deduceSymbolTypeByToken(completionScope, tokenCursorResult.significantToken, cursorPosition); - if (cursorSymbol is null) - { - warning("Coudn't find symbol ", tokenCursorResult.symbolIdentifierName); - return []; - } - - if (cursorSymbol.isInvalidForUFCSCompletion) - { - trace("CursorSymbol is invalid for UFCS"); - return []; - } - - const(DSymbol)* cursorSymbolType = deduceSymbolType(cursorSymbol); - - if (cursorSymbolType is null) + if (deducedSymbolTypeResult.deducedSymbolType is null) { return []; } - if (cursorSymbolType.isInvalidForUFCSCompletion) + if (deducedSymbolTypeResult.deducedSymbolType.isInvalidForUFCSCompletion) { trace("CursorSymbolType isn't valid for UFCS completion"); return []; @@ -217,11 +227,11 @@ DSymbol*[] getUFCSSymbolsForCursor(Scope* completionScope, scope ref const(Token if (tokenCursorResult.completionContext == CompletionContext.ParenCompletion) { - return getUFCSSymbolsForParenCompletion(cursorSymbolType, completionScope, tokenCursorResult.functionName, cursorPosition); + return getUFCSSymbolsForParenCompletion(deducedSymbolTypeResult.deducedSymbolType, completionScope, tokenCursorResult.functionName, cursorPosition); } else { - return getUFCSSymbolsForDotCompletion(cursorSymbolType, completionScope, cursorPosition, tokenCursorResult.partialIdentifier); + return getUFCSSymbolsForDotCompletion(deducedSymbolTypeResult.deducedSymbolType, completionScope, cursorPosition, tokenCursorResult.partialIdentifier); } } diff --git a/src/dcd/server/autocomplete/complete.d b/src/dcd/server/autocomplete/complete.d index 4363bf0..e7f4203 100644 --- a/src/dcd/server/autocomplete/complete.d +++ b/src/dcd/server/autocomplete/complete.d @@ -205,14 +205,21 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray, significantTokenType = beforeTokens[$ - 2].type; else return response; - switch (significantTokenType) { mixin(STRING_LITERAL_CASES); - foreach (symbol; arraySymbols) + { + foreach (symbol; arraySymbols){ response.completions ~= makeSymbolCompletionInfo(symbol, symbol.kind); + } response.completionType = CompletionType.identifiers; + RollbackAllocator rba; + ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, &rba, cursorPosition, moduleCache); + scope(exit) pair.destroy(); + response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, CompletionKind.ufcsName)).array; break; + } + mixin(TYPE_IDENT_CASES); case tok!")": case tok!"]": @@ -221,7 +228,11 @@ 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; + 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.identifiers; + } break; // these tokens before a "." mean "Module Scope Operator" case tok!":": diff --git a/tests/tc_ufcs_string_and_string_literal_completion/expected_string_literal_test.txt b/tests/tc_ufcs_string_and_string_literal_completion/expected_string_literal_test.txt new file mode 100644 index 0000000..1580c7a --- /dev/null +++ b/tests/tc_ufcs_string_and_string_literal_completion/expected_string_literal_test.txt @@ -0,0 +1,13 @@ +identifiers +alignof k +dup k +idup k +init k +length k +mangleof k +ptr k +sizeof k +stringof k +testUfcs F +ufcsString F +ufcsStringBar F diff --git a/tests/tc_ufcs_string_and_string_literal_completion/expected_string_test.txt b/tests/tc_ufcs_string_and_string_literal_completion/expected_string_test.txt new file mode 100644 index 0000000..a10414d --- /dev/null +++ b/tests/tc_ufcs_string_and_string_literal_completion/expected_string_test.txt @@ -0,0 +1,4 @@ +identifiers +testUfcs F +ufcsString F +ufcsStringBar F diff --git a/tests/tc_ufcs_string_and_string_literal_completion/file.d b/tests/tc_ufcs_string_and_string_literal_completion/file.d new file mode 100644 index 0000000..c21a30e --- /dev/null +++ b/tests/tc_ufcs_string_and_string_literal_completion/file.d @@ -0,0 +1,6 @@ +void ufcsString(string input){} +void ufcsStringBar(string input){} +void testUfcs(string x){ + "foo". + x. +} diff --git a/tests/tc_ufcs_string_and_string_literal_completion/run.sh b/tests/tc_ufcs_string_and_string_literal_completion/run.sh new file mode 100755 index 0000000..261775b --- /dev/null +++ b/tests/tc_ufcs_string_and_string_literal_completion/run.sh @@ -0,0 +1,15 @@ +set -e +set -u + +#TEST CASE 0 +SOURCE_FILE_0=file.d +ACTUAL_FILE_NAME_0=actual_string_literal_test.txt +EXPECTED_FILE_NAME_0=expected_string_literal_test.txt + +../../bin/dcd-client $1 -c99 $SOURCE_FILE_0 > $ACTUAL_FILE_NAME_0 +diff $ACTUAL_FILE_NAME_0 $EXPECTED_FILE_NAME_0 --strip-trailing-cr + +ACTUAL_FILE_NAME_1=actual_string_test.txt +EXPECTED_FILE_NAME_1=expected_string_test.txt +../../bin/dcd-client $1 -c103 $SOURCE_FILE_0 > $ACTUAL_FILE_NAME_1 +diff $ACTUAL_FILE_NAME_1 $EXPECTED_FILE_NAME_1 --strip-trailing-cr \ No newline at end of file