UFSC implementation using functionParameters instead and update tests for UFSC
This commit is contained in:
parent
951870e06f
commit
52c0298c3a
|
@ -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)
|
||||
|
|
|
@ -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)");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
identifiers
|
||||
alignof k
|
||||
doStuff f
|
||||
init k
|
||||
mangleof k
|
||||
max k
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
identifiers
|
||||
alignof k
|
||||
doStuff f
|
||||
init k
|
||||
mangleof k
|
||||
max k
|
||||
|
|
Loading…
Reference in New Issue