diff --git a/dsymbol/src/dsymbol/conversion/package.d b/dsymbol/src/dsymbol/conversion/package.d index e145cdca..d2774af4 100644 --- a/dsymbol/src/dsymbol/conversion/package.d +++ b/dsymbol/src/dsymbol/conversion/package.d @@ -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 @@ -63,10 +66,13 @@ struct ScopeSymbolPair { typeid(DSymbol).destroy(symbol); typeid(Scope).destroy(scope_); + // don't destroy ufcsSymbols contents since we don't own the values + // array itself is GC-allocated, so we just let it live } DSymbol* symbol; Scope* scope_; + DSymbol*[] ufcsSymbols; } /** diff --git a/dsymbol/src/dsymbol/tests.d b/dsymbol/src/dsymbol/tests.d index 1f8a946f..ffb6e353 100644 --- a/dsymbol/src/dsymbol/tests.d +++ b/dsymbol/src/dsymbol/tests.d @@ -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,82 @@ 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) +{ + 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"); + + } + +enum string ufcsTemplateExampleCode = +q{int increment(T)(T x) +{ + return x++; +} +void doIncrement() +{ + int life = 42; + life. +}}; + +unittest + { + import dsymbol.ufcs; + + writeln("Getting Templated UFCS Symbols For life"); + ModuleCache cache; + // position of variable life + size_t cursorPos = 82; + auto pair = generateAutocompleteTreesProd(ufcsTemplateExampleCode, randomDFilename, cursorPos, cache); + assert(pair.ufcsSymbols.length > 0); + assert(pair.ufcsSymbols[0].name == "increment"); + + } + +} diff --git a/dsymbol/src/dsymbol/ufcs.d b/dsymbol/src/dsymbol/ufcs.d index 68092905..12a49c1d 100644 --- a/dsymbol/src/dsymbol/ufcs.d +++ b/dsymbol/src/dsymbol/ufcs.d @@ -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 CompletionContext +{ + UnknownCompletion, + DotCompletion, + ParenCompletion, +} + +struct TokenCursorResult +{ + CompletionContext completionContext; + istring functionName; + istring symbolIdentifierName; +} // https://dlang.org/spec/type.html#implicit-conversions enum string[string] INTEGER_PROMOTIONS = [ @@ -26,43 +42,92 @@ 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.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) + auto sortedTokens = assumeSorted(tokens); + auto sortedBeforeTokens = sortedTokens.lowerBound(cursorPosition); + + TokenCursorResult tokenCursorResult; + + if (sortedBeforeTokens.empty) { + return tokenCursorResult; + } + + if (sortedBeforeTokens.length >= 2 + && sortedBeforeTokens[$ - 1].type is tok!"." + && sortedBeforeTokens[$ - 2].type is tok!"identifier") { - return null; + // Check if it's UFCS dot completion + tokenCursorResult.completionContext = CompletionContext.DotCompletion; + tokenCursorResult.symbolIdentifierName = istring(sortedBeforeTokens[$ - 2].text); + return tokenCursorResult; } + else + { + // Check if it's UFCS paren completion + size_t index = goBackToOpenParen(sortedBeforeTokens); + + 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 = CompletionContext.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); - assert(currentScope); - HashSet!size_t visited; + if (currentScope is null) + { + return; + } - // local appender - FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol), DSymbol*[]) localAppender; + HashSet!size_t visited; while (currentScope !is null && currentScope.parent !is null) { @@ -74,18 +139,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,21 +166,105 @@ 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) { - string* found = from in INTEGER_PROMOTIONS; - if (!found) + DSymbol* cursorSymbol; + DSymbol* cursorSymbolType; + + TokenCursorResult tokenCursorResult = getCursorToken(tokens, cursorPosition); + + if (tokenCursorResult.completionContext is CompletionContext.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 for UFCS"); + return []; + } + + cursorSymbolType = deduceSymbolType(cursorSymbol); + + if (cursorSymbolType is null) + { + return []; + } + + if (cursorSymbolType.isInvalidForUFCSCompletion) + { + trace("CursorSymbolType isn't valid for UFCS completion"); + return []; + } + + if (tokenCursorResult.completionContext == CompletionContext.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; +} + +private 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(const(DSymbol)* from, const(DSymbol)* to) +{ + if (from is null || to is null || to.functionParameters.empty || to.functionParameters.front.type is null) { return false; } - return INTEGER_PROMOTIONS[from] == to; + string fromTypeName = from.name.data; + string toTypeName = to.functionParameters.front.type.name.data; + + return typeWillBeUpcastedTo(fromTypeName, toTypeName); } -bool matchAliasThis(const(DSymbol)* beforeDotType, const(DSymbol)* incomingSymbol, int recursionDepth) +private bool typeWillBeUpcastedTo(string from, string to) { + if (auto promotionType = from in INTEGER_PROMOTIONS) + return *promotionType == to; + + return false; +} + +private bool matchAliasThis(const(DSymbol)* beforeDotType, const(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 @@ -129,6 +279,10 @@ bool matchAliasThis(const(DSymbol)* beforeDotType, const(DSymbol)* incomingSymbo return isCallableWithArg(incomingSymbol, beforeDotType.aliasThisSymbols.front.type, false, recursionDepth); } +bool isNonConstrainedTemplate(const(DSymbol)* incomingSymbol){ + return incomingSymbol.functionParameters.front.type !is null && incomingSymbol.functionParameters.front.type.kind is CompletionKind.typeTmpParam; +} + /** * Params: * incomingSymbol = the function symbol to check if it is valid for UFCS with `beforeDotType`. @@ -140,22 +294,22 @@ bool matchAliasThis(const(DSymbol)* beforeDotType, const(DSymbol)* incomingSymbo */ bool isCallableWithArg(const(DSymbol)* incomingSymbol, const(DSymbol)* beforeDotType, bool isGlobalScope = false, int recursionDepth = 0) { - if (!incomingSymbol || !beforeDotType - || (isGlobalScope && incomingSymbol.protection == tok!"private") || recursionDepth > MAX_RECURSION_DEPTH) + if (incomingSymbol is null + || beforeDotType is null + || isGlobalScope && incomingSymbol.protection is tok!"private" // don't show private functions if we are in global scope + || recursionDepth > MAX_RECURSION_DEPTH) { return false; } - if (incomingSymbol.kind == CompletionKind.functionName && !incomingSymbol - .functionParameters.empty) + if (incomingSymbol.kind is CompletionKind.functionName && !incomingSymbol.functionParameters.empty && incomingSymbol.functionParameters.front.type) { return beforeDotType is incomingSymbol.functionParameters.front.type - || willImplicitBeUpcasted(beforeDotType.name, incomingSymbol - .functionParameters.front.type.name) + || isNonConstrainedTemplate(incomingSymbol) + || willImplicitBeUpcasted(beforeDotType, incomingSymbol) || matchAliasThis(beforeDotType, incomingSymbol, recursionDepth); } - return false; } @@ -193,31 +347,8 @@ 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")); - assert(willImplicitBeUpcasted("bool", "int")); + assert(!typeWillBeUpcastedTo("A", "B")); + assert(typeWillBeUpcastedTo("bool", "int")); } diff --git a/dsymbol/src/dsymbol/utils.d b/dsymbol/src/dsymbol/utils.d new file mode 100644 index 00000000..b2c88d8d --- /dev/null +++ b/dsymbol/src/dsymbol/utils.d @@ -0,0 +1,155 @@ +module dsymbol.utils; +import dparse.lexer : tok, IdType, Token; + +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; + +/** + * 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; +} + + +/** + * 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; + } +} + +///Testing skipping +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); +} diff --git a/src/dcd/server/autocomplete/complete.d b/src/dcd/server/autocomplete/complete.d index aa86763b..17b8fb3f 100644 --- a/src/dcd/server/autocomplete/complete.d +++ b/src/dcd/server/autocomplete/complete.d @@ -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,11 @@ AutocompleteResponse parenCompletion(T)(T beforeTokens, auto expression = getExpression(beforeTokens[0 .. $ - 1]); response.setCompletions(pair.scope_, expression, cursorPosition, CompletionType.calltips, beforeTokens[$ - 1] == tok!"["); + if (!pair.ufcsSymbols.empty) { + response.completions ~= pair.ufcsSymbols.map!(s => makeSymbolCompletionInfo(s, CompletionKind.ufcsName)).array; + // Setting CompletionType in case of none symbols are found via setCompletions, but we have UFCS symbols. + response.completionType = CompletionType.calltips; + } break; default: break; @@ -558,12 +565,6 @@ void setCompletions(T)(ref AutocompleteResponse response, DSymbol*[] symbols = getSymbolsByTokenChain(completionScope, tokens, cursorPosition, completionType); - if (tokens.length > 2 && tokens[1] == tok!".") - { - symbols.getUFCSParenCompletion(completionScope, stringToken(tokens[0]), stringToken( - tokens[2]), cursorPosition); - } - if (symbols.length == 0) return; @@ -581,7 +582,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) { diff --git a/src/dcd/server/autocomplete/util.d b/src/dcd/server/autocomplete/util.d index c0405e03..8bb84d3a 100644 --- a/src/dcd/server/autocomplete/util.d +++ b/src/dcd/server/autocomplete/util.d @@ -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,118 +611,6 @@ 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; @@ -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 diff --git a/tests/tc_calltip_in_func/file.d b/tests/tc_calltip_in_func/file.d new file mode 100644 index 00000000..4b62f81a --- /dev/null +++ b/tests/tc_calltip_in_func/file.d @@ -0,0 +1,14 @@ +class Bar +{ + void fun(A param) + { + } +} + +class Foo +{ + void foo(Bar bar) + { + bar.fun( + } +} diff --git a/tests/tc_calltip_in_func/run.sh b/tests/tc_calltip_in_func/run.sh index b72bb146..ba2553c3 100755 --- a/tests/tc_calltip_in_func/run.sh +++ b/tests/tc_calltip_in_func/run.sh @@ -1,5 +1,5 @@ set -e set -u -../../bin/dcd-client $1 -c66 <<< "class Bar{void fun(A param){}}class Foo{void foo(Bar bar){bar.fun(}}" > actual.txt +../../bin/dcd-client $1 file.d -c84 > actual.txt diff actual.txt expected.txt --strip-trailing-cr diff --git a/tests/tc_ufcs_calltip_in_func/file.d b/tests/tc_ufcs_calltip_in_func/file.d index 5299951b..8de68ba2 100644 --- a/tests/tc_ufcs_calltip_in_func/file.d +++ b/tests/tc_ufcs_calltip_in_func/file.d @@ -2,6 +2,8 @@ void showSomething(int x, string message, bool ok){} void showSomething(int x, string message, bool ok, float percentage){} void showSomething(string x, string message, bool ok){} void showSomething(){} +void showSomething(){} +void showNothing(int x){} int showSomething; struct showSomething { int x; } diff --git a/tests/tc_ufcs_calltip_in_func/run.sh b/tests/tc_ufcs_calltip_in_func/run.sh index a885d86f..f3b83682 100755 --- a/tests/tc_ufcs_calltip_in_func/run.sh +++ b/tests/tc_ufcs_calltip_in_func/run.sh @@ -1,5 +1,5 @@ set -e set -u -../../bin/dcd-client $1 -c293 file.d > actual.txt +../../bin/dcd-client $1 -c342 file.d > actual.txt diff actual.txt expected.txt --strip-trailing-cr