diff --git a/README.md b/README.md index d56595a..9b29282 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,9 @@ the issue.) * *auto* declarations (Mostly) * *with* statements * Simple UFCS suggestions for concrete types. + * UFCS '.' function chaining with concrete types except string type. * Not working: - * UFCS completion for templates, literals, UFCS function arguments, and '.' chaining with other UFCS functions. + * UFCS completion for templates, literals, UFCS function arguments. * UFCS calltips * Autocompletion of declarations with template arguments (This will work to some extent, but it won't do things like replace T with int) * Determining the type of an enum member when no base type is specified, but the first member has an initializer @@ -157,6 +158,7 @@ tab character, followed by a completion kind resolvedType v calltip v getPartByName f + ufcsFunction F #### Extended output mode diff --git a/dsymbol/src/dsymbol/conversion/first.d b/dsymbol/src/dsymbol/conversion/first.d index e488566..45cd95d 100644 --- a/dsymbol/src/dsymbol/conversion/first.d +++ b/dsymbol/src/dsymbol/conversion/first.d @@ -37,6 +37,8 @@ import std.experimental.allocator; import std.experimental.allocator.gc_allocator : GCAllocator; import std.experimental.logger; import std.typecons : Rebindable; +import std.array : appender; +import std.range : empty; /** * First Pass handles the following: @@ -944,10 +946,23 @@ private: } } + void setFunctionReturnType(const Type returnType, ref DSymbol acSymbol){ + if (returnType !is null) + { + auto app = appender!string(); + app.formatNode(returnType); + auto foundSymbols = currentScope.getSymbolsByName(istring(app.data)); + if (!foundSymbols.empty){ + acSymbol.functionReturnType = foundSymbols[0]; + } + } + } + void processParameters(SemanticSymbol* symbol, const Type returnType, string functionName, const Parameters parameters, const TemplateParameters templateParameters) { + setFunctionReturnType(returnType, *symbol.acSymbol); processTemplateParameters(symbol, templateParameters); if (parameters !is null) { diff --git a/dsymbol/src/dsymbol/symbol.d b/dsymbol/src/dsymbol/symbol.d index 6f56e0f..2f5f50a 100644 --- a/dsymbol/src/dsymbol/symbol.d +++ b/dsymbol/src/dsymbol/symbol.d @@ -386,6 +386,12 @@ struct DSymbol */ DSymbol*[] functionParameters; + /** + * + * Return type of the function + */ + DSymbol* functionReturnType; + private uint _location; /** diff --git a/src/dcd/server/autocomplete/complete.d b/src/dcd/server/autocomplete/complete.d index 79bfa08..4de2968 100644 --- a/src/dcd/server/autocomplete/complete.d +++ b/src/dcd/server/autocomplete/complete.d @@ -558,8 +558,10 @@ void setCompletions(T)(ref AutocompleteResponse response, DSymbol*[] symbols = getSymbolsByTokenChain(completionScope, tokens, cursorPosition, completionType); - if (symbols.length == 0) + if (symbols.length == 0){ + lookupUFCSForDotChaining(completionScope, tokens, cursorPosition, response); return; + } if (completionType == CompletionType.identifiers) { diff --git a/src/dcd/server/autocomplete/ufcs.d b/src/dcd/server/autocomplete/ufcs.d index 8a38911..d4bb637 100644 --- a/src/dcd/server/autocomplete/ufcs.d +++ b/src/dcd/server/autocomplete/ufcs.d @@ -14,11 +14,15 @@ import dparse.lexer : tok; import std.regex; import containers.hashset : HashSet; -void lookupUFCS(Scope* completionScope, DSymbol* beforeDotSymbol, size_t cursorPosition, ref AutocompleteResponse response) +bool lookupUFCS(Scope* completionScope, DSymbol* beforeDotSymbol, size_t cursorPosition, ref AutocompleteResponse response) { - // UFCS completion + if (beforeDotSymbol.isInvalidForUFCSCompletion) + return false; DSymbol*[] ufcsSymbols = getSymbolsForUFCS(completionScope, beforeDotSymbol, cursorPosition); + if (ufcsSymbols.empty) + return false; response.completions ~= map!(s => createCompletionForUFCS(s))(ufcsSymbols).array; + return true; } AutocompleteResponse.Completion createCompletionForUFCS(const DSymbol* symbol) @@ -169,3 +173,32 @@ bool doUFCSSearch(string beforeToken, string lastToken) // we do the search if they are different from eachother return beforeToken != lastToken; } + +void lookupUFCSForDotChaining(T)(Scope* completionScope, ref T tokens, size_t cursorPosition, ref AutocompleteResponse response) +{ + auto filteredTokens = filter!(t => !t.text.empty)(tokens); + + if (filteredTokens.empty) + return; + + auto foundTokens = filteredTokens.array; + auto possibleUFCSSymbols = completionScope.getSymbolsByNameAndCursor(istring(null), cursorPosition); + for (auto i = foundTokens.length; i > 0; i--) + { + // Finding function return value type + auto foundSymbols = filter!(s => s.name == foundTokens[i - 1].text && s.kind == CompletionKind + .functionName)(possibleUFCSSymbols); + if (!foundSymbols.empty) + { + auto foundFunctionSymbol = foundSymbols.array.back; + if (foundFunctionSymbol.functionReturnType) + { + if (lookupUFCS(completionScope, foundFunctionSymbol.functionReturnType, cursorPosition, response)) + { + response.completionType = CompletionType.identifiers; + } + } + break; + } + } +} \ No newline at end of file diff --git a/tests/tc_ufcs_completions/expected.txt b/tests/tc_ufcs_completions/expected.txt index 01eae0a..1a48285 100644 --- a/tests/tc_ufcs_completions/expected.txt +++ b/tests/tc_ufcs_completions/expected.txt @@ -15,4 +15,6 @@ ufcsBarRefConstWrapped F ufcsBarRefImmuttableWrapped F ufcsBarReturnScope F ufcsBarScope F +ufcsGetNumber F ufcsHello F +ufcsSelf F diff --git a/tests/tc_ufcs_completions/expected2.txt b/tests/tc_ufcs_completions/expected2.txt new file mode 100644 index 0000000..577bfeb --- /dev/null +++ b/tests/tc_ufcs_completions/expected2.txt @@ -0,0 +1,13 @@ +identifiers +hasArgname F +u F +ufcsBar F +ufcsBarRef F +ufcsBarRefConst F +ufcsBarRefConstWrapped F +ufcsBarRefImmuttableWrapped F +ufcsBarReturnScope F +ufcsBarScope F +ufcsGetNumber F +ufcsHello F +ufcsSelf F diff --git a/tests/tc_ufcs_completions/expected3.txt b/tests/tc_ufcs_completions/expected3.txt new file mode 100644 index 0000000..7dee382 --- /dev/null +++ b/tests/tc_ufcs_completions/expected3.txt @@ -0,0 +1,3 @@ +identifiers +helloNumber F +isEvenNumber F diff --git a/tests/tc_ufcs_completions/expected4.txt b/tests/tc_ufcs_completions/expected4.txt new file mode 100644 index 0000000..1b05e62 --- /dev/null +++ b/tests/tc_ufcs_completions/expected4.txt @@ -0,0 +1,2 @@ +identifiers +getBoolToFloat F diff --git a/tests/tc_ufcs_completions/expected5.txt b/tests/tc_ufcs_completions/expected5.txt new file mode 100644 index 0000000..046e1c5 --- /dev/null +++ b/tests/tc_ufcs_completions/expected5.txt @@ -0,0 +1,2 @@ +identifiers +multiply1000 F diff --git a/tests/tc_ufcs_completions/expected6.txt b/tests/tc_ufcs_completions/expected6.txt new file mode 100644 index 0000000..7dee382 --- /dev/null +++ b/tests/tc_ufcs_completions/expected6.txt @@ -0,0 +1,3 @@ +identifiers +helloNumber F +isEvenNumber F diff --git a/tests/tc_ufcs_completions/file.d b/tests/tc_ufcs_completions/file.d index 4c7f6b8..40bc356 100644 --- a/tests/tc_ufcs_completions/file.d +++ b/tests/tc_ufcs_completions/file.d @@ -1,4 +1,3 @@ -//import foodata; import fooutils; void hasArgname(Foo f){ @@ -7,4 +6,9 @@ void main() { auto foo = Foo(); foo. + foo.ufcsSelf. + foo.ufcsSelf.ufcsGetNumber(42). + foo.ufcsSelf.ufcsGetNumber(42).isEvenNumber. + foo.ufcsSelf.ufcsGetNumber(42).isEvenNumber.getBoolToFloat. + foo.ufcsSelf.ufcsGetNumber(42).isEvenNumber.getBoolToFloat.multiply1000. } diff --git a/tests/tc_ufcs_completions/fooutils/fooutils.d b/tests/tc_ufcs_completions/fooutils/fooutils.d index 4b06cfc..c65546c 100644 --- a/tests/tc_ufcs_completions/fooutils/fooutils.d +++ b/tests/tc_ufcs_completions/fooutils/fooutils.d @@ -5,6 +5,8 @@ struct Foo { } void u(Foo foo) {} +Foo ufcsSelf(ref Foo foo) { return foo; } +int ufcsGetNumber(ref Foo foo, int number) { return number; } void ufcsHello(ref Foo foo) {} void ufcsBar(Foo foo, string mama) {} void ufcsBarRef(ref Foo foo, string mama) {} @@ -14,4 +16,7 @@ void ufcsBarRefImmuttableWrapped(ref immutable(Foo) foo, string mama) {} void ufcsBarScope(ref scope Foo foo, string mama) {} void ufcsBarReturnScope(return scope Foo foo, string mama) {} private void ufcsBarPrivate(Foo foo, string message) {} -void helloBar(string message) {} \ No newline at end of file +void helloNumber(int message) {} +bool isEvenNumber(int number) { return number % 2 == 0; } +float getBoolToFloat(bool result) { return result ? 1.0 : 0.0; } +int multiply1000(float x) { return x * 1000; } diff --git a/tests/tc_ufcs_completions/run.sh b/tests/tc_ufcs_completions/run.sh index 1532e70..fdee570 100755 --- a/tests/tc_ufcs_completions/run.sh +++ b/tests/tc_ufcs_completions/run.sh @@ -1,5 +1,15 @@ set -e set -u -../../bin/dcd-client $1 -c100 -I"$PWD"/fooutils file.d > actual.txt +../../bin/dcd-client $1 -c82 -I"$PWD"/fooutils file.d > actual.txt +../../bin/dcd-client $1 -c97 -I"$PWD"/fooutils file.d > actual2.txt +../../bin/dcd-client $1 -c130 -I"$PWD"/fooutils file.d > actual3.txt +../../bin/dcd-client $1 -c176 -I"$PWD"/fooutils file.d > actual4.txt +../../bin/dcd-client $1 -c237 -I"$PWD"/fooutils file.d > actual5.txt +../../bin/dcd-client $1 -c311 -I"$PWD"/fooutils file.d > actual6.txt diff actual.txt expected.txt +diff actual2.txt expected2.txt +diff actual3.txt expected3.txt +diff actual4.txt expected4.txt +diff actual5.txt expected5.txt +diff actual6.txt expected6.txt