UFCS support

This commit is contained in:
Per Nordlöw 2021-10-09 17:31:13 +02:00
parent 02acaa534b
commit 243ae9b524
2 changed files with 204 additions and 5 deletions

View File

@ -502,7 +502,7 @@ void setCompletions(T)(ref AutocompleteResponse response,
Scope* completionScope, T tokens, size_t cursorPosition,
CompletionType completionType, bool isBracket = false, string partial = null)
{
static void addSymToResponse(const(DSymbol)* s, ref AutocompleteResponse r, string p,
static void addSymbolsToResponse(const(DSymbol)* s, ref AutocompleteResponse r, string p,
Scope* completionScope, size_t[] circularGuard = [])
{
if (circularGuard.canFind(cast(size_t) s))
@ -518,7 +518,7 @@ void setCompletions(T)(ref AutocompleteResponse response,
r.completions ~= makeSymbolCompletionInfo(sym, sym.kind);
}
if (sym.kind == CompletionKind.importSymbol && !sym.skipOver && sym.type !is null)
addSymToResponse(sym.type, r, p, completionScope, circularGuard ~ (cast(size_t) s));
addSymbolsToResponse(sym.type, r, p, completionScope, circularGuard ~ (cast(size_t) s));
}
}
@ -577,8 +577,12 @@ void setCompletions(T)(ref AutocompleteResponse response,
if (symbols.length == 0)
return;
}
addSymToResponse(symbols[0], response, partial, completionScope);
addSymbolsToResponse(symbols[0], response, partial, completionScope);
response.completionType = CompletionType.identifiers;
// UFCS completion
foreach (const symbol; getSymbolsForUFCS(completionScope, symbols[0], cursorPosition))
addSymbolsToResponse(symbol, response, partial, completionScope);
}
else if (completionType == CompletionType.calltips)
{

View File

@ -18,6 +18,7 @@
module dcd.server.autocomplete.util;
import std.functional : unaryFun;
import std.algorithm;
import stdx.allocator;
import std.experimental.logger;
@ -336,9 +337,15 @@ DSymbol*[] getSymbolsByTokenChain(T)(Scope* completionScope,
}
//trace("looking for ", tokens[i].text, " in ", symbols[0].name);
symbols = symbols[0].getPartsByName(internString(tokens[i].text));
auto parts = symbols[0].getPartsByName(internString(tokens[i].text));
if (parts.length > 0)
symbols = parts;
else // try UFCS
symbols = getSymbolsForUFCS(completionScope, symbols[0], cursorPosition).filter!(a => a.name == tokens[i].text).array;
//trace("symbols: ", symbols.map!(a => a.name));
filterProperties();
filterProperties(); // TODO: is this needed for UFCS?
if (symbols.length == 0)
{
//trace("Couldn't find it.");
@ -766,3 +773,191 @@ AutocompleteResponse.Completion makeSymbolCompletionInfo(const DSymbol* symbol,
return AutocompleteResponse.Completion(symbol.name, kind, definition,
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
* implicitArg = 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)* implicitArg, size_t cursorPosition)
{
import containers.hashset : HashSet;
assert(implicitArg);
if (implicitArg.name is getBuiltinTypeName(tok!"void")
|| (implicitArg.type !is null
&& implicitArg.type.name is getBuiltinTypeName(tok!"void")))
return null; // no UFCS for void
Scope* scop = completionScope.getScopeByCursor(cursorPosition);
assert(scop);
HashSet!size_t visited;
// local imports only
FilteredAppender!(a => isCallableWithArg(a, implicitArg), DSymbol*[]) app;
while (scop !is null && scop.parent !is null)
{
auto localImports = scop.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);
}
}
scop = scop.parent;
}
// global symbols and global imports
Scope* globalScope = scop;
assert(globalScope !is null);
assert(globalScope.parent is null);
foreach (sym; globalScope.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 isCallableWithArg(symbol.type, 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 isCallableWithArg(symbol.type, 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]);
}