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:
davu 2022-10-02 10:54:20 +02:00 committed by Jan Jurzitza
parent 2d9084ff77
commit 9e60ab8b2c
12 changed files with 437 additions and 5 deletions

View File

@ -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)");
}

View File

@ -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!(

View File

@ -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;
}

View File

@ -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)

View File

@ -1,6 +1,7 @@
identifiers
a v
alignof k
foo f
init k
mangleof k
sizeof k

View File

@ -0,0 +1,4 @@
module barutils;
void ufcsBar(Foo foo, string message)
{
}

View File

@ -0,0 +1,7 @@
import barutils;
void main()
{
auto foo = Foo();
foo.ufcsBar;
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,10 @@
//import foodata;
import fooutils;
void hasArgname(Foo f){
}
void main()
{
auto foo = Foo();
foo.
}

View File

@ -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) {}

View File

@ -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