dsymbol deduces ufcs
This commit is contained in:
parent
6c3c67fa2f
commit
e6b94622f0
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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");
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue