dsymbol deduces ufcs
This commit is contained in:
parent
6c3c67fa2f
commit
e6b94622f0
|
@ -31,6 +31,7 @@ import dsymbol.scope_;
|
||||||
import dsymbol.semantic;
|
import dsymbol.semantic;
|
||||||
import dsymbol.string_interning;
|
import dsymbol.string_interning;
|
||||||
import dsymbol.symbol;
|
import dsymbol.symbol;
|
||||||
|
import dsymbol.ufcs;
|
||||||
import std.algorithm;
|
import std.algorithm;
|
||||||
import std.experimental.allocator;
|
import std.experimental.allocator;
|
||||||
import containers.hashset;
|
import containers.hashset;
|
||||||
|
@ -52,9 +53,11 @@ ScopeSymbolPair generateAutocompleteTrees(const(Token)[] tokens,
|
||||||
|
|
||||||
thirdPass(first.moduleScope, cache, cursorPosition);
|
thirdPass(first.moduleScope, cache, cursorPosition);
|
||||||
|
|
||||||
|
auto ufcsSymbols = getUFCSSymbolsForCursor(first.moduleScope, tokens, cursorPosition);
|
||||||
|
|
||||||
auto r = move(first.rootSymbol.acSymbol);
|
auto r = move(first.rootSymbol.acSymbol);
|
||||||
typeid(SemanticSymbol).destroy(first.rootSymbol);
|
typeid(SemanticSymbol).destroy(first.rootSymbol);
|
||||||
return ScopeSymbolPair(r, move(first.moduleScope));
|
return ScopeSymbolPair(r, move(first.moduleScope), ufcsSymbols);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ScopeSymbolPair
|
struct ScopeSymbolPair
|
||||||
|
@ -67,6 +70,7 @@ struct ScopeSymbolPair
|
||||||
|
|
||||||
DSymbol* symbol;
|
DSymbol* symbol;
|
||||||
Scope* scope_;
|
Scope* scope_;
|
||||||
|
DSymbol*[] ufcsSymbols;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,10 +3,11 @@ module dsymbol.tests;
|
||||||
import std.experimental.allocator;
|
import std.experimental.allocator;
|
||||||
import dparse.ast, dparse.parser, dparse.lexer, dparse.rollback_allocator;
|
import dparse.ast, dparse.parser, dparse.lexer, dparse.rollback_allocator;
|
||||||
import dsymbol.cache_entry, dsymbol.modulecache, dsymbol.symbol;
|
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 dsymbol.semantic, dsymbol.string_interning, dsymbol.builtin.names;
|
||||||
import std.file, std.path, std.format;
|
import std.file, std.path, std.format;
|
||||||
import std.stdio : writeln, stdout;
|
import std.stdio : writeln, stdout;
|
||||||
|
import dsymbol.ufcs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses `source`, caches its symbols and compares the the cache content
|
* Parses `source`, caches its symbols and compares the the cache content
|
||||||
|
@ -413,7 +414,7 @@ unittest
|
||||||
writeln("Running template type parameters tests...");
|
writeln("Running template type parameters tests...");
|
||||||
{
|
{
|
||||||
auto source = q{ struct Foo(T : int){} struct Bar(T : Foo){} };
|
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* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
|
||||||
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
|
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
|
||||||
assert(T2.type.name == "int");
|
assert(T2.type.name == "int");
|
||||||
|
@ -424,7 +425,7 @@ unittest
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto source = q{ struct Foo(T){ }};
|
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"));
|
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
|
||||||
assert(T1);
|
assert(T1);
|
||||||
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
|
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
|
||||||
|
@ -439,7 +440,7 @@ unittest
|
||||||
|
|
||||||
writeln("Running template variadic parameters tests...");
|
writeln("Running template variadic parameters tests...");
|
||||||
auto source = q{ struct Foo(T...){ }};
|
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"));
|
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
|
||||||
assert(T1);
|
assert(T1);
|
||||||
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
|
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
|
||||||
|
@ -528,15 +529,14 @@ unittest
|
||||||
|
|
||||||
writeln("Testing protection scopes");
|
writeln("Testing protection scopes");
|
||||||
auto source = q{version(all) { private: } struct Foo{ }};
|
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"));
|
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
|
||||||
assert(T1);
|
assert(T1);
|
||||||
assert(T1.protection != tok!"private");
|
assert(T1.protection != tok!"private");
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for memory leaks on thread termination (in static constructors)
|
// check for memory leaks on thread termination (in static constructors)
|
||||||
version (linux)
|
version (linux) unittest
|
||||||
unittest
|
|
||||||
{
|
{
|
||||||
import core.memory : GC;
|
import core.memory : GC;
|
||||||
import core.thread : Thread;
|
import core.thread : Thread;
|
||||||
|
@ -584,6 +584,7 @@ static this()
|
||||||
{
|
{
|
||||||
stringCache = StringCache(StringCache.defaultBucketCount);
|
stringCache = StringCache(StringCache.defaultBucketCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ~this()
|
static ~this()
|
||||||
{
|
{
|
||||||
destroy(stringCache);
|
destroy(stringCache);
|
||||||
|
@ -598,6 +599,7 @@ const(Token)[] lex(string source, string filename)
|
||||||
{
|
{
|
||||||
import dparse.lexer : getTokensForParser;
|
import dparse.lexer : getTokensForParser;
|
||||||
import std.string : representation;
|
import std.string : representation;
|
||||||
|
|
||||||
LexerConfig config;
|
LexerConfig config;
|
||||||
config.fileName = filename;
|
config.fileName = filename;
|
||||||
return getTokensForParser(source.dup.representation, config, &stringCache);
|
return getTokensForParser(source.dup.representation, config, &stringCache);
|
||||||
|
@ -626,7 +628,7 @@ ScopeSymbolPair generateAutocompleteTrees(string source, ref ModuleCache cache)
|
||||||
return generateAutocompleteTrees(source, randomDFilename, 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);
|
auto tokens = lex(source);
|
||||||
RollbackAllocator rba;
|
RollbackAllocator rba;
|
||||||
|
@ -635,21 +637,57 @@ ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref Mo
|
||||||
scope first = new FirstPass(m, internString(filename), &cache);
|
scope first = new FirstPass(m, internString(filename), &cache);
|
||||||
first.run();
|
first.run();
|
||||||
|
|
||||||
|
|
||||||
secondPass(first.rootSymbol, first.moduleScope, cache);
|
secondPass(first.rootSymbol, first.moduleScope, cache);
|
||||||
|
thirdPass(first.moduleScope, cache, cursorPosition);
|
||||||
|
auto ufcsSymbols = getUFCSSymbolsForCursor(first.moduleScope, tokens, cursorPosition);
|
||||||
auto r = first.rootSymbol.acSymbol;
|
auto r = first.rootSymbol.acSymbol;
|
||||||
typeid(SemanticSymbol).destroy(first.rootSymbol);
|
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)
|
ScopeSymbolPair generateAutocompleteTreesProd(string source, string filename, size_t cursorPosition, ref ModuleCache cache)
|
||||||
{
|
|
||||||
return generateAutocompleteTrees(source, null, cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopeSymbolPair generateAutocompleteTrees(string source, string filename, size_t cursorPosition, ref ModuleCache cache)
|
|
||||||
{
|
{
|
||||||
auto tokens = lex(source);
|
auto tokens = lex(source);
|
||||||
RollbackAllocator rba;
|
RollbackAllocator rba;
|
||||||
return dsymbol.conversion.generateAutocompleteTrees(
|
return dsymbol.conversion.generateAutocompleteTrees(
|
||||||
tokens, &rba, cursorPosition, cache);
|
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.symbol;
|
||||||
import dsymbol.scope_;
|
import dsymbol.scope_;
|
||||||
|
import dsymbol.builtin.names;
|
||||||
|
import dsymbol.utils;
|
||||||
|
import dparse.lexer : tok, Token;
|
||||||
import std.functional : unaryFun;
|
import std.functional : unaryFun;
|
||||||
import std.algorithm;
|
import std.algorithm;
|
||||||
import std.array;
|
import std.array;
|
||||||
import std.range;
|
import std.range;
|
||||||
import dsymbol.builtin.names;
|
|
||||||
import std.string;
|
import std.string;
|
||||||
import dparse.lexer : tok;
|
|
||||||
import std.regex;
|
import std.regex;
|
||||||
import containers.hashset : HashSet;
|
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
|
// https://dlang.org/spec/type.html#implicit-conversions
|
||||||
enum string[string] INTEGER_PROMOTIONS = [
|
enum string[string] INTEGER_PROMOTIONS = [
|
||||||
|
@ -26,43 +42,107 @@ enum string[string] INTEGER_PROMOTIONS = [
|
||||||
|
|
||||||
enum MAX_RECURSION_DEPTH = 50;
|
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
|
// Check if beforeDotSymbol is null or void
|
||||||
bool isInvalidForUFCSCompletion(const(DSymbol)* beforeDotSymbol)
|
private bool isInvalidForUFCSCompletion(const(DSymbol)* beforeDotSymbol)
|
||||||
{
|
{
|
||||||
return beforeDotSymbol is null
|
return beforeDotSymbol is null
|
||||||
|
|| beforeDotSymbol.kind == CompletionKind.templateName
|
||||||
|| beforeDotSymbol.name is getBuiltinTypeName(tok!"void")
|
|| beforeDotSymbol.name is getBuiltinTypeName(tok!"void")
|
||||||
|| (beforeDotSymbol.type !is null && beforeDotSymbol.type.name is getBuiltinTypeName(
|
|| (beforeDotSymbol.type !is null && beforeDotSymbol.type.name is getBuiltinTypeName(
|
||||||
tok!"void"));
|
tok!"void"));
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Get symbols suitable for UFCS.
|
private TokenCursorResult getCursorToken(const(Token)[] tokens, size_t cursorPosition)
|
||||||
*
|
|
||||||
* 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)
|
|
||||||
{
|
{
|
||||||
if (beforeDotSymbol.isInvalidForUFCSCompletion)
|
auto sortedTokens = assumeSorted(tokens);
|
||||||
{
|
auto sortedBeforeTokens = sortedTokens.lowerBound(cursorPosition);
|
||||||
return null;
|
|
||||||
|
TokenCursorResult tokenCursorResult;
|
||||||
|
|
||||||
|
if (sortedBeforeTokens.empty) {
|
||||||
|
return tokenCursorResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
Scope* currentScope = completionScope.getScopeByCursor(cursorPosition);
|
if (sortedBeforeTokens.length >= 2
|
||||||
assert(currentScope);
|
&& sortedBeforeTokens[$ - 1].type is tok!"."
|
||||||
HashSet!size_t visited;
|
&& 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
|
if (index == size_t.max)
|
||||||
FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol), DSymbol*[]) localAppender;
|
{
|
||||||
|
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)
|
while (currentScope !is null && currentScope.parent !is null)
|
||||||
{
|
{
|
||||||
|
@ -74,18 +154,19 @@ DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSy
|
||||||
if (sym.qualifier == SymbolQualifier.selectiveImport)
|
if (sym.qualifier == SymbolQualifier.selectiveImport)
|
||||||
localAppender.put(sym.type);
|
localAppender.put(sym.type);
|
||||||
else
|
else
|
||||||
sym.type.getParts(internString(null), localAppender, visited);
|
sym.type.getParts(istring(null), localAppender, visited);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentScope = currentScope.parent;
|
currentScope = currentScope.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// global appender
|
if (currentScope is null)
|
||||||
FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol, true), DSymbol*[]) globalAppender;
|
{
|
||||||
|
return;
|
||||||
// global symbols and global imports
|
}
|
||||||
assert(currentScope !is null);
|
assert(currentScope !is null);
|
||||||
assert(currentScope.parent is null);
|
assert(currentScope.parent is null);
|
||||||
|
|
||||||
foreach (sym; currentScope.symbols)
|
foreach (sym; currentScope.symbols)
|
||||||
{
|
{
|
||||||
if (sym.kind != CompletionKind.importSymbol)
|
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;
|
string* found = from in INTEGER_PROMOTIONS;
|
||||||
if (!found)
|
if (!found)
|
||||||
|
@ -114,7 +271,7 @@ bool willImplicitBeUpcasted(string from, string to)
|
||||||
return INTEGER_PROMOTIONS[from] == 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
|
// For now we are only resolving the first alias this symbol
|
||||||
// when multiple alias this are supported, we can rethink another solution
|
// 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`
|
* `true` if `incomingSymbols`' first parameter matches `beforeDotType`
|
||||||
* `false` otherwise
|
* `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
|
if (!incomingSymbol || !beforeDotType
|
||||||
|| (isGlobalScope && incomingSymbol.protection == tok!"private") || recursionDepth > MAX_RECURSION_DEPTH)
|
|| (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
|
if (incomingSymbol.kind == CompletionKind.functionName && !incomingSymbol
|
||||||
.functionParameters.empty)
|
.functionParameters.empty)
|
||||||
{
|
{
|
||||||
return beforeDotType is incomingSymbol.functionParameters.front.type
|
if (beforeDotType is incomingSymbol.functionParameters.front.type
|
||||||
|| willImplicitBeUpcasted(beforeDotType.name, incomingSymbol
|
|| willImplicitBeUpcasted(beforeDotType.name, incomingSymbol
|
||||||
.functionParameters.front.type.name)
|
.functionParameters.front.type.name)
|
||||||
|| matchAliasThis(beforeDotType, incomingSymbol, recursionDepth);
|
|| matchAliasThis(beforeDotType, incomingSymbol, recursionDepth))
|
||||||
|
{
|
||||||
|
// incomingSymbol.kind = CompletionKind.ufcsName;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,29 +353,6 @@ struct FilteredAppender(alias predicate, T:
|
||||||
assert(app.data == [1, 3, 5, 7, 9]);
|
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
|
unittest
|
||||||
{
|
{
|
||||||
assert(!willImplicitBeUpcasted("A", "B"));
|
assert(!willImplicitBeUpcasted("A", "B"));
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
module dsymbol.utils;
|
module dsymbol.utils;
|
||||||
import dparse.lexer : tok, IdType;
|
import dparse.lexer : tok, IdType, Token;
|
||||||
|
|
||||||
enum TYPE_IDENT_CASES = q{
|
enum TYPE_IDENT_CASES = q{
|
||||||
case tok!"int":
|
case tok!"int":
|
||||||
|
|
|
@ -42,6 +42,7 @@ import dsymbol.scope_;
|
||||||
import dsymbol.string_interning;
|
import dsymbol.string_interning;
|
||||||
import dsymbol.symbol;
|
import dsymbol.symbol;
|
||||||
import dsymbol.ufcs;
|
import dsymbol.ufcs;
|
||||||
|
import dsymbol.utils;
|
||||||
|
|
||||||
import dcd.common.constants;
|
import dcd.common.constants;
|
||||||
import dcd.common.messages;
|
import dcd.common.messages;
|
||||||
|
@ -220,6 +221,7 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray,
|
||||||
scope(exit) pair.destroy();
|
scope(exit) pair.destroy();
|
||||||
response.setCompletions(pair.scope_, getExpression(beforeTokens),
|
response.setCompletions(pair.scope_, getExpression(beforeTokens),
|
||||||
cursorPosition, CompletionType.identifiers, false, partial);
|
cursorPosition, CompletionType.identifiers, false, partial);
|
||||||
|
response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, CompletionKind.ufcsName)).array;
|
||||||
break;
|
break;
|
||||||
// these tokens before a "." mean "Module Scope Operator"
|
// these tokens before a "." mean "Module Scope Operator"
|
||||||
case tok!":":
|
case tok!":":
|
||||||
|
@ -306,6 +308,8 @@ AutocompleteResponse parenCompletion(T)(T beforeTokens,
|
||||||
auto expression = getExpression(beforeTokens[0 .. $ - 1]);
|
auto expression = getExpression(beforeTokens[0 .. $ - 1]);
|
||||||
response.setCompletions(pair.scope_, expression,
|
response.setCompletions(pair.scope_, expression,
|
||||||
cursorPosition, CompletionType.calltips, beforeTokens[$ - 1] == tok!"[");
|
cursorPosition, CompletionType.calltips, beforeTokens[$ - 1] == tok!"[");
|
||||||
|
response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, char.init)).array;
|
||||||
|
response.completionType = CompletionType.calltips;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -559,9 +563,8 @@ void setCompletions(T)(ref AutocompleteResponse response,
|
||||||
cursorPosition, completionType);
|
cursorPosition, completionType);
|
||||||
|
|
||||||
if (tokens.length > 2 && tokens[1] == tok!".")
|
if (tokens.length > 2 && tokens[1] == tok!".")
|
||||||
{
|
{
|
||||||
symbols.getUFCSParenCompletion(completionScope, stringToken(tokens[0]), stringToken(
|
|
||||||
tokens[2]), cursorPosition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (symbols.length == 0)
|
if (symbols.length == 0)
|
||||||
|
@ -581,7 +584,6 @@ 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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -38,6 +38,7 @@ import dsymbol.scope_;
|
||||||
import dsymbol.string_interning;
|
import dsymbol.string_interning;
|
||||||
import dsymbol.symbol;
|
import dsymbol.symbol;
|
||||||
import dsymbol.ufcs;
|
import dsymbol.ufcs;
|
||||||
|
import dsymbol.utils;
|
||||||
|
|
||||||
enum ImportKind : ubyte
|
enum ImportKind : ubyte
|
||||||
{
|
{
|
||||||
|
@ -420,43 +421,6 @@ DSymbol*[] getSymbolsByTokenChain(T)(Scope* completionScope,
|
||||||
return symbols;
|
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;
|
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)
|
AutocompleteResponse.Completion makeSymbolCompletionInfo(const DSymbol* symbol, char kind)
|
||||||
{
|
{
|
||||||
string definition;
|
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;
|
definition = symbol.type.name ~ ' ' ~ symbol.name;
|
||||||
else if (kind == CompletionKind.enumMember)
|
else if (kind == CompletionKind.enumMember)
|
||||||
definition = symbol.name; // TODO: add enum value to definition string
|
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);
|
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)
|
bool doUFCSSearch(string beforeToken, string lastToken)
|
||||||
{
|
{
|
||||||
// we do the search if they are different from eachother
|
// we do the search if they are different from eachother
|
||||||
|
|
Loading…
Reference in New Issue