Adding Simple UFCS
Added UFCS navigation Adding test cases for ufcs With ufcs update expected test Updated UFCS with templating support Removing template function.
This commit is contained in:
parent
2d9084ff77
commit
9e60ab8b2c
|
@ -0,0 +1,107 @@
|
|||
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('(');
|
||||
// has only one argument
|
||||
if (!callTip.canFind(','))
|
||||
{
|
||||
return parentheseSplit[0] ~ "()";
|
||||
}
|
||||
auto commaSplit = parentheseSplit[1].split(',');
|
||||
string newCallTip = callTip.replace((commaSplit[0] ~ ", "), "");
|
||||
return newCallTip;
|
||||
|
||||
}
|
||||
|
||||
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)");
|
||||
assert(result, "void fooFunction()");
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
auto result = removeFirstArgumentOfFunction(
|
||||
"void fooFunction(const(immutable(Foo)) foo), string message");
|
||||
assert(result, "void fooFunction(string message)");
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
auto result = removeFirstArgumentOfFunction(
|
||||
"void fooFunction(const(immutable(Foo)) foo), string message, ref int age");
|
||||
assert(result, "void fooFunction(string message, ref int age)");
|
||||
}
|
|
@ -30,6 +30,7 @@ import std.string;
|
|||
import std.typecons;
|
||||
|
||||
import dcd.server.autocomplete.util;
|
||||
import dcd.server.autocomplete.ufcs;
|
||||
|
||||
import dparse.lexer;
|
||||
import dparse.rollback_allocator;
|
||||
|
@ -201,7 +202,7 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray,
|
|||
}
|
||||
else if (beforeTokens.length >= 2 && beforeTokens[$ - 1] == tok!".")
|
||||
significantTokenType = beforeTokens[$ - 2].type;
|
||||
else
|
||||
else
|
||||
return response;
|
||||
|
||||
switch (significantTokenType)
|
||||
|
@ -324,7 +325,7 @@ in
|
|||
{
|
||||
assert (beforeTokens.length >= 2);
|
||||
}
|
||||
body
|
||||
do
|
||||
{
|
||||
AutocompleteResponse response;
|
||||
if (beforeTokens.length <= 2)
|
||||
|
@ -574,6 +575,7 @@ void setCompletions(T)(ref AutocompleteResponse response,
|
|||
}
|
||||
addSymToResponse(symbols[0], response, partial, completionScope);
|
||||
response.completionType = CompletionType.identifiers;
|
||||
lookupUFCS(completionScope, symbols[0], cursorPosition, response);
|
||||
}
|
||||
else if (completionType == CompletionType.calltips)
|
||||
{
|
||||
|
@ -669,7 +671,7 @@ in
|
|||
{
|
||||
assert(symbol.kind == CompletionKind.structName);
|
||||
}
|
||||
body
|
||||
do
|
||||
{
|
||||
string generatedStructConstructorCalltip = "this(";
|
||||
const(DSymbol)*[] fields = symbol.opSlice().filter!(
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
module dcd.server.autocomplete.ufcs;
|
||||
|
||||
import dcd.server.autocomplete.util;
|
||||
import dsymbol.symbol;
|
||||
import dsymbol.scope_;
|
||||
import dcd.common.messages;
|
||||
import std.functional : unaryFun;
|
||||
import std.algorithm;
|
||||
import std.array;
|
||||
import std.range;
|
||||
import dsymbol.builtin.names;
|
||||
import std.string;
|
||||
import dparse.lexer : tok;
|
||||
import std.regex;
|
||||
import dcd.server.autocomplete.calltip_utils;
|
||||
import containers.hashset : HashSet;
|
||||
import std.experimental.logger;
|
||||
|
||||
void lookupUFCS(Scope* completionScope, DSymbol* beforeDotSymbol, size_t cursorPosition, ref AutocompleteResponse response)
|
||||
{
|
||||
// UFCS completion
|
||||
DSymbol*[] ufcsSymbols = getSymbolsForUFCS(completionScope, beforeDotSymbol, cursorPosition);
|
||||
|
||||
foreach (const symbol; ufcsSymbols)
|
||||
{
|
||||
// Filtering only those that match with type of the beforeDotSymbol
|
||||
// We use the calltip since we need more data from dsymbol
|
||||
// hopefully this is solved in the future
|
||||
if (getFirstArgumentOfFunction(symbol.callTip) == beforeDotSymbol.name)
|
||||
{
|
||||
response.completions ~= createCompletionForUFCS(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AutocompleteResponse.Completion createCompletionForUFCS(const DSymbol* symbol)
|
||||
{
|
||||
return AutocompleteResponse.Completion(symbol.name, symbol.kind, removeFirstArgumentOfFunction(
|
||||
symbol.callTip), symbol
|
||||
.symbolFile, symbol
|
||||
.location, symbol
|
||||
.doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get symbols suitable for UFCS.
|
||||
*
|
||||
* a symbol is suitable for UFCS if it satisfies the following:
|
||||
* $(UL
|
||||
* $(LI is global or imported)
|
||||
* $(LI is callable with $(D implicitArg) as it's first argument)
|
||||
* )
|
||||
*
|
||||
* Params:
|
||||
* completionScope = current scope
|
||||
* beforeDotSymbol = the symbol before the dot (implicit first argument to UFCS function)
|
||||
* cursorPosition = current position
|
||||
* Returns:
|
||||
* callable an array of symbols suitable for UFCS at $(D cursorPosition)
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
Scope* currentScope = completionScope.getScopeByCursor(cursorPosition);
|
||||
assert(currentScope);
|
||||
HashSet!size_t visited;
|
||||
// local imports only
|
||||
FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol), DSymbol*[]) app;
|
||||
while (currentScope !is null && currentScope.parent !is null)
|
||||
{
|
||||
auto localImports = currentScope.symbols.filter!(a => a.kind == CompletionKind.importSymbol);
|
||||
foreach (sym; localImports)
|
||||
{
|
||||
if (sym.type is null)
|
||||
continue;
|
||||
if (sym.qualifier == SymbolQualifier.selectiveImport)
|
||||
app.put(sym.type);
|
||||
else
|
||||
sym.type.getParts(internString(null), app, visited);
|
||||
}
|
||||
|
||||
currentScope = currentScope.parent;
|
||||
}
|
||||
// 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);
|
||||
else if (sym.type !is null)
|
||||
{
|
||||
if (sym.qualifier == SymbolQualifier.selectiveImport)
|
||||
app.put(sym.type);
|
||||
else
|
||||
sym.type.getParts(internString(null), app, visited);
|
||||
}
|
||||
}
|
||||
return app.data;
|
||||
}
|
||||
|
||||
/**
|
||||
Params:
|
||||
symbol = the symbol to check
|
||||
arg0 = the argument
|
||||
Returns:
|
||||
true if if $(D symbol) is callable with $(D arg0) as it's first argument
|
||||
false otherwise
|
||||
*/
|
||||
bool isCallableWithArg(const(DSymbol)* symbol, const(DSymbol)* arg0)
|
||||
{
|
||||
// FIXME: do signature type checking?
|
||||
// a lot is to be done in dsymbol for type checking to work.
|
||||
// for instance, define an isSbtype function for where it is applicable
|
||||
// ex: interfaces, subclasses, builtintypes ...
|
||||
|
||||
// FIXME: instruct dsymbol to always save paramater symbols
|
||||
// and check these instead of checking callTip
|
||||
|
||||
static bool checkCallTip(string callTip)
|
||||
{
|
||||
assert(callTip.length);
|
||||
if (callTip.endsWith("()"))
|
||||
return false; // takes no arguments
|
||||
else if (callTip.endsWith("(...)"))
|
||||
return true;
|
||||
else
|
||||
return true; // FIXME: assume yes?
|
||||
}
|
||||
|
||||
assert(symbol);
|
||||
assert(arg0);
|
||||
|
||||
switch (symbol.kind)
|
||||
{
|
||||
case CompletionKind.dummy:
|
||||
if (symbol.qualifier == SymbolQualifier.func)
|
||||
return checkCallTip(symbol.callTip);
|
||||
break;
|
||||
case CompletionKind.importSymbol:
|
||||
if (symbol.type is null)
|
||||
break;
|
||||
if (symbol.qualifier == SymbolQualifier.selectiveImport)
|
||||
return symbol.type.isCallableWithArg(arg0);
|
||||
break;
|
||||
case CompletionKind.structName:
|
||||
foreach (constructor; symbol.getPartsByName(CONSTRUCTOR_SYMBOL_NAME))
|
||||
{
|
||||
// check user defined contructors or auto-generated constructor
|
||||
if (checkCallTip(constructor.callTip))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case CompletionKind.variableName:
|
||||
case CompletionKind.enumMember: // assuming anonymous enum member
|
||||
if (symbol.type !is null)
|
||||
{
|
||||
if (symbol.type.qualifier == SymbolQualifier.func)
|
||||
return checkCallTip(symbol.type.callTip);
|
||||
foreach (functor; symbol.type.getPartsByName(internString("opCall")))
|
||||
if (checkCallTip(functor.callTip))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case CompletionKind.functionName:
|
||||
return checkCallTip(symbol.callTip);
|
||||
case CompletionKind.enumName:
|
||||
case CompletionKind.aliasName:
|
||||
if (symbol.type !is null && symbol.type !is symbol)
|
||||
return symbol.type.isCallableWithArg(arg0);
|
||||
break;
|
||||
case CompletionKind.unionName:
|
||||
case CompletionKind.templateName:
|
||||
return true; // can we do more checks?
|
||||
case CompletionKind.withSymbol:
|
||||
case CompletionKind.className:
|
||||
case CompletionKind.interfaceName:
|
||||
case CompletionKind.memberVariableName:
|
||||
case CompletionKind.keyword:
|
||||
case CompletionKind.packageName:
|
||||
case CompletionKind.moduleName:
|
||||
case CompletionKind.mixinTemplateName:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// $(D appender) with filter on $(D put)
|
||||
struct FilteredAppender(alias predicate, T:
|
||||
T[] = DSymbol*[]) if (__traits(compiles, unaryFun!predicate(T.init) ? 0 : 0))
|
||||
{
|
||||
alias pred = unaryFun!predicate;
|
||||
private Appender!(T[]) app;
|
||||
|
||||
void put(T item)
|
||||
{
|
||||
if (pred(item))
|
||||
app.put(item);
|
||||
}
|
||||
|
||||
void put(R)(R items) if (isInputRange!R && __traits(compiles, put(R.init.front)))
|
||||
{
|
||||
foreach (item; items)
|
||||
put(item);
|
||||
}
|
||||
|
||||
void opOpAssign(string op : "~")(T rhs)
|
||||
{
|
||||
put(rhs);
|
||||
}
|
||||
|
||||
alias app this;
|
||||
}
|
||||
|
||||
@safe pure nothrow unittest
|
||||
{
|
||||
FilteredAppender!("a%2", int[]) app;
|
||||
app.put(iota(10));
|
||||
assert(app.data == [1, 3, 5, 7, 9]);
|
||||
}
|
||||
|
||||
bool doUFCSSearch(string beforeToken, string lastToken)
|
||||
{
|
||||
// we do the search if they are different from eachother
|
||||
return beforeToken != lastToken;
|
||||
}
|
|
@ -37,6 +37,7 @@ import dsymbol.modulecache;
|
|||
import dsymbol.scope_;
|
||||
import dsymbol.string_interning;
|
||||
import dsymbol.symbol;
|
||||
import dcd.server.autocomplete.ufcs;
|
||||
|
||||
enum ImportKind : ubyte
|
||||
{
|
||||
|
@ -143,8 +144,14 @@ SymbolStuff getSymbolsForCompletion(const AutocompleteRequest request,
|
|||
ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray,
|
||||
rba, request.cursorPosition, moduleCache);
|
||||
auto expression = getExpression(beforeTokens);
|
||||
return SymbolStuff(getSymbolsByTokenChain(pair.scope_, expression,
|
||||
request.cursorPosition, type), pair.symbol, pair.scope_);
|
||||
auto symbols = getSymbolsByTokenChain(pair.scope_, expression,
|
||||
request.cursorPosition, type);
|
||||
if (symbols.length == 0 && doUFCSSearch(stringToken(beforeTokens.front), stringToken(beforeTokens.back))) {
|
||||
// Let search for UFCS, since we got no hit
|
||||
symbols ~= getSymbolsByTokenChain(pair.scope_, getExpression([beforeTokens.back]),
|
||||
request.cursorPosition, type);
|
||||
}
|
||||
return SymbolStuff(symbols, pair.symbol, pair.scope_);
|
||||
}
|
||||
|
||||
bool isSliceExpression(T)(T tokens, size_t index)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
identifiers
|
||||
a v
|
||||
alignof k
|
||||
foo f
|
||||
init k
|
||||
mangleof k
|
||||
sizeof k
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
module barutils;
|
||||
void ufcsBar(Foo foo, string message)
|
||||
{
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import barutils;
|
||||
|
||||
void main()
|
||||
{
|
||||
auto foo = Foo();
|
||||
foo.ufcsBar;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
set -e
|
||||
set -u
|
||||
|
||||
../../bin/dcd-client $1 -c57 -l -I"$PWD"/barutils file.d > actual.txt
|
||||
echo -e "$PWD/barutils/barutils.d\t22" > expected.txt
|
||||
diff actual.txt expected.txt
|
|
@ -0,0 +1,18 @@
|
|||
identifiers
|
||||
alignof k
|
||||
fooHey f
|
||||
hasArgname f
|
||||
init k
|
||||
mangleof k
|
||||
sizeof k
|
||||
stringof k
|
||||
tupleof k
|
||||
u f
|
||||
ufcsBar f
|
||||
ufcsBarRef f
|
||||
ufcsBarRefConst f
|
||||
ufcsBarRefConstWrapped f
|
||||
ufcsBarRefImmuttableWrapped f
|
||||
ufcsBarReturnScope f
|
||||
ufcsBarScope f
|
||||
ufcsHello f
|
|
@ -0,0 +1,10 @@
|
|||
//import foodata;
|
||||
import fooutils;
|
||||
|
||||
void hasArgname(Foo f){
|
||||
}
|
||||
void main()
|
||||
{
|
||||
auto foo = Foo();
|
||||
foo.
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
module fooutils;
|
||||
|
||||
struct Foo {
|
||||
void fooHey(){ }
|
||||
}
|
||||
|
||||
void u(Foo foo) {
|
||||
}
|
||||
|
||||
void ufcsHello(ref Foo foo)
|
||||
{
|
||||
}
|
||||
|
||||
void ufcsBar(Foo foo, string mama)
|
||||
{
|
||||
}
|
||||
|
||||
void ufcsBarRef(ref Foo foo, string mama)
|
||||
{
|
||||
}
|
||||
|
||||
void ufcsBarRefConst(ref const Foo foo, string mama)
|
||||
{
|
||||
}
|
||||
void ufcsBarRefConstWrapped(ref const(Foo) foo, string mama) {}
|
||||
void ufcsBarRefImmuttableWrapped(ref immutable(Foo) foo, string mama) {}
|
||||
void ufcsBarScope(ref scope Foo foo, string mama) {}
|
||||
void ufcsBarReturnScope(return scope Foo foo, string mama) {}
|
|
@ -0,0 +1,5 @@
|
|||
set -e
|
||||
set -u
|
||||
|
||||
../../bin/dcd-client $1 -c100 -I"$PWD"/fooutils file.d > actual.txt
|
||||
diff actual.txt expected.txt
|
Loading…
Reference in New Issue