UFSC implementation using functionParameters instead and update tests for UFSC

This commit is contained in:
davu 2022-10-15 23:12:56 +02:00 committed by Jan Jurzitza
parent 951870e06f
commit 52c0298c3a
5 changed files with 44 additions and 102 deletions

View File

@ -35,8 +35,9 @@ the issue.)
* *alias this* * *alias this*
* *auto* declarations (Mostly) * *auto* declarations (Mostly)
* *with* statements * *with* statements
* Simple UFCS suggestions for concrete types.
* Not working: * Not working:
* UFCS suggestions * UFCS completion for templates, literals, UFCS function arguments, and '.' chaining with other UFCS functions.
* Autocompletion of declarations with template arguments (This will work to some extent, but it won't do things like replace T with int) * 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 * Determining the type of an enum member when no base type is specified, but the first member has an initializer
* auto functions (which can then propagate the failure to auto declarations) * auto functions (which can then propagate the failure to auto declarations)

View File

@ -1,53 +1,10 @@
module dcd.server.autocomplete.calltip_utils; module dcd.server.autocomplete.calltip_utils;
import std.string; import std.string;
import std.regex; import std.regex;
import std.range : empty; import std.range : empty;
import std.experimental.logger; import std.experimental.logger;
import std.algorithm : canFind; import std.algorithm : canFind;
/**
* Extracting the first argument type
* which isn't lazy, return, scope etc
* Params:
* text = the string we want to extract from
* Returns: first type in the text
*/
string extractFirstArgType(string text)
{
// Then match the first word that isn't lazy return scope ... etc.
auto firstWordRegex = regex(`(?!lazy|return|scope|in|out|ref|const|immutable\b)\b\w+`);
auto matchFirstType = matchFirst(text, firstWordRegex);
string firstArgument = matchFirstType.captures.back;
return firstArgument.empty ? "" : firstArgument;
}
/**
*
* Params:
* callTip = the symbols calltip
* Returns: the first argument type of the calltip
*/
string getFirstArgumentOfFunction(string callTip)
{
auto splitParentheses = callTip.split('(');
// First match all inside the parentheses
auto insideParenthesesRegex = regex(`\((.*\))`);
auto match = matchFirst(callTip, insideParenthesesRegex);
string insideParentheses = match.captures.back;
if (insideParentheses.empty)
{
return "";
}
return extractFirstArgType(insideParentheses);
}
string removeFirstArgumentOfFunction(string callTip) string removeFirstArgumentOfFunction(string callTip)
{ {
auto parentheseSplit = callTip.split('('); auto parentheseSplit = callTip.split('(');
@ -62,30 +19,6 @@ string removeFirstArgumentOfFunction(string callTip)
} }
unittest
{
auto result = getFirstArgumentOfFunction("void fooFunction(ref const(Foo) bar)");
assert(result, "Foo");
}
unittest
{
auto result = getFirstArgumentOfFunction("void fooFunction(Foo foo, string message)");
assert(result, "Foo");
}
unittest
{
auto result = getFirstArgumentOfFunction("void fooFunction(ref immutable(Foo) bar)");
assert(result, "Foo");
}
unittest
{
auto result = getFirstArgumentOfFunction("void fooFunction(const(immutable(Foo)) foo)");
assert(result, "Foo");
}
unittest unittest
{ {
auto result = removeFirstArgumentOfFunction("void fooFunction(const(immutable(Foo)) foo)"); auto result = removeFirstArgumentOfFunction("void fooFunction(const(immutable(Foo)) foo)");

View File

@ -15,27 +15,32 @@ import std.regex;
import dcd.server.autocomplete.calltip_utils; import dcd.server.autocomplete.calltip_utils;
import containers.hashset : HashSet; import containers.hashset : HashSet;
import std.experimental.logger; import std.experimental.logger;
import std.algorithm.iteration : map;
void lookupUFCS(Scope* completionScope, DSymbol* beforeDotSymbol, size_t cursorPosition, ref AutocompleteResponse response) void lookupUFCS(Scope* completionScope, DSymbol* beforeDotSymbol, size_t cursorPosition, ref AutocompleteResponse response)
{ {
// UFCS completion // UFCS completion
DSymbol*[] ufcsSymbols = getSymbolsForUFCS(completionScope, beforeDotSymbol, cursorPosition); DSymbol*[] ufcsSymbols = getSymbolsForUFCS(completionScope, beforeDotSymbol, cursorPosition);
response.completions ~= map!(s => createCompletionForUFCS(s))(ufcsSymbols).array;
foreach (const symbol; ufcsSymbols)
{
response.completions ~= createCompletionForUFCS(symbol);
}
} }
AutocompleteResponse.Completion createCompletionForUFCS(const DSymbol* symbol) AutocompleteResponse.Completion createCompletionForUFCS(const DSymbol* symbol)
{ {
return AutocompleteResponse.Completion(symbol.name, symbol.kind, removeFirstArgumentOfFunction( return AutocompleteResponse.Completion(symbol.name, symbol.kind, "(UFCS) " ~ removeFirstArgumentOfFunction(
symbol.callTip), symbol symbol.callTip), symbol
.symbolFile, symbol .symbolFile, symbol
.location, symbol .location, symbol
.doc); .doc);
} }
// Check if beforeDotSymbol is null or void
bool isInvalidForUFCSCompletion(const(DSymbol)* beforeDotSymbol)
{
return beforeDotSymbol is null
|| beforeDotSymbol.name is getBuiltinTypeName(tok!"void")
|| (beforeDotSymbol.type !is null && beforeDotSymbol.type.name is getBuiltinTypeName(
tok!"void"));
}
/** /**
* Get symbols suitable for UFCS. * Get symbols suitable for UFCS.
* *
@ -54,22 +59,16 @@ AutocompleteResponse.Completion createCompletionForUFCS(const DSymbol* symbol)
*/ */
DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSymbol, size_t cursorPosition) DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSymbol, size_t cursorPosition)
{ {
assert(beforeDotSymbol); if (beforeDotSymbol.isInvalidForUFCSCompletion) {
return null;
if (beforeDotSymbol.name is getBuiltinTypeName(tok!"void")
|| (beforeDotSymbol.type !is null
&& beforeDotSymbol.type.name is getBuiltinTypeName(tok!"void")))
{
return null; // no UFCS for void
} }
Scope* currentScope = completionScope.getScopeByCursor(cursorPosition); Scope* currentScope = completionScope.getScopeByCursor(cursorPosition);
assert(currentScope); assert(currentScope);
HashSet!size_t visited; HashSet!size_t visited;
// local imports only
FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol), DSymbol*[]) app; // local appender
FilteredAppender!(a => a.protection != tok!"private", DSymbol*[]) globalsFunctions; FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol), DSymbol*[]) localAppender;
while (currentScope !is null && currentScope.parent !is null) while (currentScope !is null && currentScope.parent !is null)
{ {
@ -79,51 +78,58 @@ DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSy
if (sym.type is null) if (sym.type is null)
continue; continue;
if (sym.qualifier == SymbolQualifier.selectiveImport) if (sym.qualifier == SymbolQualifier.selectiveImport)
app.put(sym.type); localAppender.put(sym.type);
else else
sym.type.getParts(internString(null), app, visited); sym.type.getParts(internString(null), localAppender, visited);
} }
currentScope = currentScope.parent; currentScope = currentScope.parent;
} }
// global appender
FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol, true), DSymbol*[]) globalAppender;
// global symbols and global imports // global symbols and global imports
assert(currentScope !is null); assert(currentScope !is null);
assert(currentScope.parent is null); assert(currentScope.parent is null);
foreach (sym; currentScope.symbols) foreach (sym; currentScope.symbols)
{ {
if (sym.kind != CompletionKind.importSymbol) if (sym.kind != CompletionKind.importSymbol)
app.put(sym); localAppender.put(sym);
else if (sym.type !is null) else if (sym.type !is null)
{ {
if (sym.qualifier == SymbolQualifier.selectiveImport) if (sym.qualifier == SymbolQualifier.selectiveImport)
app.put(sym.type); localAppender.put(sym.type);
else{ else
sym.type.getParts(istring(null), globalsFunctions, visited); {
foreach(gSym; globalsFunctions) { sym.type.getParts(istring(null), globalAppender, visited);
app.put(gSym);
} }
} }
} }
} return localAppender.opSlice ~ globalAppender.opSlice;
return app.data;
} }
/** /**
Params: Params:
symbol = the symbol to check symbol = the symbol to check
firstArgumentSymbol = the first argument incomingSymbol = symbols we check on
Returns: Returns:
true if if $(D symbol) is callable with $(D firstArgumentSymbol) as it's first argument true if incomingSymbols first parameter matches beforeDotSymbol
false otherwise false otherwise
*/ */
bool isCallableWithArg(const(DSymbol)* beforeDotSymbol, const(DSymbol)* firstArgumentSymbol) bool isCallableWithArg(const(DSymbol)* incomingSymbol, const(DSymbol)* beforeDotSymbol, bool isGlobalScope = false)
{ {
if (beforeDotSymbol.kind == CompletionKind.functionName) if (isGlobalScope && incomingSymbol.protection == tok!"private")
{ {
// Keeping it simple only support for functions for now return false;
// Where beforeDotSymbol matches first argument
return getFirstArgumentOfFunction(beforeDotSymbol.callTip) == firstArgumentSymbol.name;
} }
if (incomingSymbol.kind == CompletionKind.functionName && !incomingSymbol
.functionParameters.empty)
{
return incomingSymbol.functionParameters.front.type is beforeDotSymbol;
}
return false; return false;
} }

View File

@ -1,5 +1,6 @@
identifiers identifiers
alignof k alignof k
doStuff f
init k init k
mangleof k mangleof k
max k max k

View File

@ -1,5 +1,6 @@
identifiers identifiers
alignof k alignof k
doStuff f
init k init k
mangleof k mangleof k
max k max k