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*
* *auto* declarations (Mostly)
* *with* statements
* Simple UFCS suggestions for concrete types.
* 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)
* 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)

View File

@ -1,53 +1,10 @@
module dcd.server.autocomplete.calltip_utils;
import std.string;
import std.regex;
import std.range : empty;
import std.experimental.logger;
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)
{
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
{
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 containers.hashset : HashSet;
import std.experimental.logger;
import std.algorithm.iteration : map;
void lookupUFCS(Scope* completionScope, DSymbol* beforeDotSymbol, size_t cursorPosition, ref AutocompleteResponse response)
{
// UFCS completion
DSymbol*[] ufcsSymbols = getSymbolsForUFCS(completionScope, beforeDotSymbol, cursorPosition);
foreach (const symbol; ufcsSymbols)
{
response.completions ~= createCompletionForUFCS(symbol);
}
response.completions ~= map!(s => createCompletionForUFCS(s))(ufcsSymbols).array;
}
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
.symbolFile, symbol
.location, symbol
.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.
*
@ -54,22 +59,16 @@ AutocompleteResponse.Completion createCompletionForUFCS(const DSymbol* symbol)
*/
DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSymbol, size_t cursorPosition)
{
assert(beforeDotSymbol);
if (beforeDotSymbol.name is getBuiltinTypeName(tok!"void")
|| (beforeDotSymbol.type !is null
&& beforeDotSymbol.type.name is getBuiltinTypeName(tok!"void")))
{
return null; // no UFCS for void
if (beforeDotSymbol.isInvalidForUFCSCompletion) {
return null;
}
Scope* currentScope = completionScope.getScopeByCursor(cursorPosition);
assert(currentScope);
HashSet!size_t visited;
// local imports only
FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol), DSymbol*[]) app;
FilteredAppender!(a => a.protection != tok!"private", DSymbol*[]) globalsFunctions;
// local appender
FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol), DSymbol*[]) localAppender;
while (currentScope !is null && currentScope.parent !is null)
{
@ -79,51 +78,58 @@ DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSy
if (sym.type is null)
continue;
if (sym.qualifier == SymbolQualifier.selectiveImport)
app.put(sym.type);
localAppender.put(sym.type);
else
sym.type.getParts(internString(null), app, visited);
sym.type.getParts(internString(null), localAppender, visited);
}
currentScope = currentScope.parent;
}
// global appender
FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol, true), DSymbol*[]) globalAppender;
// global symbols and global imports
assert(currentScope !is null);
assert(currentScope.parent is null);
foreach (sym; currentScope.symbols)
{
if (sym.kind != CompletionKind.importSymbol)
app.put(sym);
localAppender.put(sym);
else if (sym.type !is null)
{
if (sym.qualifier == SymbolQualifier.selectiveImport)
app.put(sym.type);
else{
sym.type.getParts(istring(null), globalsFunctions, visited);
foreach(gSym; globalsFunctions) {
app.put(gSym);
}
localAppender.put(sym.type);
else
{
sym.type.getParts(istring(null), globalAppender, visited);
}
}
}
return app.data;
return localAppender.opSlice ~ globalAppender.opSlice;
}
/**
Params:
symbol = the symbol to check
firstArgumentSymbol = the first argument
incomingSymbol = symbols we check on
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
*/
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
// Where beforeDotSymbol matches first argument
return getFirstArgumentOfFunction(beforeDotSymbol.callTip) == firstArgumentSymbol.name;
return false;
}
if (incomingSymbol.kind == CompletionKind.functionName && !incomingSymbol
.functionParameters.empty)
{
return incomingSymbol.functionParameters.front.type is beforeDotSymbol;
}
return false;
}

View File

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

View File

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