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 std.typecons;
|
||||||
|
|
||||||
import dcd.server.autocomplete.util;
|
import dcd.server.autocomplete.util;
|
||||||
|
import dcd.server.autocomplete.ufcs;
|
||||||
|
|
||||||
import dparse.lexer;
|
import dparse.lexer;
|
||||||
import dparse.rollback_allocator;
|
import dparse.rollback_allocator;
|
||||||
|
@ -324,7 +325,7 @@ in
|
||||||
{
|
{
|
||||||
assert (beforeTokens.length >= 2);
|
assert (beforeTokens.length >= 2);
|
||||||
}
|
}
|
||||||
body
|
do
|
||||||
{
|
{
|
||||||
AutocompleteResponse response;
|
AutocompleteResponse response;
|
||||||
if (beforeTokens.length <= 2)
|
if (beforeTokens.length <= 2)
|
||||||
|
@ -574,6 +575,7 @@ void setCompletions(T)(ref AutocompleteResponse response,
|
||||||
}
|
}
|
||||||
addSymToResponse(symbols[0], response, partial, completionScope);
|
addSymToResponse(symbols[0], response, partial, completionScope);
|
||||||
response.completionType = CompletionType.identifiers;
|
response.completionType = CompletionType.identifiers;
|
||||||
|
lookupUFCS(completionScope, symbols[0], cursorPosition, response);
|
||||||
}
|
}
|
||||||
else if (completionType == CompletionType.calltips)
|
else if (completionType == CompletionType.calltips)
|
||||||
{
|
{
|
||||||
|
@ -669,7 +671,7 @@ in
|
||||||
{
|
{
|
||||||
assert(symbol.kind == CompletionKind.structName);
|
assert(symbol.kind == CompletionKind.structName);
|
||||||
}
|
}
|
||||||
body
|
do
|
||||||
{
|
{
|
||||||
string generatedStructConstructorCalltip = "this(";
|
string generatedStructConstructorCalltip = "this(";
|
||||||
const(DSymbol)*[] fields = symbol.opSlice().filter!(
|
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.scope_;
|
||||||
import dsymbol.string_interning;
|
import dsymbol.string_interning;
|
||||||
import dsymbol.symbol;
|
import dsymbol.symbol;
|
||||||
|
import dcd.server.autocomplete.ufcs;
|
||||||
|
|
||||||
enum ImportKind : ubyte
|
enum ImportKind : ubyte
|
||||||
{
|
{
|
||||||
|
@ -143,8 +144,14 @@ SymbolStuff getSymbolsForCompletion(const AutocompleteRequest request,
|
||||||
ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray,
|
ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray,
|
||||||
rba, request.cursorPosition, moduleCache);
|
rba, request.cursorPosition, moduleCache);
|
||||||
auto expression = getExpression(beforeTokens);
|
auto expression = getExpression(beforeTokens);
|
||||||
return SymbolStuff(getSymbolsByTokenChain(pair.scope_, expression,
|
auto symbols = getSymbolsByTokenChain(pair.scope_, expression,
|
||||||
request.cursorPosition, type), pair.symbol, pair.scope_);
|
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)
|
bool isSliceExpression(T)(T tokens, size_t index)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
identifiers
|
identifiers
|
||||||
a v
|
a v
|
||||||
alignof k
|
alignof k
|
||||||
|
foo f
|
||||||
init k
|
init k
|
||||||
mangleof k
|
mangleof k
|
||||||
sizeof 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