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*
|
* *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)
|
||||||
|
|
|
@ -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)");
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
identifiers
|
identifiers
|
||||||
alignof k
|
alignof k
|
||||||
|
doStuff f
|
||||||
init k
|
init k
|
||||||
mangleof k
|
mangleof k
|
||||||
max k
|
max k
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
identifiers
|
identifiers
|
||||||
alignof k
|
alignof k
|
||||||
|
doStuff f
|
||||||
init k
|
init k
|
||||||
mangleof k
|
mangleof k
|
||||||
max k
|
max k
|
||||||
|
|
Loading…
Reference in New Issue