diff --git a/.github/workflows/pr_info_untrusted.yml b/.github/workflows/pr_info_untrusted.yml index f72c0de..9e8be32 100644 --- a/.github/workflows/pr_info_untrusted.yml +++ b/.github/workflows/pr_info_untrusted.yml @@ -61,7 +61,7 @@ jobs: echo ${{ github.event.number }} > ./pr/NR - name: upload comment to high-trust action making the comment - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: pr path: pr/ diff --git a/README.md b/README.md index 20e74d6..1bc4397 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,19 @@ Otherwise the client outputs _00000_ so that the length of the answer is guarant 45 133 +## Inlay Hints + +Build a list of extra annoations for your IDE to display. +You must submit the content of the current file displayed in your editor. + + dcd-client --inlayHints + +This is a W.I.P., currently it only provide annoatations about aliases for your variables, +more is planned. + +#### Example output + + l ->MyAlias->MyType 42 # Server diff --git a/common/src/dcd/common/messages.d b/common/src/dcd/common/messages.d index e0e7a8f..11c71d6 100644 --- a/common/src/dcd/common/messages.d +++ b/common/src/dcd/common/messages.d @@ -78,10 +78,12 @@ enum RequestKind : ushort localUse = 0b00000010_00000000, /// Remove import directory from server removeImport = 0b00000100_00000000, + /// Get inlay hints + inlayHints = 0b00001000_00000000, /// These request kinds require source code and won't be executed if there /// is no source sent - requiresSourceCode = autocomplete | doc | symbolLocation | search | localUse, + requiresSourceCode = autocomplete | doc | symbolLocation | search | localUse | inlayHints, // dfmt on } @@ -260,36 +262,39 @@ AutocompleteResponse getResponse(Socket socket) */ bool serverIsRunning(bool useTCP, string socketFile, ushort port) { - scope (failure) - return false; - AutocompleteRequest request; - request.kind = RequestKind.query; - Socket socket; - scope (exit) - { - socket.shutdown(SocketShutdown.BOTH); - socket.close(); - } - version(Windows) useTCP = true; - if (useTCP) - { - socket = new TcpSocket(AddressFamily.INET); - socket.connect(new InternetAddress("localhost", port)); - } - else - { - version(Windows) {} else + try { + AutocompleteRequest request; + request.kind = RequestKind.query; + Socket socket; + scope (exit) { - socket = new Socket(AddressFamily.UNIX, SocketType.STREAM); - socket.connect(new UnixAddress(socketFile)); + socket.shutdown(SocketShutdown.BOTH); + socket.close(); } + version(Windows) useTCP = true; + if (useTCP) + { + socket = new TcpSocket(AddressFamily.INET); + socket.connect(new InternetAddress("localhost", port)); + } + else + { + version(Windows) {} else + { + socket = new Socket(AddressFamily.UNIX, SocketType.STREAM); + socket.connect(new UnixAddress(socketFile)); + } + } + socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(5)); + socket.blocking = true; + if (sendRequest(socket, request)) + return getResponse(socket).completionType == "ack"; + else + return false; } - socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(5)); - socket.blocking = true; - if (sendRequest(socket, request)) - return getResponse(socket).completionType == "ack"; - else + catch (Exception _) { return false; + } } /// Escapes \n, \t and \ in the string. If `single` is true \t won't be escaped. diff --git a/common/src/dcd/common/socket.d b/common/src/dcd/common/socket.d index 9be4246..9e00837 100644 --- a/common/src/dcd/common/socket.d +++ b/common/src/dcd/common/socket.d @@ -27,6 +27,9 @@ version (OSX) version = haveUnixSockets; version (linux) version = haveUnixSockets; version (BSD) version = haveUnixSockets; version (FreeBSD) version = haveUnixSockets; +version (OpenBSD) version = haveUnixSockets; +version (NetBSD) version = haveUnixSockets; +version (DragonflyBSD) version = haveUnixSockets; enum DEFAULT_PORT_NUMBER = 9166; diff --git a/dsymbol/src/dsymbol/builtin/names.d b/dsymbol/src/dsymbol/builtin/names.d index b91739f..5bd5fb4 100644 --- a/dsymbol/src/dsymbol/builtin/names.d +++ b/dsymbol/src/dsymbol/builtin/names.d @@ -27,6 +27,15 @@ package istring[24] builtinTypeNames; * or the child type for single index access. */ @("*arr*") istring ARRAY_SYMBOL_NAME; +/** + * In breadcrumbs this is a single entry meaning that the type following this + * started with a dot `.`, so module scope instead of local scope is to be used + * for type resolution. + * + * Note that auto-completion does not rely on this symbol, only type / symbol + * lookup relies on this. + */ +@("*arr*") istring MODULE_SYMBOL_NAME; /** * Type suffix, in breadcrumbs this is a single entry. * @@ -165,6 +174,20 @@ package istring[24] builtinTypeNames; * class. DSymbol child of the class type, with the baseClass as its child type. */ @("super") istring SUPER_SYMBOL_NAME; +/** + * This symbol name may appear at the start of breadcrumbs meaning the remaining + * breadcrumbs up until the matching $(LREF TYPEOF_END_SYMBOL_NAME) are an + * initializer or typeof expression. Pointer/Array suffixes are parsed + * beforehand, using popBack to remove them from the breadcrumbs. + * + * See_Also: $(LREF TYPEOF_END_SYMBOL_NAME) + */ +@("typeof(") istring TYPEOF_SYMBOL_NAME; +/** + * This symbol always appears in pairs with TYPEOF_SYMBOL_NAME, designates the + * end of the typeof expression in the breadcrumbs. + */ +@(")/*typeof*/") istring TYPEOF_END_SYMBOL_NAME; /** * Breadcrumb part in initializer type generation for literal values in the diff --git a/dsymbol/src/dsymbol/conversion/first.d b/dsymbol/src/dsymbol/conversion/first.d index c9ac1b6..6cad7ff 100644 --- a/dsymbol/src/dsymbol/conversion/first.d +++ b/dsymbol/src/dsymbol/conversion/first.d @@ -131,6 +131,7 @@ final class FirstPass : ASTVisitor scope (exit) popSymbol(); currentSymbol.acSymbol.protection = protection.current; currentSymbol.acSymbol.doc = makeDocumentation(dec.comment); + currentSymbol.acSymbol.qualifier = SymbolQualifier.func; istring lastComment = this.lastComment; this.lastComment = istring.init; @@ -270,7 +271,7 @@ final class FirstPass : ASTVisitor part.identifier.text, CompletionKind.variableName, symbolFile, part.identifier.index); symbol.parent = currentSymbol; - populateInitializer(symbol, part.initializer); + populateInitializer(symbol.typeLookups, part.initializer); symbol.acSymbol.protection = protection.current; symbol.acSymbol.doc = makeDocumentation(dec.comment); currentSymbol.addChild(symbol, true); @@ -718,7 +719,7 @@ final class FirstPass : ASTVisitor currentSymbol.addChild(symbol, true); currentScope.addSymbol(symbol.acSymbol, true); if (symbol.typeLookups.empty && feExpression !is null) - populateInitializer(symbol, feExpression, true); + populateInitializer(symbol.typeLookups, feExpression, true); } } @@ -737,7 +738,7 @@ final class FirstPass : ASTVisitor currentSymbol.addChild(symbol, true); currentScope.addSymbol(symbol.acSymbol, true); if (symbol.typeLookups.empty && ifs.condition !is null && ifs.condition.expression !is null) - populateInitializer(symbol, ifs.condition.expression, false); + populateInitializer(symbol.typeLookups, ifs.condition.expression, false); } ifs.accept(this); } @@ -755,7 +756,7 @@ final class FirstPass : ASTVisitor currentScope.startLocation, null); scope(exit) popSymbol(); - populateInitializer(currentSymbol, withStatement.expression, false); + populateInitializer(currentSymbol.typeLookups, withStatement.expression, false); withStatement.accept(this); } @@ -908,7 +909,6 @@ private: CompletionKind.functionName, symbolFile, location); symbol.parent = currentSymbol; currentSymbol.addChild(symbol, true); - processParameters(symbol, null, THIS_SYMBOL_NAME, parameters, templateParameters); symbol.acSymbol.protection = protection.current; symbol.acSymbol.doc = makeDocumentation(doc); @@ -921,9 +921,16 @@ private: pushFunctionScope(functionBody, location + 4); // 4 == "this".length scope(exit) popScope(); currentSymbol = symbol; + processParameters(symbol, null, THIS_SYMBOL_NAME, parameters, templateParameters); functionBody.accept(this); currentSymbol = currentSymbol.parent; } + else + { + currentSymbol = symbol; + processParameters(symbol, null, THIS_SYMBOL_NAME, parameters, templateParameters); + currentSymbol = currentSymbol.parent; + } } void visitDestructor(size_t location, const FunctionBody functionBody, string doc) @@ -999,7 +1006,6 @@ private: break; } } - currentSymbol.acSymbol.argNames.insert(parameter.acSymbol.name); currentSymbol.acSymbol.functionParameters ~= parameter.acSymbol; @@ -1121,12 +1127,20 @@ private: return istring(app.data); } - void populateInitializer(T)(SemanticSymbol* symbol, const T initializer, - bool appendForeach = false) + void populateInitializer(T)(ref TypeLookups lookups, const T initializer, + bool appendForeach = false, TypeLookup* l = null) { - auto lookup = TypeLookupsAllocator.instance.make!TypeLookup(TypeLookupKind.initializer); + auto lookup = l ? l : TypeLookupsAllocator.instance.make!TypeLookup(TypeLookupKind.varOrFunType); + + lookup.breadcrumbs.insert(TYPEOF_SYMBOL_NAME); scope visitor = new InitializerVisitor(lookup, appendForeach, this); - symbol.typeLookups.insert(lookup); + scope (exit) + if (!visitor.isCast) + lookup.breadcrumbs.insert(TYPEOF_END_SYMBOL_NAME); + + if (l is null) + lookups.insert(lookup); + static if (is(T == typeof(feExpression))) visitor.dynamicDispatch(initializer); else @@ -1149,22 +1163,20 @@ private: auto lookup = l !is null ? l : TypeLookupsAllocator.instance.make!TypeLookup( TypeLookupKind.varOrFunType); auto t2 = type.type2; - if (t2.type !is null) - addTypeToLookups(lookups, t2.type, lookup); + if (t2.typeofExpression !is null) + populateInitializer(lookups, t2.typeofExpression, false, lookup); else if (t2.superOrThis is tok!"this") lookup.breadcrumbs.insert(internString("this")); else if (t2.superOrThis is tok!"super") lookup.breadcrumbs.insert(internString("super")); + + if (t2.type !is null) + addTypeToLookups(lookups, t2.type, lookup); else if (t2.builtinType !is tok!"") lookup.breadcrumbs.insert(getBuiltinTypeName(t2.builtinType)); else if (t2.typeIdentifierPart !is null) writeIotcTo(t2.typeIdentifierPart, lookup.breadcrumbs); - else - { - // TODO: Add support for typeof expressions - // TODO: Add support for __vector -// warning("typeof() and __vector are not yet supported"); - } + // TODO: support __vector, traits and mixin foreach (suffix; type.typeSuffixes) { @@ -1346,6 +1358,10 @@ void writeIotcTo(T)(const TypeIdentifierPart tip, ref T output) nothrow { if (!tip.identifierOrTemplateInstance) return; + + if (tip.dot) + output.insert(MODULE_SYMBOL_NAME); + if (tip.identifierOrTemplateInstance.identifier != tok!"") output.insert(internString(tip.identifierOrTemplateInstance.identifier.text)); else @@ -1575,6 +1591,25 @@ class InitializerVisitor : ASTVisitor on = false; } + override void visit(const CastExpression expression) + { + if (expression.type) + { + if (lookup.breadcrumbs.empty || lookup.breadcrumbs.back != TYPEOF_SYMBOL_NAME) + return; + + isCast = true; + lookup.breadcrumbs.popBack(); + TypeLookups none; + fp.addTypeToLookups(none, expression.type, lookup); + } + else + { + // we don't care about non-type casts (e.g. `cast()` or `cast(const)`) yet + expression.accept(this); + } + } + override void dynamicDispatch(const ExpressionNode expression) { on = true; @@ -1588,6 +1623,7 @@ class InitializerVisitor : ASTVisitor bool on = false; const bool appendForeach; FirstPass fp; + bool isCast; } class ArgumentListVisitor : ASTVisitor diff --git a/dsymbol/src/dsymbol/conversion/package.d b/dsymbol/src/dsymbol/conversion/package.d index 6b0df50..57e6626 100644 --- a/dsymbol/src/dsymbol/conversion/package.d +++ b/dsymbol/src/dsymbol/conversion/package.d @@ -115,6 +115,7 @@ class AutocompleteParser : Parser { if (!currentIs(tok!"{")) return null; + if (cursorPosition == -1) return super.parseBlockStatement(); if (current.index > cursorPosition) { BlockStatement bs = allocator.make!(BlockStatement); diff --git a/dsymbol/src/dsymbol/conversion/second.d b/dsymbol/src/dsymbol/conversion/second.d index a7b3580..bac4b50 100644 --- a/dsymbol/src/dsymbol/conversion/second.d +++ b/dsymbol/src/dsymbol/conversion/second.d @@ -237,16 +237,40 @@ do } // Follow all the names and try to resolve them - size_t i = 0; - foreach (part; lookup.breadcrumbs[]) + bool first = true; + auto breadcrumbs = lookup.breadcrumbs[]; + + while (!breadcrumbs.empty) { - if (i == 0) + auto part = breadcrumbs.front; + breadcrumbs.popFront(); + scope (exit) + first = false; + + if (part == TYPEOF_SYMBOL_NAME) + { + if (currentSymbol !is null) + { + warning("Invalid breadcrumbs, found `Type.typeof(...)`"); + return; + } + resolveTypeFromInitializer(symbol, lookup, moduleScope, cache, + breadcrumbs, currentSymbol); + } + else if (first) { if (moduleScope is null) getSymbolFromImports(imports, part); else { - auto symbols = moduleScope.getSymbolsByNameAndCursor(part, symbol.location); + auto symbols = part == MODULE_SYMBOL_NAME + ? { + assert(!breadcrumbs.empty); + part = breadcrumbs.front; + breadcrumbs.popFront(); + return moduleScope.getSymbolsByName(part); + }() + : moduleScope.getSymbolsByNameAndCursor(part, symbol.location); if (symbols.length > 0) currentSymbol = symbols[0]; else @@ -273,7 +297,6 @@ do return; currentSymbol = currentSymbol.getFirstPartNamed(part); } - ++i; if (currentSymbol is null) return; } @@ -311,19 +334,32 @@ do } } -void resolveTypeFromInitializer(DSymbol* symbol, TypeLookup* lookup, - Scope* moduleScope, ref ModuleCache cache) +private void resolveTypeFromInitializer(R)(DSymbol* symbol, TypeLookup* lookup, + Scope* moduleScope, ref ModuleCache cache, + ref R breadcrumbs, ref DSymbol* currentSymbol) { - if (lookup.breadcrumbs.length == 0) + if (breadcrumbs.empty) return; - DSymbol* currentSymbol = null; - size_t i = 0; - auto crumbs = lookup.breadcrumbs[]; - foreach (crumb; crumbs) + bool first = true; + while (!breadcrumbs.empty) { - if (i == 0) + auto crumb = breadcrumbs.front; + breadcrumbs.popFront(); + if (crumb == TYPEOF_SYMBOL_NAME) { + resolveTypeFromInitializer(symbol, lookup, moduleScope, cache, + breadcrumbs, currentSymbol); + if (currentSymbol is null) + return; + continue; + } + if (crumb == TYPEOF_END_SYMBOL_NAME) + break; + + if (first) + { + first = false; currentSymbol = moduleScope.getFirstSymbolByNameAndCursor( symbolNameToTypeName(crumb), symbol.location); @@ -348,18 +384,21 @@ void resolveTypeFromInitializer(DSymbol* symbol, TypeLookup* lookup, || currentSymbol.qualifier == SymbolQualifier.pointer || currentSymbol.kind == CompletionKind.aliasName) { - if (currentSymbol.type !is null) - currentSymbol = currentSymbol.type; - else - return; + // may become null, returns later + currentSymbol = currentSymbol.type; } else { auto opIndex = currentSymbol.getFirstPartNamed(internString("opIndex")); if (opIndex !is null) + { currentSymbol = opIndex.type; + } else + { + currentSymbol = null; return; + } } } else if (crumb == "foreach") @@ -371,19 +410,19 @@ void resolveTypeFromInitializer(DSymbol* symbol, TypeLookup* lookup, || currentSymbol.qualifier == SymbolQualifier.assocArray) { currentSymbol = currentSymbol.type; - break; + continue; } auto front = currentSymbol.getFirstPartNamed(internString("front")); if (front !is null) { currentSymbol = front.type; - break; + continue; } auto opApply = currentSymbol.getFirstPartNamed(internString("opApply")); if (opApply !is null) { currentSymbol = opApply.type; - break; + continue; } } else @@ -393,13 +432,10 @@ void resolveTypeFromInitializer(DSymbol* symbol, TypeLookup* lookup, return; currentSymbol = currentSymbol.getFirstPartNamed(crumb); } - ++i; if (currentSymbol is null) return; } typeSwap(currentSymbol); - symbol.type = currentSymbol; - symbol.ownType = false; } private: @@ -506,8 +542,6 @@ void resolveType(DSymbol* symbol, ref TypeLookups typeLookups, foreach(lookup; typeLookups) { if (lookup.kind == TypeLookupKind.varOrFunType) resolveTypeFromType(symbol, lookup, moduleScope, cache, null); - else if (lookup.kind == TypeLookupKind.initializer) - resolveTypeFromInitializer(symbol, lookup, moduleScope, cache); // issue 94 else if (lookup.kind == TypeLookupKind.inherit) resolveInheritance(symbol, typeLookups, moduleScope, cache); diff --git a/dsymbol/src/dsymbol/conversion/third.d b/dsymbol/src/dsymbol/conversion/third.d index 1148dac..29aa91c 100644 --- a/dsymbol/src/dsymbol/conversion/third.d +++ b/dsymbol/src/dsymbol/conversion/third.d @@ -64,8 +64,6 @@ void checkMissingTypes(SemanticSymbol* currentSymbol, Scope* moduleScope, ref Mo auto lookup = currentSymbol.typeLookups.front; if (lookup.kind == TypeLookupKind.varOrFunType) resolveTypeFromType(currentSymbol.acSymbol, lookup, moduleScope, cache, null); - else if (lookup.kind == TypeLookupKind.initializer) - resolveTypeFromInitializer(currentSymbol.acSymbol, lookup, moduleScope, cache); } break; default: diff --git a/dsymbol/src/dsymbol/symbol.d b/dsymbol/src/dsymbol/symbol.d index c70be9e..9350709 100644 --- a/dsymbol/src/dsymbol/symbol.d +++ b/dsymbol/src/dsymbol/symbol.d @@ -234,7 +234,7 @@ struct DSymbol // pointers are implicitly dereferenced on members (a single layer) if (qualifier == SymbolQualifier.pointer - && this.type.qualifier != SymbolQualifier.pointer) + && (this.type && this.type.qualifier != SymbolQualifier.pointer)) return type.getParts!OR(name, app, visited, onlyOne); if (name is null) @@ -386,11 +386,6 @@ struct DSymbol // Is alias this symbols DSymbol*[] aliasThisSymbols; - /** - * Names of function arguments - */ - // TODO: remove since we have function arguments - UnrolledList!(istring) argNames; /** * Function parameter symbols diff --git a/dsymbol/src/dsymbol/type_lookup.d b/dsymbol/src/dsymbol/type_lookup.d index 2260e57..8f69ceb 100644 --- a/dsymbol/src/dsymbol/type_lookup.d +++ b/dsymbol/src/dsymbol/type_lookup.d @@ -10,7 +10,6 @@ enum TypeLookupKind : ubyte { inherit, aliasThis, - initializer, mixinTemplate, varOrFunType, selectiveImport, diff --git a/dub.json b/dub.json index f5bade8..5868c76 100644 --- a/dub.json +++ b/dub.json @@ -8,7 +8,7 @@ "license": "GPL-3.0", "dependencies": { ":dsymbol": "*", - "libdparse": ">=0.23.0 <0.24.0", + "libdparse": ">=0.23.0 <0.26.0", ":common": "*", "emsi_containers": "~>0.9.0" }, diff --git a/dub.selections.json b/dub.selections.json index 6513ec2..f9773e7 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -3,8 +3,8 @@ "versions": { "dsymbol": "0.14.1", "emsi_containers": "0.9.0", - "libdparse": "0.23.0", - "msgpack-d": "1.0.4", + "libdparse": "0.25.0", + "msgpack-d": "1.0.5", "stdx-allocator": "2.77.5" } } diff --git a/libdparse b/libdparse index 86c9bf4..f8a6c28 160000 --- a/libdparse +++ b/libdparse @@ -1 +1 @@ -Subproject commit 86c9bf44c96e1666eb175c749cc26f62c2008979 +Subproject commit f8a6c28589aae180532fb460a1b22e92a0978292 diff --git a/makefile b/makefile index 74c0b57..eb9e46c 100644 --- a/makefile +++ b/makefile @@ -13,8 +13,6 @@ LDC := ldc2 DPARSE_DIR := libdparse DSYMBOL_DIR := dsymbol -SHELL:=/bin/bash - githash: @mkdir -p bin git describe --tags > bin/githash.txt @@ -37,7 +35,6 @@ CLIENT_SRC := \ DMD_CLIENT_FLAGS := -Imsgpack-d/src\ -Imsgpack-d/src\ -Jbin\ - -inline\ -O\ -wi\ -ofbin/dcd-client @@ -56,6 +53,10 @@ LDC_CLIENT_FLAGS := -Imsgpack-d/src\ -oq\ -of=bin/dcd-client +ifneq ($(shell uname), OpenBSD) + override DMD_CLIENT_FLAGS += -inline +endif + override DMD_CLIENT_FLAGS += $(DFLAGS) override LDC_CLIENT_FLAGS += $(DFLAGS) override GDC_CLIENT_FLAGS += $(DFLAGS) @@ -76,7 +77,6 @@ DMD_SERVER_FLAGS := -Icontainers/src\ -wi\ -O\ -release\ - -inline\ -ofbin/dcd-server DEBUG_SERVER_FLAGS := -Icontainers/src\ @@ -106,6 +106,10 @@ LDC_SERVER_FLAGS := -Icontainers/src\ -O5\ -release +ifneq ($(shell uname), OpenBSD) + DMD_SERVER_FLAGS += -inline +endif + override DMD_SERVER_FLAGS += $(DFLAGS) override LDC_SERVER_FLAGS += $(DFLAGS) override GDC_SERVER_FLAGS += $(DFLAGS) diff --git a/msgpack-d b/msgpack-d index 480f3bf..26ef07e 160000 --- a/msgpack-d +++ b/msgpack-d @@ -1 +1 @@ -Subproject commit 480f3bf9ee80ccf6695ed900cfcc1850ba8da991 +Subproject commit 26ef07e16023483ad93e3f86374b19d0e541c924 diff --git a/src/dcd/client/client.d b/src/dcd/client/client.d index e0be61d..207264e 100644 --- a/src/dcd/client/client.d +++ b/src/dcd/client/client.d @@ -62,6 +62,7 @@ int runClient(string[] args) bool clearCache; bool symbolLocation; bool doc; + bool inlayHints; bool query; bool printVersion; bool listImports; @@ -86,6 +87,7 @@ int runClient(string[] args) "R", &removedImportPaths, "port|p", &port, "help|h", &help, "shutdown", &shutdown, "clearCache", &clearCache, "symbolLocation|l", &symbolLocation, "doc|d", &doc, + "inlayHints", &inlayHints, "query|status|q", &query, "search|s", &search, "version", &printVersion, "listImports", &listImports, "tcp", &useTCP, "socketFile", &socketFile, @@ -181,7 +183,7 @@ int runClient(string[] args) printImportList(response); return 0; } - else if (search == null && cursorPos == size_t.max) + else if (search == null && !inlayHints && cursorPos == size_t.max) { // cursor position is a required argument printHelp(args[0]); @@ -234,6 +236,8 @@ int runClient(string[] args) request.kind |= RequestKind.search; else if(localUse) request.kind |= RequestKind.localUse; + else if (inlayHints) + request.kind |= RequestKind.inlayHints; else request.kind |= RequestKind.autocomplete; @@ -250,11 +254,13 @@ int runClient(string[] args) else if (getIdentifier) printIdentifierResponse(response); else if (doc) - printDocResponse(response); + printDocResponse(response, fullOutput); else if (search !is null) printSearchResponse(response); else if (localUse) printLocalUse(response); + else if (inlayHints) + printInlayHintsResponse(response); else printCompletionResponse(response, fullOutput); @@ -295,6 +301,10 @@ Options: Gets documentation comments associated with the symbol at the cursor location. + --inlayHints + For all supported variable usages, show value types. Currently shows + alias definitions. + --search | -s symbolName Searches for symbolName in both stdin / the given file name as well as others files cached by the server. @@ -359,10 +369,14 @@ Socket createSocket(string socketFile, ushort port) return socket; } -void printDocResponse(ref const AutocompleteResponse response) +void printDocResponse(ref const AutocompleteResponse response, bool extended) { - import std.algorithm : each; - response.completions.each!(a => a.documentation.escapeConsoleOutputString(true).writeln); + foreach (ref completion; response.completions) + { + if (extended) + writeln(completion.definition); + writeln(completion.documentation.escapeConsoleOutputString(true)); + } } void printIdentifierResponse(ref const AutocompleteResponse response) @@ -380,6 +394,21 @@ void printLocationResponse(ref const AutocompleteResponse response) writeln(makeTabSeparated(response.symbolFilePath, response.symbolLocation.to!string)); } +void printInlayHintsResponse(ref const AutocompleteResponse response) +{ + auto app = appender!(string[])(); + foreach (ref completion; response.completions) + { + app.put(makeTabSeparated( + completion.kind == char.init ? "" : "" ~ completion.kind, + completion.identifier, + completion.symbolLocation.to!string + )); + } + foreach (line; app.data) + writeln(line); +} + void printCompletionResponse(ref const AutocompleteResponse response, bool extended) { if (response.completions.length > 0) diff --git a/src/dcd/server/autocomplete/complete.d b/src/dcd/server/autocomplete/complete.d index cf3825c..6622890 100644 --- a/src/dcd/server/autocomplete/complete.d +++ b/src/dcd/server/autocomplete/complete.d @@ -361,7 +361,12 @@ CalltipHint getCalltipHint(T)(T beforeTokens, out size_t parenIndex) // evaluate at comma case if (beforeTokens.isComma) { - parenIndex = beforeTokens.goBackToOpenParen; + size_t tmp = beforeTokens.goBackToOpenParen; + if(tmp == size_t.max){ + return CalltipHint.regularArguments; + } + parenIndex = tmp; + // check if we are actually a "!(" if (beforeTokens[0 .. parenIndex].isTemplateBangParen) { diff --git a/src/dcd/server/autocomplete/doc.d b/src/dcd/server/autocomplete/doc.d index 060ba30..c3dbc1d 100644 --- a/src/dcd/server/autocomplete/doc.d +++ b/src/dcd/server/autocomplete/doc.d @@ -62,7 +62,7 @@ public AutocompleteResponse getDoc(const AutocompleteRequest request, continue; firstSymbol = false; - AutocompleteResponse.Completion c; + AutocompleteResponse.Completion c = makeSymbolCompletionInfo(symbol, symbol.kind); c.documentation = symbol.doc; response.completions ~= c; } diff --git a/src/dcd/server/autocomplete/inlayhints.d b/src/dcd/server/autocomplete/inlayhints.d new file mode 100644 index 0000000..41db78b --- /dev/null +++ b/src/dcd/server/autocomplete/inlayhints.d @@ -0,0 +1,111 @@ +/** + * This file is part of DCD, a development tool for the D programming language. + * Copyright (C) 2014 Brian Schott + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +module dcd.server.autocomplete.inlayhints; + +import std.stdio; +import std.algorithm; +import std.array; +import std.experimental.allocator; +import std.experimental.logger; +import std.typecons; + +import dcd.server.autocomplete.util; + +import dparse.lexer; +import dparse.rollback_allocator; + +import dsymbol.modulecache; +import dsymbol.symbol; +import dsymbol.scope_; +import dsymbol.conversion; +import dsymbol.string_interning; + +import dcd.common.messages; + +import containers.hashset; + +public AutocompleteResponse getInlayHints(const AutocompleteRequest request, + ref ModuleCache moduleCache) +{ +// trace("Getting inlay hints comments"); + AutocompleteResponse response; + + LexerConfig config; + config.fileName = ""; + auto cache = StringCache(request.sourceCode.length.optimalBucketCount); + auto tokenArray = getTokensForParser(cast(ubyte[]) request.sourceCode, config, &cache); + RollbackAllocator rba; + auto pair = generateAutocompleteTrees(tokenArray, &rba, -1, moduleCache); + scope(exit) pair.destroy(); + + void check(DSymbol* it, ref HashSet!size_t visited) + { + if (visited.contains(cast(size_t) it)) + return; + if (it.symbolFile != "stdin") return; + visited.insert(cast(size_t) it); + + //writeln("sym: ", it.name," ", it.location, " kind: ", it.kind," qualifier: ", it.qualifier); + //if (auto type = it.type) + //{ + // writeln(" ", type.name, " kind: ", type.kind, " qualifier", type.qualifier); + // if (auto ttype = type.type) + // writeln(" ", ttype.name, " kind: ", ttype.kind, " qualifier", ttype.qualifier); + //} + + + // aliases + // struct Data {} + // alias Alias1 = Data; + // Alias1 var; becomes: Alias1 [-> Data] var; + if (it.kind == CompletionKind.variableName && it.type && it.type.kind == CompletionKind.aliasName) + { + AutocompleteResponse.Completion c; + c.symbolLocation = it.location - 1; + c.kind = CompletionKind.aliasName; + + DSymbol* type = it.type; + + while (type) + { + if (type.kind == CompletionKind.aliasName && type.type) + c.identifier ~= "->" ~ type.type.name; + if (type.type && type.type.kind != CompletionKind.aliasName) break; + type = type.type; + } + + response.completions ~= c; + } + + foreach(part; it.opSlice()) + check(part, visited); + } + + HashSet!size_t visited; + foreach (symbol; pair.scope_.symbols) + { + check(symbol, visited); + foreach(part; symbol.opSlice()) + check(part, visited); + } + + response.completions.sort!"a.symbolLocation < b.symbolLocation"; + + return response; +} diff --git a/src/dcd/server/autocomplete/localuse.d b/src/dcd/server/autocomplete/localuse.d index 5974cfb..86f042b 100644 --- a/src/dcd/server/autocomplete/localuse.d +++ b/src/dcd/server/autocomplete/localuse.d @@ -57,12 +57,14 @@ public AutocompleteResponse findLocalUse(AutocompleteRequest request, config.fileName = ""; const(Token)[] tokenArray = getTokensForParser(cast(ubyte[]) request.sourceCode, config, &cache); + auto sortedTokens = assumeSorted(tokenArray); + ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, + &rba, request.cursorPosition, moduleCache); + scope(exit) pair.destroy(); + SymbolStuff getSymbolsAtCursor(size_t cursorPosition) { - auto sortedTokens = assumeSorted(tokenArray); auto beforeTokens = sortedTokens.lowerBound(cursorPosition); - ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, - &rba, request.cursorPosition, moduleCache); auto expression = getExpression(beforeTokens); return SymbolStuff(getSymbolsByTokenChain(pair.scope_, expression, cursorPosition, CompletionType.location), pair.symbol, pair.scope_); @@ -70,7 +72,6 @@ public AutocompleteResponse findLocalUse(AutocompleteRequest request, // gets the symbol matching to cursor pos SymbolStuff stuff = getSymbolsAtCursor(cast(size_t)request.cursorPosition); - scope(exit) stuff.destroy(); // starts searching only if no ambiguity with the symbol if (stuff.symbols.length == 1) @@ -100,7 +101,6 @@ public AutocompleteResponse findLocalUse(AutocompleteRequest request, { size_t pos = cast(size_t) t.index + 1; // place cursor inside the token SymbolStuff candidate = getSymbolsAtCursor(pos); - scope(exit) candidate.destroy(); if (candidate.symbols.length == 1 && candidate.symbols[0].location == sourceSymbol.location && candidate.symbols[0].symbolFile == sourceSymbol.symbolFile) diff --git a/src/dcd/server/autocomplete/package.d b/src/dcd/server/autocomplete/package.d index ee7811c..8e36e47 100644 --- a/src/dcd/server/autocomplete/package.d +++ b/src/dcd/server/autocomplete/package.d @@ -24,3 +24,4 @@ import dcd.server.autocomplete.complete; import dcd.server.autocomplete.doc; import dcd.server.autocomplete.localuse; import dcd.server.autocomplete.symbols; +import dcd.server.autocomplete.inlayhints; diff --git a/src/dcd/server/autocomplete/util.d b/src/dcd/server/autocomplete/util.d index 2f618f4..976e4a1 100644 --- a/src/dcd/server/autocomplete/util.d +++ b/src/dcd/server/autocomplete/util.d @@ -147,7 +147,8 @@ SymbolStuff getSymbolsForCompletion(const AutocompleteRequest request, auto expression = getExpression(beforeTokens); auto symbols = getSymbolsByTokenChain(pair.scope_, expression, request.cursorPosition, type); - if (symbols.length == 0 && doUFCSSearch(stringToken(beforeTokens.front), stringToken(beforeTokens.back))) { + if (symbols.length == 0 && !beforeTokens.empty && + doUFCSSearch(stringToken(beforeTokens.front), stringToken(beforeTokens.back))) { // Let search for UFCS, since we got no hit symbols ~= getSymbolsByTokenChain(pair.scope_, getExpression([beforeTokens.back]), request.cursorPosition, type); } diff --git a/src/dcd/server/main.d b/src/dcd/server/main.d index 3f3828d..7dbfd78 100644 --- a/src/dcd/server/main.d +++ b/src/dcd/server/main.d @@ -347,6 +347,11 @@ int runServer(string[] args) s.trySendResponse(symbolSearch(request, cache), "Could not perform symbol search"); else if (request.kind & RequestKind.localUse) s.trySendResponse(findLocalUse(request, cache), "Couldnot find local usage"); + else if (request.kind & RequestKind.inlayHints) + { + info("Getting inlay hints"); + s.trySendResponse(getInlayHints(request, cache), "Could not get inlay hints"); + } else if (needResponse) s.trySendResponse(AutocompleteResponse.ack, "Could not send ack"); } diff --git a/src/dcd/server/server.d b/src/dcd/server/server.d index 10b3651..0095ffe 100644 --- a/src/dcd/server/server.d +++ b/src/dcd/server/server.d @@ -35,6 +35,9 @@ enum CONFIG_FILE_NAME = "dcd.conf"; version(linux) version = useXDG; version(BSD) version = useXDG; version(FreeBSD) version = useXDG; +version(OpenBSD) version = useXDG; +version(NetBSD) version = useXDG; +version(DragonflyBSD) version = useXDG; version(OSX) version = useXDG; /** diff --git a/tests/extra/tc_ufcs_all_kinds/run.sh b/tests/extra/tc_ufcs_all_kinds/run.sh index 4c548a4..2c5e79c 100755 --- a/tests/extra/tc_ufcs_all_kinds/run.sh +++ b/tests/extra/tc_ufcs_all_kinds/run.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash if [ -z "${DC:-}" ]; then DC=dmd diff --git a/tests/run_tests.sh b/tests/run_tests.sh index aa64ce8..1d4b62b 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -1,4 +1,4 @@ -#! /bin/bash +#! /usr/bin/env bash RED="\033[31m" GREEN="\033[32m" YELLOW="\033[33m" diff --git a/tests/tc_casts/expected1.txt b/tests/tc_casts/expected1.txt new file mode 100644 index 0000000..52a2264 --- /dev/null +++ b/tests/tc_casts/expected1.txt @@ -0,0 +1,8 @@ +identifiers +alignof k +init k +inside_c v +mangleof k +sizeof k +stringof k +tupleof k diff --git a/tests/tc_casts/expected2.txt b/tests/tc_casts/expected2.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/tc_casts/file.d b/tests/tc_casts/file.d new file mode 100644 index 0000000..43834b1 --- /dev/null +++ b/tests/tc_casts/file.d @@ -0,0 +1,34 @@ +struct A +{ + struct B + { + struct C + { + int inside_c; + } + int inside_b; + } + int inside_a; +} + +unittest +{ + auto from_cast = cast(A.B.C) nonExist; + from_cast. +} + +unittest +{ + struct A {} + + auto from_cast = cast(A.B.C) nonExist; + from_cast. +} + +unittest +{ + struct A {} + + auto from_cast = cast(.A.B.C) nonExist; + from_cast. +} diff --git a/tests/tc_casts/run.sh b/tests/tc_casts/run.sh new file mode 100755 index 0000000..a45085f --- /dev/null +++ b/tests/tc_casts/run.sh @@ -0,0 +1,11 @@ +set -e +set -u + +../../bin/dcd-client $1 file.d -c159 > actual1.txt +diff actual1.txt expected1.txt --strip-trailing-cr + +../../bin/dcd-client $1 file.d -c239 > actual2.txt +diff actual2.txt expected2.txt --strip-trailing-cr + +../../bin/dcd-client $1 file.d -c320 > actual3.txt +diff actual3.txt expected1.txt --strip-trailing-cr diff --git a/tests/tc_ctors/expected.txt b/tests/tc_ctors/expected.txt new file mode 100644 index 0000000..a68b5ef --- /dev/null +++ b/tests/tc_ctors/expected.txt @@ -0,0 +1,3 @@ +identifiers +mangleof k +member1 v diff --git a/tests/tc_ctors/file.d b/tests/tc_ctors/file.d new file mode 100644 index 0000000..3679e23 --- /dev/null +++ b/tests/tc_ctors/file.d @@ -0,0 +1,19 @@ +struct Foo { + this(int mCtor) {} + int member1; +} + +class Bar { + this(int mCtor) {} + int member1; +} + +unittest { + Foo f; + f.m +} + +unittest { + Bar b = new Bar(1); + b.m +} \ No newline at end of file diff --git a/tests/tc_ctors/run.sh b/tests/tc_ctors/run.sh new file mode 100755 index 0000000..4afdf99 --- /dev/null +++ b/tests/tc_ctors/run.sh @@ -0,0 +1,8 @@ +set -e +set -u + +../../bin/dcd-client $1 file.d -c122 > actual.txt +diff actual.txt expected.txt --strip-trailing-cr + +../../bin/dcd-client $1 file.d -c162 > actual.txt +diff actual.txt expected.txt --strip-trailing-cr diff --git a/tests/tc_inlay_hints/expected.txt b/tests/tc_inlay_hints/expected.txt new file mode 100644 index 0000000..26f4596 --- /dev/null +++ b/tests/tc_inlay_hints/expected.txt @@ -0,0 +1,2 @@ +l ->Point 208 +l ->Point 247 diff --git a/tests/tc_inlay_hints/file.d b/tests/tc_inlay_hints/file.d new file mode 100644 index 0000000..8df2e2f --- /dev/null +++ b/tests/tc_inlay_hints/file.d @@ -0,0 +1,17 @@ +// when extending the inlayHints capabilities, don't forget to update the --help +// text inside client.d + +import point; +import point : P = Point; + +void foo(int x, int y) {} +void foo(Point point) {} +void bar(P point, int z = 1) {} + +void main() +{ + P p; + foo(1, 2); + foo(p); + bar(p, 3); +} diff --git a/tests/tc_inlay_hints/run.sh b/tests/tc_inlay_hints/run.sh new file mode 100755 index 0000000..c35089d --- /dev/null +++ b/tests/tc_inlay_hints/run.sh @@ -0,0 +1,5 @@ +set -e +set -u + +../../bin/dcd-client $1 --inlayHints file.d > actual.txt +diff actual.txt expected.txt --strip-trailing-cr diff --git a/tests/tc_typeof/expected1.txt b/tests/tc_typeof/expected1.txt new file mode 100644 index 0000000..3301724 --- /dev/null +++ b/tests/tc_typeof/expected1.txt @@ -0,0 +1,20 @@ +identifiers +getMember f typeof(member) getMember() stdin 78 Result +identifiers +staticMember f typeof(S.member) staticMember() stdin 133 Result +identifiers +alignof k +expected v int expected stdin 21 int +init k +mangleof k +sizeof k +stringof k +tupleof k +identifiers +alignof k +expected v int expected stdin 21 int +init k +mangleof k +sizeof k +stringof k +tupleof k diff --git a/tests/tc_typeof/expected2.txt b/tests/tc_typeof/expected2.txt new file mode 100644 index 0000000..293e517 --- /dev/null +++ b/tests/tc_typeof/expected2.txt @@ -0,0 +1,2 @@ +identifiers +test v Enum test stdin 121 Enum diff --git a/tests/tc_typeof/expected3.txt b/tests/tc_typeof/expected3.txt new file mode 100644 index 0000000..bcfd9d8 --- /dev/null +++ b/tests/tc_typeof/expected3.txt @@ -0,0 +1,8 @@ +identifiers +alignof k +init k +mangleof k +ok v bool ok stdin 16 bool +sizeof k +stringof k +tupleof k diff --git a/tests/tc_typeof/run.sh b/tests/tc_typeof/run.sh new file mode 100755 index 0000000..3fd2acd --- /dev/null +++ b/tests/tc_typeof/run.sh @@ -0,0 +1,14 @@ +set -e +set -u + +../../bin/dcd-client $1 test1.d -x -c213 > actual1.txt +../../bin/dcd-client $1 test1.d -x -c239 >> actual1.txt +../../bin/dcd-client $1 test1.d -x -c254 >> actual1.txt +../../bin/dcd-client $1 test1.d -x -c265 >> actual1.txt +diff actual1.txt expected1.txt --strip-trailing-cr + +../../bin/dcd-client $1 test2.d -x -c132 > actual2.txt +diff actual2.txt expected2.txt --strip-trailing-cr + +../../bin/dcd-client $1 test3.d -x -c83 > actual3.txt +diff actual3.txt expected3.txt --strip-trailing-cr diff --git a/tests/tc_typeof/test1.d b/tests/tc_typeof/test1.d new file mode 100644 index 0000000..7fe98a0 --- /dev/null +++ b/tests/tc_typeof/test1.d @@ -0,0 +1,32 @@ +struct Result +{ + int expected; +} + +struct S +{ + Result member; + + typeof(member) getMember() + { + return member; + } +} + +typeof(S.member) staticMember() +{ + return S.init.member; +} + +void test() +{ + S s; + auto a = S.getMember(); + auto b = staticMember(); + { + a. + } + { + b. + } +} diff --git a/tests/tc_typeof/test2.d b/tests/tc_typeof/test2.d new file mode 100644 index 0000000..9bc2f15 --- /dev/null +++ b/tests/tc_typeof/test2.d @@ -0,0 +1,15 @@ +struct MyTemplate(T) +{ + enum Enum { a, b } + + T member1; +} + +MyTemplate!long global2; + +void main() +{ + typeof(global2).Enum test; + test +} + diff --git a/tests/tc_typeof/test3.d b/tests/tc_typeof/test3.d new file mode 100644 index 0000000..9b85483 --- /dev/null +++ b/tests/tc_typeof/test3.d @@ -0,0 +1,10 @@ +struct S { bool ok; } + +S global3; + +void main() +{ + typeof(global3)[] test; + test[0]. +} +