dsymbol deduces ufcs

This commit is contained in:
davu 2023-03-13 01:25:12 +01:00 committed by Jan Jurzitza
parent 6c3c67fa2f
commit e6b94622f0
6 changed files with 268 additions and 250 deletions

View File

@ -31,6 +31,7 @@ import dsymbol.scope_;
import dsymbol.semantic;
import dsymbol.string_interning;
import dsymbol.symbol;
import dsymbol.ufcs;
import std.algorithm;
import std.experimental.allocator;
import containers.hashset;
@ -52,9 +53,11 @@ ScopeSymbolPair generateAutocompleteTrees(const(Token)[] tokens,
thirdPass(first.moduleScope, cache, cursorPosition);
auto ufcsSymbols = getUFCSSymbolsForCursor(first.moduleScope, tokens, cursorPosition);
auto r = move(first.rootSymbol.acSymbol);
typeid(SemanticSymbol).destroy(first.rootSymbol);
return ScopeSymbolPair(r, move(first.moduleScope));
return ScopeSymbolPair(r, move(first.moduleScope), ufcsSymbols);
}
struct ScopeSymbolPair
@ -67,6 +70,7 @@ struct ScopeSymbolPair
DSymbol* symbol;
Scope* scope_;
DSymbol*[] ufcsSymbols;
}
/**

View File

@ -3,10 +3,11 @@ module dsymbol.tests;
import std.experimental.allocator;
import dparse.ast, dparse.parser, dparse.lexer, dparse.rollback_allocator;
import dsymbol.cache_entry, dsymbol.modulecache, dsymbol.symbol;
import dsymbol.conversion, dsymbol.conversion.first, dsymbol.conversion.second;
import dsymbol.conversion, dsymbol.conversion.first, dsymbol.conversion.second, dsymbol.conversion.third;
import dsymbol.semantic, dsymbol.string_interning, dsymbol.builtin.names;
import std.file, std.path, std.format;
import std.stdio : writeln, stdout;
import dsymbol.ufcs;
/**
* Parses `source`, caches its symbols and compares the the cache content
@ -413,7 +414,7 @@ unittest
writeln("Running template type parameters tests...");
{
auto source = q{ struct Foo(T : int){} struct Bar(T : Foo){} };
auto pair = generateAutocompleteTrees(source, "", 0, cache);
auto pair = generateAutocompleteTreesProd(source, "", 0, cache);
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
assert(T2.type.name == "int");
@ -424,7 +425,7 @@ unittest
}
{
auto source = q{ struct Foo(T){ }};
auto pair = generateAutocompleteTrees(source, "", 0, cache);
auto pair = generateAutocompleteTreesProd(source, "", 0, cache);
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
assert(T1);
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
@ -439,7 +440,7 @@ unittest
writeln("Running template variadic parameters tests...");
auto source = q{ struct Foo(T...){ }};
auto pair = generateAutocompleteTrees(source, "", 0, cache);
auto pair = generateAutocompleteTreesProd(source, "", 0, cache);
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
assert(T1);
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
@ -528,15 +529,14 @@ unittest
writeln("Testing protection scopes");
auto source = q{version(all) { private: } struct Foo{ }};
auto pair = generateAutocompleteTrees(source, "", 0, cache);
auto pair = generateAutocompleteTreesProd(source, "", 0, cache);
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
assert(T1);
assert(T1.protection != tok!"private");
}
// check for memory leaks on thread termination (in static constructors)
version (linux)
unittest
version (linux) unittest
{
import core.memory : GC;
import core.thread : Thread;
@ -584,6 +584,7 @@ static this()
{
stringCache = StringCache(StringCache.defaultBucketCount);
}
static ~this()
{
destroy(stringCache);
@ -598,6 +599,7 @@ const(Token)[] lex(string source, string filename)
{
import dparse.lexer : getTokensForParser;
import std.string : representation;
LexerConfig config;
config.fileName = filename;
return getTokensForParser(source.dup.representation, config, &stringCache);
@ -626,7 +628,7 @@ ScopeSymbolPair generateAutocompleteTrees(string source, ref ModuleCache cache)
return generateAutocompleteTrees(source, randomDFilename, cache);
}
ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref ModuleCache cache)
ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref ModuleCache cache, size_t cursorPosition = -1)
{
auto tokens = lex(source);
RollbackAllocator rba;
@ -635,21 +637,57 @@ ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref Mo
scope first = new FirstPass(m, internString(filename), &cache);
first.run();
secondPass(first.rootSymbol, first.moduleScope, cache);
thirdPass(first.moduleScope, cache, cursorPosition);
auto ufcsSymbols = getUFCSSymbolsForCursor(first.moduleScope, tokens, cursorPosition);
auto r = first.rootSymbol.acSymbol;
typeid(SemanticSymbol).destroy(first.rootSymbol);
return ScopeSymbolPair(r, first.moduleScope);
return ScopeSymbolPair(r, first.moduleScope, ufcsSymbols);
}
ScopeSymbolPair generateAutocompleteTrees(string source, size_t cursorPosition, ref ModuleCache cache)
{
return generateAutocompleteTrees(source, null, cache);
}
ScopeSymbolPair generateAutocompleteTrees(string source, string filename, size_t cursorPosition, ref ModuleCache cache)
ScopeSymbolPair generateAutocompleteTreesProd(string source, string filename, size_t cursorPosition, ref ModuleCache cache)
{
auto tokens = lex(source);
RollbackAllocator rba;
return dsymbol.conversion.generateAutocompleteTrees(
tokens, &rba, cursorPosition, cache);
}
version (linux)
{
import std.string;
enum string ufcsExampleCode =
q{class Incrementer
{
int run(int x)
{
return x++;
}
}
int increment(int x)
{
return x++;
}
void doIncrement()
{
int life = 42;
life.
}};
unittest
{
import dsymbol.ufcs;
writeln("Getting UFCS Symbols For life");
ModuleCache cache;
// position of variable life
size_t cursorPos = 139;
auto pair = generateAutocompleteTreesProd(ufcsExampleCode, randomDFilename, cursorPos, cache);
assert(pair.ufcsSymbols.length > 0);
assert(pair.ufcsSymbols[0].name == "increment");
}
}

View File

@ -2,15 +2,31 @@ module dsymbol.ufcs;
import dsymbol.symbol;
import dsymbol.scope_;
import dsymbol.builtin.names;
import dsymbol.utils;
import dparse.lexer : tok, Token;
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 containers.hashset : HashSet;
import std.experimental.logger;
enum UFCSCompletionContext
{
DotCompletion,
ParenCompletion,
UnknownCompletion
}
struct TokenCursorResult
{
UFCSCompletionContext completionContext = UFCSCompletionContext.UnknownCompletion;
istring functionName;
istring symbolIdentifierName;
}
// https://dlang.org/spec/type.html#implicit-conversions
enum string[string] INTEGER_PROMOTIONS = [
@ -26,43 +42,107 @@ enum string[string] INTEGER_PROMOTIONS = [
enum MAX_RECURSION_DEPTH = 50;
private DSymbol* deduceSymbolType(DSymbol* symbol)
{
DSymbol* symbolType = symbol.type;
while (symbolType !is null && (symbolType.qualifier == SymbolQualifier.func
|| symbolType.kind == CompletionKind.functionName
|| symbolType.kind == CompletionKind.importSymbol
|| symbolType.kind == CompletionKind.aliasName))
{
if (symbolType.type is null || symbolType.type is symbolType)
{
break;
}
//look at next type to deduce
symbolType = symbolType.type;
}
return symbolType;
}
// Check if beforeDotSymbol is null or void
bool isInvalidForUFCSCompletion(const(DSymbol)* beforeDotSymbol)
private bool isInvalidForUFCSCompletion(const(DSymbol)* beforeDotSymbol)
{
return beforeDotSymbol is null
|| beforeDotSymbol.kind == CompletionKind.templateName
|| beforeDotSymbol.name is getBuiltinTypeName(tok!"void")
|| (beforeDotSymbol.type !is null && beforeDotSymbol.type.name is getBuiltinTypeName(
tok!"void"));
}
/**
* 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 beforeDotSymbol) 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)
private TokenCursorResult getCursorToken(const(Token)[] tokens, size_t cursorPosition)
{
if (beforeDotSymbol.isInvalidForUFCSCompletion)
{
return null;
auto sortedTokens = assumeSorted(tokens);
auto sortedBeforeTokens = sortedTokens.lowerBound(cursorPosition);
TokenCursorResult tokenCursorResult;
if (sortedBeforeTokens.empty) {
return tokenCursorResult;
}
Scope* currentScope = completionScope.getScopeByCursor(cursorPosition);
assert(currentScope);
HashSet!size_t visited;
if (sortedBeforeTokens.length >= 2
&& sortedBeforeTokens[$ - 1].type is tok!"."
&& sortedBeforeTokens[$ - 2].type is tok!"identifier")
{
// Check if it's UFCS dot completion
tokenCursorResult.completionContext = UFCSCompletionContext.DotCompletion;
tokenCursorResult.symbolIdentifierName = istring(sortedBeforeTokens[$ - 2].text);
return tokenCursorResult;
}
else
{
// Check if it's UFCS paren completion
size_t index = goBackToOpenParen(sortedBeforeTokens);
// local appender
FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol), DSymbol*[]) localAppender;
if (index == size_t.max)
{
return tokenCursorResult;
}
auto slicedAtParen = sortedBeforeTokens[0 .. index];
if (slicedAtParen.length >= 4
&& slicedAtParen[$ - 4].type is tok!"identifier"
&& slicedAtParen[$ - 3].type is tok!"."
&& slicedAtParen[$ - 2].type is tok!"identifier"
&& slicedAtParen[$ - 1].type is tok!"(")
{
tokenCursorResult.completionContext = UFCSCompletionContext.ParenCompletion;
tokenCursorResult.symbolIdentifierName = istring(slicedAtParen[$ - 4].text);
tokenCursorResult.functionName = istring(slicedAtParen[$ - 2].text);
return tokenCursorResult;
}
}
// if none then it's unknown
return tokenCursorResult;
}
private void getUFCSSymbols(T, Y)(ref T localAppender, ref Y globalAppender, Scope* completionScope, size_t cursorPosition)
{
Scope* currentScope = completionScope.getScopeByCursor(cursorPosition);
if (currentScope is null)
{
return;
}
DSymbol*[] cursorSymbols = currentScope.getSymbolsInCursorScope(cursorPosition);
if (cursorSymbols.empty)
{
return;
}
auto filteredSymbols = cursorSymbols.filter!(s => s.kind == CompletionKind.functionName).array;
foreach (DSymbol* sym; filteredSymbols)
{
globalAppender.put(sym);
}
HashSet!size_t visited;
while (currentScope !is null && currentScope.parent !is null)
{
@ -74,18 +154,19 @@ DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSy
if (sym.qualifier == SymbolQualifier.selectiveImport)
localAppender.put(sym.type);
else
sym.type.getParts(internString(null), localAppender, visited);
sym.type.getParts(istring(null), localAppender, visited);
}
currentScope = currentScope.parent;
}
// global appender
FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol, true), DSymbol*[]) globalAppender;
// global symbols and global imports
if (currentScope is null)
{
return;
}
assert(currentScope !is null);
assert(currentScope.parent is null);
foreach (sym; currentScope.symbols)
{
if (sym.kind != CompletionKind.importSymbol)
@ -100,10 +181,86 @@ DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSy
}
}
}
return localAppender.opSlice ~ globalAppender.opSlice;
}
bool willImplicitBeUpcasted(string from, string to)
DSymbol*[] getUFCSSymbolsForCursor(Scope* completionScope, ref const(Token)[] tokens, size_t cursorPosition)
{
DSymbol* cursorSymbol;
DSymbol* cursorSymbolType;
TokenCursorResult tokenCursorResult = getCursorToken(tokens, cursorPosition);
if (tokenCursorResult.completionContext is UFCSCompletionContext.UnknownCompletion)
{
trace("Is not a valid UFCS completion");
return [];
}
cursorSymbol = completionScope.getFirstSymbolByNameAndCursor(
tokenCursorResult.symbolIdentifierName, cursorPosition);
if (cursorSymbol is null)
{
warning("Coudn't find symbol ", tokenCursorResult.symbolIdentifierName);
return [];
}
if (cursorSymbol.isInvalidForUFCSCompletion)
{
trace("CursorSymbol is invalid");
return [];
}
cursorSymbolType = deduceSymbolType(cursorSymbol);
if (cursorSymbolType is null)
{
return [];
}
if (cursorSymbolType.isInvalidForUFCSCompletion)
{
trace("CursorSymbolType isn't valid for UFCS completion");
return [];
}
if (tokenCursorResult.completionContext == UFCSCompletionContext.ParenCompletion)
{
return getUFCSSymbolsForParenCompletion(cursorSymbolType, completionScope, tokenCursorResult.functionName, cursorPosition);
}
else
{
return getUFCSSymbolsForDotCompletion(cursorSymbolType, completionScope, cursorPosition);
}
}
private DSymbol*[] getUFCSSymbolsForDotCompletion(DSymbol* symbolType, Scope* completionScope, size_t cursorPosition)
{
// local appender
FilteredAppender!(a => a.isCallableWithArg(symbolType), DSymbol*[]) localAppender;
// global appender
FilteredAppender!(a => a.isCallableWithArg(symbolType, true), DSymbol*[]) globalAppender;
getUFCSSymbols(localAppender, globalAppender, completionScope, cursorPosition);
return localAppender.data ~ globalAppender.data;
}
DSymbol*[] getUFCSSymbolsForParenCompletion(DSymbol* symbolType, Scope* completionScope, istring searchWord, size_t cursorPosition)
{
// local appender
FilteredAppender!(a => a.isCallableWithArg(symbolType) && a.name.among(searchWord), DSymbol*[]) localAppender;
// global appender
FilteredAppender!(a => a.isCallableWithArg(symbolType, true) && a.name.among(searchWord), DSymbol*[]) globalAppender;
getUFCSSymbols(localAppender, globalAppender, completionScope, cursorPosition);
return localAppender.data ~ globalAppender.data;
}
private bool willImplicitBeUpcasted(string from, string to)
{
string* found = from in INTEGER_PROMOTIONS;
if (!found)
@ -114,7 +271,7 @@ bool willImplicitBeUpcasted(string from, string to)
return INTEGER_PROMOTIONS[from] == to;
}
bool matchAliasThis(const(DSymbol)* beforeDotType, const(DSymbol)* incomingSymbol, int recursionDepth)
private bool matchAliasThis(const(DSymbol)* beforeDotType, DSymbol* incomingSymbol, int recursionDepth)
{
// For now we are only resolving the first alias this symbol
// when multiple alias this are supported, we can rethink another solution
@ -138,7 +295,7 @@ bool matchAliasThis(const(DSymbol)* beforeDotType, const(DSymbol)* incomingSymbo
* `true` if `incomingSymbols`' first parameter matches `beforeDotType`
* `false` otherwise
*/
bool isCallableWithArg(const(DSymbol)* incomingSymbol, const(DSymbol)* beforeDotType, bool isGlobalScope = false, int recursionDepth = 0)
bool isCallableWithArg(DSymbol* incomingSymbol, const(DSymbol)* beforeDotType, bool isGlobalScope = false, int recursionDepth = 0)
{
if (!incomingSymbol || !beforeDotType
|| (isGlobalScope && incomingSymbol.protection == tok!"private") || recursionDepth > MAX_RECURSION_DEPTH)
@ -149,13 +306,16 @@ bool isCallableWithArg(const(DSymbol)* incomingSymbol, const(DSymbol)* beforeDot
if (incomingSymbol.kind == CompletionKind.functionName && !incomingSymbol
.functionParameters.empty)
{
return beforeDotType is incomingSymbol.functionParameters.front.type
if (beforeDotType is incomingSymbol.functionParameters.front.type
|| willImplicitBeUpcasted(beforeDotType.name, incomingSymbol
.functionParameters.front.type.name)
|| matchAliasThis(beforeDotType, incomingSymbol, recursionDepth);
.functionParameters.front.type.name)
|| matchAliasThis(beforeDotType, incomingSymbol, recursionDepth))
{
// incomingSymbol.kind = CompletionKind.ufcsName;
return true;
}
}
return false;
}
@ -193,29 +353,6 @@ struct FilteredAppender(alias predicate, T:
assert(app.data == [1, 3, 5, 7, 9]);
}
void getUFCSParenCompletion(ref DSymbol*[] symbols, Scope* completionScope, istring firstToken, istring nextToken, size_t cursorPosition)
{
DSymbol* firstSymbol = completionScope.getFirstSymbolByNameAndCursor(
firstToken, cursorPosition);
if (firstSymbol is null)
return;
DSymbol*[] possibleUFCSSymbol = completionScope.getSymbolsByNameAndCursor(
nextToken, cursorPosition);
foreach (nextSymbol; possibleUFCSSymbol)
{
if (nextSymbol && nextSymbol.functionParameters)
{
if (nextSymbol.isCallableWithArg(firstSymbol.type))
{
nextSymbol.kind = CompletionKind.ufcsName;
symbols ~= nextSymbol;
}
}
}
}
unittest
{
assert(!willImplicitBeUpcasted("A", "B"));

View File

@ -1,5 +1,5 @@
module dsymbol.utils;
import dparse.lexer : tok, IdType;
import dparse.lexer : tok, IdType, Token;
enum TYPE_IDENT_CASES = q{
case tok!"int":

View File

@ -42,6 +42,7 @@ import dsymbol.scope_;
import dsymbol.string_interning;
import dsymbol.symbol;
import dsymbol.ufcs;
import dsymbol.utils;
import dcd.common.constants;
import dcd.common.messages;
@ -220,6 +221,7 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray,
scope(exit) pair.destroy();
response.setCompletions(pair.scope_, getExpression(beforeTokens),
cursorPosition, CompletionType.identifiers, false, partial);
response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, CompletionKind.ufcsName)).array;
break;
// these tokens before a "." mean "Module Scope Operator"
case tok!":":
@ -306,6 +308,8 @@ AutocompleteResponse parenCompletion(T)(T beforeTokens,
auto expression = getExpression(beforeTokens[0 .. $ - 1]);
response.setCompletions(pair.scope_, expression,
cursorPosition, CompletionType.calltips, beforeTokens[$ - 1] == tok!"[");
response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, char.init)).array;
response.completionType = CompletionType.calltips;
break;
default:
break;
@ -559,9 +563,8 @@ void setCompletions(T)(ref AutocompleteResponse response,
cursorPosition, completionType);
if (tokens.length > 2 && tokens[1] == tok!".")
{
symbols.getUFCSParenCompletion(completionScope, stringToken(tokens[0]), stringToken(
tokens[2]), cursorPosition);
{
}
if (symbols.length == 0)
@ -581,7 +584,6 @@ 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)
{

View File

@ -38,6 +38,7 @@ import dsymbol.scope_;
import dsymbol.string_interning;
import dsymbol.symbol;
import dsymbol.ufcs;
import dsymbol.utils;
enum ImportKind : ubyte
{
@ -420,43 +421,6 @@ DSymbol*[] getSymbolsByTokenChain(T)(Scope* completionScope,
return symbols;
}
enum TYPE_IDENT_CASES = q{
case tok!"int":
case tok!"uint":
case tok!"long":
case tok!"ulong":
case tok!"char":
case tok!"wchar":
case tok!"dchar":
case tok!"bool":
case tok!"byte":
case tok!"ubyte":
case tok!"short":
case tok!"ushort":
case tok!"cent":
case tok!"ucent":
case tok!"float":
case tok!"ifloat":
case tok!"cfloat":
case tok!"idouble":
case tok!"cdouble":
case tok!"double":
case tok!"real":
case tok!"ireal":
case tok!"creal":
case tok!"this":
case tok!"super":
case tok!"identifier":
};
enum STRING_LITERAL_CASES = q{
case tok!"stringLiteral":
case tok!"wstringLiteral":
case tok!"dstringLiteral":
};
enum TYPE_IDENT_AND_LITERAL_CASES = TYPE_IDENT_CASES ~ STRING_LITERAL_CASES;
/**
*
*/
@ -647,122 +611,10 @@ bool isUdaExpression(T)(ref T tokens)
return result;
}
/**
* Traverses a token slice in reverse to find the opening parentheses or square bracket
* that begins the block the last token is in.
*/
size_t goBackToOpenParen(T)(T beforeTokens)
in
{
assert (beforeTokens.length > 0);
}
do
{
size_t i = beforeTokens.length - 1;
while (true) switch (beforeTokens[i].type)
{
case tok!",":
case tok!".":
case tok!"*":
case tok!"&":
case tok!"doubleLiteral":
case tok!"floatLiteral":
case tok!"idoubleLiteral":
case tok!"ifloatLiteral":
case tok!"intLiteral":
case tok!"longLiteral":
case tok!"realLiteral":
case tok!"irealLiteral":
case tok!"uintLiteral":
case tok!"ulongLiteral":
case tok!"characterLiteral":
mixin(TYPE_IDENT_AND_LITERAL_CASES);
if (i == 0)
return size_t.max;
else
i--;
break;
case tok!"(":
case tok!"[":
return i + 1;
case tok!")":
i = beforeTokens.skipParenReverseBefore(i, tok!")", tok!"(");
break;
case tok!"}":
i = beforeTokens.skipParenReverseBefore(i, tok!"}", tok!"{");
break;
case tok!"]":
i = beforeTokens.skipParenReverseBefore(i, tok!"]", tok!"[");
break;
default:
return size_t.max;
}
}
/**
* Skips blocks of parentheses until the starting block has been closed
*/
void skipParen(T)(T tokenSlice, ref size_t i, IdType open, IdType close)
{
if (i >= tokenSlice.length || tokenSlice.length <= 0)
return;
int depth = 1;
while (depth != 0 && i + 1 != tokenSlice.length)
{
i++;
if (tokenSlice[i].type == open)
depth++;
else if (tokenSlice[i].type == close)
depth--;
}
}
/**
* Skips blocks of parentheses in reverse until the starting block has been opened
*/
size_t skipParenReverse(T)(T beforeTokens, size_t i, IdType open, IdType close)
{
if (i == 0)
return 0;
int depth = 1;
while (depth != 0 && i != 0)
{
i--;
if (beforeTokens[i].type == open)
depth++;
else if (beforeTokens[i].type == close)
depth--;
}
return i;
}
size_t skipParenReverseBefore(T)(T beforeTokens, size_t i, IdType open, IdType close)
{
i = skipParenReverse(beforeTokens, i, open, close);
if (i != 0)
i--;
return i;
}
///
unittest
{
Token[] t = [
Token(tok!"identifier"), Token(tok!"identifier"), Token(tok!"("),
Token(tok!"identifier"), Token(tok!"("), Token(tok!")"), Token(tok!",")
];
size_t i = t.length - 1;
i = skipParenReverse(t, i, tok!")", tok!"(");
assert(i == 2);
i = t.length - 1;
i = skipParenReverseBefore(t, i, tok!")", tok!"(");
assert(i == 1);
}
AutocompleteResponse.Completion makeSymbolCompletionInfo(const DSymbol* symbol, char kind)
{
string definition;
if ((kind == CompletionKind.variableName || kind == CompletionKind.memberVariableName) && symbol.type)
if ((kind == CompletionKind.variableName || kind == CompletionKind.ufcsName || kind == CompletionKind.memberVariableName) && symbol.type)
definition = symbol.type.name ~ ' ' ~ symbol.name;
else if (kind == CompletionKind.enumMember)
definition = symbol.name; // TODO: add enum value to definition string
@ -773,21 +625,6 @@ AutocompleteResponse.Completion makeSymbolCompletionInfo(const DSymbol* symbol,
symbol.symbolFile, symbol.location, symbol.doc);
}
void lookupUFCS(Scope* completionScope, DSymbol* beforeDotSymbol, size_t cursorPosition, ref AutocompleteResponse response)
{
// UFCS completion
DSymbol*[] ufcsSymbols = getSymbolsForUFCS(completionScope, beforeDotSymbol, cursorPosition);
response.completions ~= map!(s => createCompletionForUFCS(s))(ufcsSymbols).array;
}
AutocompleteResponse.Completion createCompletionForUFCS(const DSymbol* symbol)
{
return AutocompleteResponse.Completion(symbol.name, CompletionKind.ufcsName, symbol.callTip, symbol
.symbolFile, symbol
.location, symbol
.doc);
}
bool doUFCSSearch(string beforeToken, string lastToken)
{
// we do the search if they are different from eachother