diff options
37 files changed, 1615 insertions, 991 deletions
diff --git a/build/visual-studio/slang/slang.vcxproj b/build/visual-studio/slang/slang.vcxproj index f9a76211a..67c360403 100644 --- a/build/visual-studio/slang/slang.vcxproj +++ b/build/visual-studio/slang/slang.vcxproj @@ -311,6 +311,7 @@ IF EXIST ..\..\..\external\slang-binaries\bin\windows-aarch64\slang-glslang.dll\ <ClInclude Include="..\..\..\source\slang\slang-ast-decl.h" />
<ClInclude Include="..\..\..\source\slang\slang-ast-dump.h" />
<ClInclude Include="..\..\..\source\slang\slang-ast-expr.h" />
+ <ClInclude Include="..\..\..\source\slang\slang-ast-iterator.h" />
<ClInclude Include="..\..\..\source\slang\slang-ast-modifier.h" />
<ClInclude Include="..\..\..\source\slang\slang-ast-print.h" />
<ClInclude Include="..\..\..\source\slang\slang-ast-reflect.h" />
@@ -323,6 +324,7 @@ IF EXIST ..\..\..\external\slang-binaries\bin\windows-aarch64\slang-glslang.dll\ <ClInclude Include="..\..\..\source\slang\slang-check-impl.h" />
<ClInclude Include="..\..\..\source\slang\slang-check.h" />
<ClInclude Include="..\..\..\source\slang\slang-compiler.h" />
+ <ClInclude Include="..\..\..\source\slang\slang-content-assist-info.h" />
<ClInclude Include="..\..\..\source\slang\slang-diagnostic-defs.h" />
<ClInclude Include="..\..\..\source\slang\slang-diagnostics.h" />
<ClInclude Include="..\..\..\source\slang\slang-doc-ast.h" />
diff --git a/build/visual-studio/slang/slang.vcxproj.filters b/build/visual-studio/slang/slang.vcxproj.filters index 3b07a8ef5..b454d0806 100644 --- a/build/visual-studio/slang/slang.vcxproj.filters +++ b/build/visual-studio/slang/slang.vcxproj.filters @@ -30,6 +30,9 @@ <ClInclude Include="..\..\..\source\slang\slang-ast-expr.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\slang\slang-ast-iterator.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\slang\slang-ast-modifier.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -66,6 +69,9 @@ <ClInclude Include="..\..\..\source\slang\slang-compiler.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\slang\slang-content-assist-info.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\slang\slang-diagnostic-defs.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -4000,10 +4000,6 @@ namespace slang You have been warned. */ kSessionFlag_FalcorCustomSharedKeywordSemantics = 1 << 0, - - /** Indicates that this is a session created by language server. - */ - kSessionFlag_LanguageServer = 1 << 1, }; struct PreprocessorMacroDesc diff --git a/source/compiler-core/slang-json-rpc-connection.h b/source/compiler-core/slang-json-rpc-connection.h index 07b7cc347..18749229c 100644 --- a/source/compiler-core/slang-json-rpc-connection.h +++ b/source/compiler-core/slang-json-rpc-connection.h @@ -148,6 +148,8 @@ public: /// it will become invalid in most usage scenarios. PersistentJSONValue getPersistentValue(const JSONValue& value) { return PersistentJSONValue(value, &m_container, SourceLoc()); } + HTTPPacketConnection* getUnderlyingConnection() { return m_connection.Ptr(); } + /// Dtor ~JSONRPCConnection() { disconnect(); } diff --git a/source/compiler-core/slang-lexer.cpp b/source/compiler-core/slang-lexer.cpp index d149a88bc..fd0255575 100644 --- a/source/compiler-core/slang-lexer.cpp +++ b/source/compiler-core/slang-lexer.cpp @@ -1167,6 +1167,9 @@ namespace Slang switch(_peek(lexer)) { case '#': _advance(lexer); return TokenType::PoundPound; + + case '?': _advance(lexer); return TokenType::CompletionRequest; + default: return TokenType::Pound; } @@ -1337,7 +1340,7 @@ namespace Slang } } - if (tokenType == TokenType::Identifier) + if (tokenType == TokenType::Identifier || tokenType == TokenType::CompletionRequest) { token.setName(m_namePool->getName(token.getContent())); } diff --git a/source/compiler-core/slang-token-defs.h b/source/compiler-core/slang-token-defs.h index fdbe81e17..45b4912e7 100644 --- a/source/compiler-core/slang-token-defs.h +++ b/source/compiler-core/slang-token-defs.h @@ -90,6 +90,8 @@ PUNCTUATION(PoundPound, "##") PUNCTUATION(Scope, "::") +PUNCTUATION(CompletionRequest, "#?") + #undef PUNCTUATION // Un-define the `TOKEN` macro so that client doesn't have to diff --git a/source/slang/slang-ast-decl.h b/source/slang/slang-ast-decl.h index 65eaea4f1..9d2c99f14 100644 --- a/source/slang/slang-ast-decl.h +++ b/source/slang/slang-ast-decl.h @@ -406,6 +406,9 @@ class ImportDecl : public Decl // The module that actually got imported ModuleDecl* importedModuleDecl = nullptr; + SourceLoc startLoc; + SourceLoc endLoc; + SLANG_UNREFLECTED // The scope that we want to import into Scope* scope = nullptr; diff --git a/source/slang/slang-ast-iterator.h b/source/slang/slang-ast-iterator.h new file mode 100644 index 000000000..417f93d0d --- /dev/null +++ b/source/slang/slang-ast-iterator.h @@ -0,0 +1,424 @@ +#pragma once +#include "slang-syntax.h" + +namespace Slang +{ +template <typename Callback> +struct ASTIterator +{ + const Callback& callback; + UnownedStringSlice fileName; + SourceManager* sourceManager; + ASTIterator(const Callback& func, SourceManager* manager, UnownedStringSlice sourceFileName) + : callback(func) + , fileName(sourceFileName) + , sourceManager(manager) + {} + + void visitDecl(DeclBase* decl); + void visitExpr(Expr* expr); + void visitStmt(Stmt* stmt); + + void maybeDispatchCallback(SyntaxNode* node) + { + if (node) + { + callback(node); + } + } + + struct ASTIteratorExprVisitor : public ExprVisitor<ASTIteratorExprVisitor> + { + public: + ASTIterator* iterator; + ASTIteratorExprVisitor(ASTIterator* iter) + : iterator(iter) + {} + void dispatchIfNotNull(Expr* expr) + { + if (!expr) + return; + expr->accept(this, nullptr); + } + bool visitExpr(Expr*) { return false; } + void visitBoolLiteralExpr(BoolLiteralExpr* expr) { iterator->maybeDispatchCallback(expr); } + void visitNullPtrLiteralExpr(NullPtrLiteralExpr* expr) + { + iterator->maybeDispatchCallback(expr); + } + void visitIntegerLiteralExpr(IntegerLiteralExpr* expr) + { + iterator->maybeDispatchCallback(expr); + } + void visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr) + { + iterator->maybeDispatchCallback(expr); + } + void visitStringLiteralExpr(StringLiteralExpr* expr) + { + iterator->maybeDispatchCallback(expr); + } + void visitIncompleteExpr(IncompleteExpr* expr) { iterator->maybeDispatchCallback(expr); } + void visitIndexExpr(IndexExpr* subscriptExpr) + { + iterator->maybeDispatchCallback(subscriptExpr); + dispatchIfNotNull(subscriptExpr->baseExpression); + dispatchIfNotNull(subscriptExpr->indexExpression); + } + + void visitParenExpr(ParenExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->base); + } + + void visitAssignExpr(AssignExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->left); + dispatchIfNotNull(expr->right); + } + + void visitGenericAppExpr(GenericAppExpr* genericAppExpr) + { + iterator->maybeDispatchCallback(genericAppExpr); + + dispatchIfNotNull(genericAppExpr->functionExpr); + for (auto arg : genericAppExpr->arguments) + dispatchIfNotNull(arg); + } + + void visitSharedTypeExpr(SharedTypeExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->base.exp); + } + + void visitTaggedUnionTypeExpr(TaggedUnionTypeExpr* expr) + { + iterator->maybeDispatchCallback(expr); + } + + void visitInvokeExpr(InvokeExpr* expr) + { + iterator->maybeDispatchCallback(expr); + + dispatchIfNotNull(expr->functionExpr); + dispatchIfNotNull(expr->originalFunctionExpr); + + for (auto arg : expr->arguments) + dispatchIfNotNull(arg); + } + + void visitVarExpr(VarExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->originalExpr); + } + + void visitTryExpr(TryExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->base); + } + + void visitTypeCastExpr(TypeCastExpr* expr) + { + iterator->maybeDispatchCallback(expr); + + dispatchIfNotNull(expr->functionExpr); + for (auto arg : expr->arguments) + dispatchIfNotNull(arg); + } + + void visitDerefExpr(DerefExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->base); + } + void visitMatrixSwizzleExpr(MatrixSwizzleExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->base); + } + void visitSwizzleExpr(SwizzleExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->base); + } + void visitOverloadedExpr(OverloadedExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->base); + } + void visitOverloadedExpr2(OverloadedExpr2* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->base); + for (auto candidate : expr->candidiateExprs) + { + dispatchIfNotNull(candidate); + } + } + void visitAggTypeCtorExpr(AggTypeCtorExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->base.exp); + for (auto arg : expr->arguments) + { + dispatchIfNotNull(arg); + } + } + void visitCastToSuperTypeExpr(CastToSuperTypeExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->valueArg); + } + void visitModifierCastExpr(ModifierCastExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->valueArg); + } + void visitLetExpr(LetExpr* expr) + { + iterator->maybeDispatchCallback(expr); + iterator->visitDecl(expr->decl); + dispatchIfNotNull(expr->body); + } + void visitExtractExistentialValueExpr(ExtractExistentialValueExpr* expr) + { + iterator->maybeDispatchCallback(expr); + } + + void visitDeclRefExpr(DeclRefExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->originalExpr); + } + + void visitStaticMemberExpr(StaticMemberExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->baseExpression); + } + + void visitMemberExpr(MemberExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->baseExpression); + } + + void visitInitializerListExpr(InitializerListExpr* expr) + { + iterator->maybeDispatchCallback(expr); + for (auto arg : expr->args) + { + dispatchIfNotNull(arg); + } + } + + void visitThisExpr(ThisExpr* expr) { iterator->maybeDispatchCallback(expr); } + void visitThisTypeExpr(ThisTypeExpr* expr) { iterator->maybeDispatchCallback(expr); } + void visitAndTypeExpr(AndTypeExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->left.exp); + dispatchIfNotNull(expr->right.exp); + } + void visitModifiedTypeExpr(ModifiedTypeExpr* expr) + { + iterator->maybeDispatchCallback(expr); + dispatchIfNotNull(expr->base.exp); + } + }; + + struct ASTIteratorStmtVisitor : public StmtVisitor<ASTIteratorStmtVisitor> + { + ASTIterator* iterator; + ASTIteratorStmtVisitor(ASTIterator* iter) + : iterator(iter) + {} + + void dispatchIfNotNull(Stmt* stmt) + { + if (!stmt) + return; + stmt->accept(this, nullptr); + } + + void visitDeclStmt(DeclStmt* stmt) + { + iterator->maybeDispatchCallback(stmt); + iterator->visitDecl(stmt->decl); + } + + void visitBlockStmt(BlockStmt* stmt) + { + iterator->maybeDispatchCallback(stmt); + dispatchIfNotNull(stmt->body); + } + + void visitSeqStmt(SeqStmt* seqStmt) + { + iterator->maybeDispatchCallback(seqStmt); + for (auto stmt : seqStmt->stmts) + dispatchIfNotNull(stmt); + } + + void visitBreakStmt(BreakStmt* stmt) { iterator->maybeDispatchCallback(stmt); } + + void visitContinueStmt(ContinueStmt* stmt) { iterator->maybeDispatchCallback(stmt); } + + void visitDoWhileStmt(DoWhileStmt* stmt) + { + iterator->maybeDispatchCallback(stmt); + iterator->visitExpr(stmt->predicate); + dispatchIfNotNull(stmt->statement); + } + + void visitForStmt(ForStmt* stmt) + { + iterator->maybeDispatchCallback(stmt); + dispatchIfNotNull(stmt->initialStatement); + iterator->visitExpr(stmt->predicateExpression); + iterator->visitExpr(stmt->sideEffectExpression); + dispatchIfNotNull(stmt->statement); + } + + void visitCompileTimeForStmt(CompileTimeForStmt* stmt) + { + iterator->maybeDispatchCallback(stmt); + } + + void visitSwitchStmt(SwitchStmt* stmt) + { + iterator->maybeDispatchCallback(stmt); + iterator->visitExpr(stmt->condition); + dispatchIfNotNull(stmt->body); + } + + void visitCaseStmt(CaseStmt* stmt) + { + iterator->maybeDispatchCallback(stmt); + iterator->visitExpr(stmt->expr); + } + + void visitDefaultStmt(DefaultStmt* stmt) { iterator->maybeDispatchCallback(stmt); } + + void visitIfStmt(IfStmt* stmt) + { + iterator->maybeDispatchCallback(stmt); + iterator->visitExpr(stmt->predicate); + dispatchIfNotNull(stmt->positiveStatement); + dispatchIfNotNull(stmt->negativeStatement); + } + + void visitUnparsedStmt(UnparsedStmt* stmt) { iterator->maybeDispatchCallback(stmt); } + + void visitEmptyStmt(EmptyStmt* stmt) { iterator->maybeDispatchCallback(stmt); } + + void visitDiscardStmt(DiscardStmt* stmt) { iterator->maybeDispatchCallback(stmt); } + + void visitReturnStmt(ReturnStmt* stmt) + { + iterator->maybeDispatchCallback(stmt); + iterator->visitExpr(stmt->expression); + } + + void visitWhileStmt(WhileStmt* stmt) + { + iterator->maybeDispatchCallback(stmt); + iterator->visitExpr(stmt->predicate); + dispatchIfNotNull(stmt->statement); + } + + void visitGpuForeachStmt(GpuForeachStmt* stmt) { iterator->maybeDispatchCallback(stmt); } + + void visitExpressionStmt(ExpressionStmt* stmt) + { + iterator->maybeDispatchCallback(stmt); + iterator->visitExpr(stmt->expression); + } + }; +}; + +template <typename CallbackFunc> +void ASTIterator<CallbackFunc>::visitDecl(DeclBase* decl) +{ + // Don't look at the decl if it is defined in a different file. + if (!as<ModuleDecl>(decl) && !sourceManager->getHumaneLoc(decl->loc, SourceLocType::Actual) + .pathInfo.foundPath.getUnownedSlice() + .endsWithCaseInsensitive(fileName)) + return; + + maybeDispatchCallback(decl); + if (auto funcDecl = as<FunctionDeclBase>(decl)) + { + visitStmt(funcDecl->body); + visitExpr(funcDecl->returnType.exp); + } + else if (auto propertyDecl = as<PropertyDecl>(decl)) + { + visitExpr(propertyDecl->type.exp); + } + else if (auto varDecl = as<VarDeclBase>(decl)) + { + visitExpr(varDecl->type.exp); + visitExpr(varDecl->initExpr); + } + else if (auto genericDecl = as<GenericDecl>(decl)) + { + visitDecl(genericDecl->inner); + } + else if (auto typeConstraint = as<TypeConstraintDecl>(decl)) + { + visitExpr(typeConstraint->getSup().exp); + } + else if (auto typedefDecl = as<TypeDefDecl>(decl)) + { + visitExpr(typedefDecl->type.exp); + } + else if (auto extDecl = as<ExtensionDecl>(decl)) + { + visitExpr(extDecl->targetType.exp); + } + if (auto container = as<ContainerDecl>(decl)) + { + for (auto member : container->members) + { + visitDecl(member); + } + } +} +template <typename CallbackFunc> +void ASTIterator<CallbackFunc>::visitExpr(Expr* expr) +{ + ASTIteratorExprVisitor visitor(this); + visitor.dispatchIfNotNull(expr); +} +template <typename CallbackFunc> +void ASTIterator<CallbackFunc>::visitStmt(Stmt* stmt) +{ + ASTIteratorStmtVisitor visitor(this); + visitor.dispatchIfNotNull(stmt); +} + +template <typename Func> +void iterateAST( + UnownedStringSlice fileName, SourceManager* manager, SyntaxNode* node, const Func& f) +{ + ASTIterator<Func> iter(f, manager, fileName); + if (auto decl = as<Decl>(node)) + { + iter.visitDecl(decl); + } + else if (auto expr = as<Expr>(node)) + { + iter.visitExpr(expr); + } + else if (auto stmt = as<Stmt>(node)) + { + iter.visitStmt(stmt); + } +} +} // namespace Slang diff --git a/source/slang/slang-ast-support-types.h b/source/slang/slang-ast-support-types.h index e3dea7df1..c1e6a0132 100644 --- a/source/slang/slang-ast-support-types.h +++ b/source/slang/slang-ast-support-types.h @@ -1120,6 +1120,7 @@ namespace Slang { None = 0, IgnoreBaseInterfaces = 1 << 0, + Completion = 1 << 1, ///< Lookup all applicable decls for code completion suggestions }; class SerialRefObject; @@ -1323,26 +1324,26 @@ namespace Slang { return items.getCount() > 1 ? items[0].declRef.getName() : item.declRef.getName(); } - LookupResultItem* begin() + LookupResultItem* begin() const { if (isValid()) { if (isOverloaded()) - return items.begin(); + return const_cast<LookupResultItem*>(items.begin()); else - return &item; + return const_cast<LookupResultItem*>(&item); } else return nullptr; } - LookupResultItem* end() + LookupResultItem* end() const { if (isValid()) { if (isOverloaded()) - return items.end(); + return const_cast<LookupResultItem*>(items.end()); else - return &item + 1; + return const_cast<LookupResultItem*>(&item + 1); } else return nullptr; @@ -1359,6 +1360,8 @@ namespace Slang LookupMask mask = LookupMask::Default; LookupOptions options = LookupOptions::None; + + bool isCompletionRequest() const { return ((int)options & (int)LookupOptions::Completion) != 0; } }; struct WitnessTable; diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index bb762c1c6..7871d35dd 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -698,6 +698,17 @@ namespace Slang return; } + // If we should skip the checking, return now. + // A common case to skip checking is for the function bodies when we are in + // the language server. In that case we only care about the function bodies in a + // specific module and can skip checking the reference modules until they + // are being opened/edited later. + if (shouldSkipChecking(decl, state)) + { + decl->setCheckState(state); + return; + } + // Set the flag that indicates we are checking this declaration, // so that the cycle check above will catch us before we go // into any infinite loops. @@ -835,6 +846,48 @@ namespace Slang return true; } + bool SemanticsVisitor::shouldSkipChecking(Decl* decl, DeclCheckState state) + { + if (state != DeclCheckState::Checked) + return false; + // If we are in language server, we should skip checking all the function bodies + // except for the module or function that the user cared about. + // This optimization helps reduce the response time. + if (!getLinkage()->isInLanguageServer()) + { + return false; + } + if (auto funcDecl = as<FunctionDeclBase>(decl)) + { + auto& assistInfo = getLinkage()->contentAssistInfo; + // If this func is not defined in the primary module, skip checking its body. + auto moduleDecl = getModuleDecl(decl); + if (moduleDecl && moduleDecl->getName() != assistInfo.primaryModuleName) + return true; + if (funcDecl->body) + { + auto humaneLoc = getLinkage()->getSourceManager()->getHumaneLoc( + decl->loc, SourceLocType::Actual); + if (humaneLoc.pathInfo.foundPath != assistInfo.primaryModulePath) + { + return true; + } + if (assistInfo.checkingMode == ContentAssistCheckingMode::Completion) + { + // For completion requests, we skip all funtion bodies except for the one + // that the current cursor is in. + auto closingLoc = getLinkage()->getSourceManager()->getHumaneLoc( + funcDecl->closingSourceLoc, SourceLocType::Actual); + + if (assistInfo.cursorLine < humaneLoc.line || + assistInfo.cursorLine > closingLoc.line) + return true; + } + } + } + return false; + } + void SemanticsVisitor::_validateCircularVarDefinition(VarDeclBase* varDecl) { // The easiest way to test if the declaration is circular is to diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index 13552cc61..3b308c46a 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -514,6 +514,19 @@ namespace Slang return ConstructDeclRefExpr(item.declRef, bb, loc, originalExpr); } + void SemanticsVisitor::suggestCompletionItems( + CompletionSuggestions::ScopeKind scopeKind, LookupResult const& lookupResult) + { + auto& suggestions = getLinkage()->contentAssistInfo.completionSuggestions; + suggestions.clear(); + suggestions.scopeKind = scopeKind; + for (auto item : lookupResult) + { + suggestions.candidateItems.add(item); + } + } + + Expr* SemanticsVisitor::createLookupResultExpr( Name* name, LookupResult const& lookupResult, @@ -1472,6 +1485,15 @@ namespace Slang auto lookupResult = lookUp( m_astBuilder, this, expr->name, expr->scope); + if (expr->name == getSession()->getCompletionRequestTokenName()) + { + auto scopeKind = CompletionSuggestions::ScopeKind::Expr; + if (!m_parentFunc) + scopeKind = CompletionSuggestions::ScopeKind::Decl; + suggestCompletionItems(scopeKind, lookupResult); + return expr; + } + if (lookupResult.isValid()) { return createLookupResultExpr( @@ -1673,6 +1695,17 @@ namespace Slang bool anyDuplicates = false; int zeroIndexOffset = -1; + if (memberRefExpr->name == getSession()->getCompletionRequestTokenName()) + { + auto& suggestions = getLinkage()->contentAssistInfo.completionSuggestions; + suggestions.clear(); + suggestions.scopeKind = CompletionSuggestions::ScopeKind::Swizzle; + suggestions.swizzleBaseType = + memberRefExpr->baseExpression ? memberRefExpr->baseExpression->type : nullptr; + suggestions.elementCount[0] = baseElementRowCount; + suggestions.elementCount[1] = baseElementColCount; + } + String swizzleText = getText(memberRefExpr->name); auto cursor = swizzleText.begin(); @@ -1831,7 +1864,16 @@ namespace Slang bool elementUsed[4] = { false, false, false, false }; bool anyDuplicates = false; bool anyError = false; - + if (memberRefExpr->name == getSession()->getCompletionRequestTokenName()) + { + auto& suggestions = getLinkage()->contentAssistInfo.completionSuggestions; + suggestions.clear(); + suggestions.scopeKind = CompletionSuggestions::ScopeKind::Swizzle; + suggestions.swizzleBaseType = + memberRefExpr->baseExpression ? memberRefExpr->baseExpression->type : nullptr; + suggestions.elementCount[0] = baseElementCount; + suggestions.elementCount[1] = 0; + } auto swizzleText = getText(memberRefExpr->name); for (Index i = 0; i < swizzleText.getLength(); i++) @@ -1952,6 +1994,10 @@ namespace Slang return lookupMemberResultFailure(expr, baseType); } + if (expr->name == getSession()->getCompletionRequestTokenName()) + { + suggestCompletionItems(CompletionSuggestions::ScopeKind::Member, lookupResult); + } return createLookupResultExpr( expr->name, lookupResult, @@ -2007,11 +2053,11 @@ namespace Slang // For now let's just be expedient and disallow all of that, because // we can always add it back in later. - // If the lookup result is overloaded, then we want to filter + // If the lookup result is valid, then we want to filter // it to just those candidates that can be referenced statically, // and ignore any that would only be allowed as instance members. // - if(lookupResult.isOverloaded()) + if(lookupResult.isValid()) { // We track both the usable items, and whether or // not there were any non-static items that need @@ -2019,7 +2065,7 @@ namespace Slang // bool anyNonStatic = false; List<LookupResultItem> staticItems; - for (auto item : lookupResult.items) + for (auto item : lookupResult) { // Is this item usable as a static member? if (isUsableAsStaticMember(item)) @@ -2042,6 +2088,7 @@ namespace Slang if (staticItems.getCount()) { lookupResult.items = staticItems; + lookupResult.item = staticItems[0]; } else { @@ -2057,7 +2104,10 @@ namespace Slang // If there were no non-static items, then the `items` // array already represents what we'd get by filtering... } - + if (expr->name == getSession()->getCompletionRequestTokenName()) + { + suggestCompletionItems(CompletionSuggestions::ScopeKind::Member, lookupResult); + } return createLookupResultExpr( expr->name, lookupResult, @@ -2217,7 +2267,10 @@ namespace Slang { return lookupMemberResultFailure(expr, baseType); } - + if (expr->name == getSession()->getCompletionRequestTokenName()) + { + suggestCompletionItems(CompletionSuggestions::ScopeKind::Member, lookupResult); + } return createLookupResultExpr( expr->name, lookupResult, diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index bc037b0c2..5ef853b62 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -660,6 +660,7 @@ namespace Slang /// void _validateCircularVarDefinition(VarDeclBase* varDecl); + bool shouldSkipChecking(Decl* decl, DeclCheckState state); public: bool ValuesAreEqual( @@ -1647,6 +1648,9 @@ namespace Slang // void importModuleIntoScope(Scope* scope, ModuleDecl* moduleDecl); + + void suggestCompletionItems( + CompletionSuggestions::ScopeKind scopeKind, LookupResult const& lookupResult); }; struct SemanticsExprVisitor diff --git a/source/slang/slang-check-modifier.cpp b/source/slang/slang-check-modifier.cpp index 429479a38..c6a33930b 100644 --- a/source/slang/slang-check-modifier.cpp +++ b/source/slang/slang-check-modifier.cpp @@ -78,6 +78,19 @@ namespace Slang // Do nothing with modifiers for now } + static bool _isDeclAllowedAsAttribute(DeclRef<Decl> declRef) + { + if (as<AttributeDecl>(declRef.getDecl())) + return true; + auto structDecl = as<StructDecl>(declRef.getDecl()); + if (!structDecl) + return false; + auto attrUsageAttr = structDecl->findModifier<AttributeUsageAttribute>(); + if (!attrUsageAttr) + return false; + return true; + } + AttributeDecl* SemanticsVisitor::lookUpAttributeDecl(Name* attributeName, Scope* scope) { if (!attributeName) @@ -88,7 +101,29 @@ namespace Slang { // Look up the name and see what attributes we find. // - auto lookupResult = lookUp(m_astBuilder, this, attributeName, scope, LookupMask::Attribute); + LookupMask lookupMask = LookupMask::Attribute; + if (attributeName == getSession()->getCompletionRequestTokenName()) + { + lookupMask = + LookupMask((uint32_t)LookupMask::Attribute | (uint32_t)LookupMask::type); + } + + auto lookupResult = lookUp(m_astBuilder, this, attributeName, scope, lookupMask); + + if (attributeName == getSession()->getCompletionRequestTokenName()) + { + // If this is a completion request, add the lookup result to linkage. + auto& suggestions = getLinkage()->contentAssistInfo.completionSuggestions; + suggestions.clear(); + suggestions.scopeKind = CompletionSuggestions::ScopeKind::Attribute; + for (auto& item : lookupResult) + { + if (_isDeclAllowedAsAttribute(item.declRef)) + { + suggestions.candidateItems.add(item); + } + } + } // If the result was overloaded, then that means there // are multiple attributes matching the name, and we @@ -709,6 +744,16 @@ namespace Slang return checkAttribute(hlslUncheckedAttribute, syntaxNode); } + + if (auto hlslSemantic = as<HLSLSimpleSemantic>(m)) + { + if (hlslSemantic->name.getName() == getSession()->getCompletionRequestTokenName()) + { + getLinkage()->contentAssistInfo.completionSuggestions.scopeKind = + CompletionSuggestions::ScopeKind::HLSLSemantics; + } + } + // Default behavior is to leave things as they are, // and assume that modifiers are mostly already checked. // diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 280bdf688..e7114fc2c 100755 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -21,7 +21,7 @@ #include "slang-preprocessor.h" #include "slang-profile.h" #include "slang-syntax.h" - +#include "slang-content-assist-info.h" #include "slang-serialize-ir-types.h" @@ -1671,14 +1671,6 @@ namespace Slang /// lookup additional loaded modules. typedef Dictionary<Name*, Module*> LoadedModuleDictionary; - class Linkage; - class IModuleCache - { - public: - virtual RefPtr<Module> tryLoadModule(Linkage* linkage, String filePath) = 0; - virtual void storeModule(Linkage* linkage, String filePath, RefPtr<Module> module) = 0; - }; - /// A context for loading and re-using code modules. class Linkage : public RefObject, public slang::ISession { @@ -1754,7 +1746,7 @@ namespace Slang slang::SessionFlags m_flag = 0; void setFlags(slang::SessionFlags flags) { m_flag = flags; } - bool isInLanguageServer() { return (m_flag & slang::kSessionFlag_LanguageServer) != 0; } + bool isInLanguageServer() { return contentAssistInfo.checkingMode != ContentAssistCheckingMode::None; } /// Get the parent session for this linkage Session* getSessionImpl() { return m_session; } @@ -1799,8 +1791,6 @@ namespace Slang TypeCheckingCache* m_typeCheckingCache = nullptr; - void setModuleCache(IModuleCache* cache) { m_moduleCache = cache; } - // Modules that have been dynamically loaded via `import` // // This is a list of unique modules loaded, in the order they were encountered. @@ -1822,6 +1812,8 @@ namespace Slang // The resulting specialized IR module for each entry point request List<RefPtr<IRModule>> compiledModules; + ContentAssistInfo contentAssistInfo; + /// File system implementation to use when loading files from disk. /// /// If this member is `null`, a default implementation that tries @@ -1935,8 +1927,6 @@ namespace Slang RefPtr<Session> m_retainedSession; - IModuleCache* m_moduleCache = nullptr; - /// Tracks state of modules currently being loaded. /// /// This information is used to diagnose cases where @@ -3017,6 +3007,8 @@ namespace Slang /// Get the built in linkage -> handy to get the stdlibs from Linkage* getBuiltinLinkage() const { return m_builtinLinkage; } + Name* getCompletionRequestTokenName() const { return m_completionTokenName; } + void init(); void addBuiltinSource( @@ -3034,6 +3026,7 @@ namespace Slang RefPtr<DownstreamCompilerSet> m_downstreamCompilerSet; ///< Information about all available downstream compilers. RefPtr<DownstreamCompiler> m_downstreamCompilers[int(PassThroughMode::CountOf)]; ///< A downstream compiler for a pass through DownstreamCompilerLocatorFunc m_downstreamCompilerLocators[int(PassThroughMode::CountOf)]; + Name* m_completionTokenName = nullptr; ///< The name of a completion request token. private: diff --git a/source/slang/slang-content-assist-info.h b/source/slang/slang-content-assist-info.h new file mode 100644 index 000000000..bd2c4b7d9 --- /dev/null +++ b/source/slang/slang-content-assist-info.h @@ -0,0 +1,112 @@ +// slang-content-assist-info.h + +#pragma once + +#include "slang-syntax.h" +#include "../../slang.h" + +namespace Slang +{ + +struct CompletionSuggestions +{ + enum class ScopeKind + { + Invalid, + Member, + Swizzle, + Decl, + Stmt, + Expr, + Attribute, + HLSLSemantics + }; + ScopeKind scopeKind = ScopeKind::Invalid; + List<LookupResultItem> candidateItems; + Type* swizzleBaseType = nullptr; + IntegerLiteralValue elementCount[2] = {0, 0}; + + void clear() + { + scopeKind = ScopeKind::Invalid; + candidateItems.clear(); + elementCount[0] = 0; + elementCount[1] = 0; + swizzleBaseType = nullptr; + } +}; + +struct MacroDefinitionContentAssistInfo +{ + struct Param + { + Name* name; + bool isVariadic; + }; + + Name* name; + SourceLoc loc; + List<Param> params; + List<Token> tokenList; +}; + +struct MacroInvocationContentAssistInfo +{ + Name* name; + SourceLoc loc; +}; + +struct FileIncludeContentAssistInfo +{ + SourceLoc loc; + int length; + String path; +}; + +struct PreprocessorContentAssistInfo +{ + List<MacroDefinitionContentAssistInfo> macroDefinitions; + List<MacroInvocationContentAssistInfo> macroInvocations; + List<FileIncludeContentAssistInfo> fileIncludes; +}; + +enum class ContentAssistCheckingMode +{ + // Language server not enabled. + None, + + // General full checking for semantic token/document symbol/goto-defintion features. + General, + + // Checking for completion request only. Will ignore checking all function bodies + // except for the function the user is editing. + Completion +}; + +// This struct wraps all input/output data that is used by the language server to provide +// content assist support. +struct ContentAssistInfo +{ + // The mode the semantics checking should be operating on. Provided by the + // language server. + ContentAssistCheckingMode checkingMode = ContentAssistCheckingMode::None; + // The primary module from which the current content assist request is made. Provided by the + // language server. + Name* primaryModuleName = nullptr; + // The primary module path from which the current content assist request is made. Provided by the + // language server. + String primaryModulePath; + // The cursor location at which a completion request is made. Provided by the language server. + Index cursorLine = 0; + // The cursor location at which a completion request is made. Provided by the language server. + Index cursorCol = 0; + + // The result candidate items for a completion request. Filled in during semantics checking. + CompletionSuggestions completionSuggestions; + + // The preprocessors definitions and invocations found during preprocessing. Filled in during + // preprocessing. + PreprocessorContentAssistInfo preprocessorInfo; +}; + +} diff --git a/source/slang/slang-language-server-ast-lookup.cpp b/source/slang/slang-language-server-ast-lookup.cpp index 0e5a68687..6792a18e7 100644 --- a/source/slang/slang-language-server-ast-lookup.cpp +++ b/source/slang/slang-language-server-ast-lookup.cpp @@ -78,11 +78,13 @@ bool _isLocInRange(ASTLookupContext* context, SourceLoc start, SourceLoc end) { auto startLoc = context->sourceManager->getHumaneLoc(start, SourceLocType::Actual); auto endLoc = context->sourceManager->getHumaneLoc(end, SourceLocType::Actual); - + Loc s{startLoc.line, startLoc.column}; Loc e{endLoc.line, endLoc.column}; Loc c{context->line, context->col}; - return s <= c && c <= e; + return s <= c && c <= e && + startLoc.pathInfo.foundPath.getUnownedSlice().endsWithCaseInsensitive( + context->sourceFileName); } bool _findAstNodeImpl(ASTLookupContext& context, SyntaxNode* node); @@ -149,6 +151,8 @@ public: PushNode pushNodeRAII(context, expr); if (dispatchIfNotNull(expr->functionExpr)) return true; + if (dispatchIfNotNull(expr->originalFunctionExpr)) + return true; for (auto arg : expr->arguments) if (dispatchIfNotNull(arg)) return true; @@ -317,7 +321,7 @@ public: context->results.add(result); return true; } - return false; + return dispatchIfNotNull(expr->originalExpr); } bool visitStaticMemberExpr(StaticMemberExpr* expr) @@ -548,6 +552,16 @@ bool _findAstNodeImpl(ASTLookupContext& context, SyntaxNode* node) if (visitor.dispatchIfNotNull(extDecl->targetType.exp)) return true; } + else if (auto importDecl = as<ImportDecl>(node)) + { + if (_isLocInRange(&context, importDecl->startLoc, importDecl->endLoc)) + { + ASTLookupResult result; + result.path = context.nodePath; + context.results.add(_Move(result)); + return true; + } + } for (auto modifier : decl->modifiers) { if (auto hlslSemantic = as<HLSLSemantic>(modifier)) @@ -562,6 +576,21 @@ bool _findAstNodeImpl(ASTLookupContext& context, SyntaxNode* node) return true; } } + else if (auto attribute = as<AttributeBase>(modifier)) + { + if (attribute->getKeywordName() && + _isLocInRange( + &context, + attribute->getKeywordNameAndLoc().loc, + attribute->getKeywordName()->text.getLength())) + { + ASTLookupResult result; + result.path = context.nodePath; + result.path.add(attribute); + context.results.add(result); + return true; + } + } } if (auto container = as<ContainerDecl>(node)) { diff --git a/source/slang/slang-language-server-completion.cpp b/source/slang/slang-language-server-completion.cpp index c47e951fb..5eee1dba7 100644 --- a/source/slang/slang-language-server-completion.cpp +++ b/source/slang/slang-language-server-completion.cpp @@ -5,18 +5,29 @@ #include "slang-language-server.h" #include "slang-ast-all.h" -#include "slang-syntax.h" #include "slang-check-impl.h" +#include "slang-syntax.h" namespace Slang { -static bool _isIdentifierChar(char ch) -{ - return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_'; -} - -static bool _isWhitespaceChar(char ch) { return ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t'; } +static const char* kDeclKeywords[] = { + "throws", "static", "const", "in", "out", "inout", + "ref", "__subscript", "__init", "property", "get", "set", + "class", "struct", "interface", "public", "private", "internal", + "protected", "typedef", "typealias", "uniform", "export", "groupshared", + "extension", "associatedtype", "namespace", "This", "using", + "__generic", "__exported", "import", "enum", "cbuffer", "tbuffer", "func"}; +static const char* kStmtKeywords[] = { + "if", "else", "switch", "case", "default", "return", + "try", "throw", "throws", "catch", "while", "for", + "do", "static", "const", "in", "out", "inout", + "ref", "__subscript", "__init", "property", "get", "set", + "class", "struct", "interface", "public", "private", "internal", + "protected", "typedef", "typealias", "uniform", "export", "groupshared", + "extension", "associatedtype", "this", "namespace", "This", "using", + "__generic", "__exported", "import", "enum", "break", "continue", + "discard", "defer", "cbuffer", "tbuffer", "func"}; static const char* hlslSemanticNames[] = { "register", @@ -54,416 +65,294 @@ static const char* hlslSemanticNames[] = { SlangResult CompletionContext::tryCompleteHLSLSemantic() { - auto findResult = findASTNodesAt( - doc, - version->linkage->getSourceManager(), - parsedModule->getModuleDecl(), - ASTLookupType::Decl, - canonicalPath, - line, - col); - if (findResult.getCount() == 1 && findResult[0].path.getCount() != 0) - { - if (auto semantic = as<HLSLSemantic>(findResult[0].path.getLast())) - { - List<LanguageServerProtocol::CompletionItem> items; - for (auto name : hlslSemanticNames) - { - LanguageServerProtocol::CompletionItem item; - item.label = name; - item.kind = LanguageServerProtocol::kCompletionItemKindKeyword; - for (auto ch : getCommitChars()) - item.commitCharacters.add(ch); - items.add(item); - } - server->m_connection->sendResult(&items, responseId); - return SLANG_OK; - } - } - return SLANG_FAIL; -} - -SlangResult CompletionContext::tryCompleteMember() -{ - // Scan backward until we locate a '.' or ':'. - if (cursorOffset > 0) - cursorOffset--; - while (cursorOffset > 0 && _isWhitespaceChar(doc->getText()[cursorOffset])) - { - cursorOffset--; - } - while (cursorOffset > 0 && _isIdentifierChar(doc->getText()[cursorOffset])) - { - cursorOffset--; - } - while (cursorOffset > 0 && _isWhitespaceChar(doc->getText()[cursorOffset])) - { - cursorOffset--; - } - if (cursorOffset > 0 && doc->getText()[cursorOffset] == ':') - cursorOffset--; - if (cursorOffset <= 0 || - (doc->getText()[cursorOffset] != '.' && doc->getText()[cursorOffset] != ':')) - { - return SLANG_FAIL; - } - doc->offsetToLineCol(cursorOffset, line, col); - auto findResult = findASTNodesAt( - doc, - version->linkage->getSourceManager(), - parsedModule->getModuleDecl(), - ASTLookupType::Decl, - canonicalPath, - line, - col); - if (findResult.getCount() != 1) + if (version->linkage->contentAssistInfo.completionSuggestions.scopeKind != + CompletionSuggestions::ScopeKind::HLSLSemantics) { return SLANG_FAIL; } - if (findResult[0].path.getCount() == 0) - { - return SLANG_FAIL; - } - Expr* baseExpr = nullptr; - if (auto memberExpr = as<MemberExpr>(findResult[0].path.getLast())) - { - baseExpr = memberExpr->baseExpression; - } - else if (auto staticMemberExpr = as<StaticMemberExpr>(findResult[0].path.getLast())) + List<LanguageServerProtocol::CompletionItem> items; + for (auto name : hlslSemanticNames) { - baseExpr = staticMemberExpr->baseExpression; + LanguageServerProtocol::CompletionItem item; + item.label = name; + item.kind = LanguageServerProtocol::kCompletionItemKindKeyword; + items.add(item); } - else if (auto swizzleExpr = as<SwizzleExpr>(findResult[0].path.getLast())) - { - baseExpr = swizzleExpr->base; - } - else if (auto matSwizzleExpr = as<MatrixSwizzleExpr>(findResult[0].path.getLast())) - { - baseExpr = matSwizzleExpr->base; - } - if (!baseExpr || !baseExpr->type.type || - baseExpr->type.type->equals(version->linkage->getASTBuilder()->getErrorType())) + server->m_connection->sendResult(&items, responseId); + return SLANG_OK; +} + +SlangResult CompletionContext::tryCompleteAttributes() +{ + if (version->linkage->contentAssistInfo.completionSuggestions.scopeKind != + CompletionSuggestions::ScopeKind::Attribute) { return SLANG_FAIL; } - - List<LanguageServerProtocol::CompletionItem> items = collectMembers(baseExpr); + List<LanguageServerProtocol::CompletionItem> items = collectAttributes(); server->m_connection->sendResult(&items, responseId); return SLANG_OK; } -// The following collectMember* functions implement the logic to collect all members from a parsed type.] -// The flow is mostly the same as `lookupMemberInType`, but instead of looking for a specific name, -// we collect all members we see. - -struct MemberCollectingContext +SlangResult CompletionContext::tryCompleteMemberAndSymbol() { - ASTBuilder* astBuilder; - List<Decl*> members; - bool includeInstanceMembers = true; - SharedSemanticsContext semanticsContext; - MemberCollectingContext(Linkage* linkage, Module* module, DiagnosticSink* sink) - : semanticsContext(linkage, module, sink) - {} -}; - -void collectMembersInTypeDeclImpl(MemberCollectingContext* context, DeclRef<Decl> declRef); - -void collectMembersInType(MemberCollectingContext* context, Type* type); + List<LanguageServerProtocol::CompletionItem> items = collectMembersAndSymbols(); + server->m_connection->sendResult(&items, responseId); + return SLANG_OK; +} -void collectMembersInType(MemberCollectingContext* context, Type* type) +List<LanguageServerProtocol::CompletionItem> CompletionContext::collectMembersAndSymbols() { - if (auto pointerLikeType = as<PointerLikeType>(type)) - { - collectMembersInType(context, pointerLikeType->elementType); - return; - } - - if (auto declRefType = as<DeclRefType>(type)) - { - auto declRef = declRefType->declRef; - - collectMembersInTypeDeclImpl( - context, - declRef); - } - else if (auto nsType = as<NamespaceType>(type)) - { - auto declRef = nsType->declRef; - - collectMembersInTypeDeclImpl(context, declRef); - } - else if (auto extractExistentialType = as<ExtractExistentialType>(type)) - { - // We want lookup to be performed on the underlying interface type of the existential, - // but we need to have a this-type substitution applied to ensure that the result of - // lookup will have a comparable substitution applied (allowing things like associated - // types, etc. used in the signature of a method to resolve correctly). - // - auto interfaceDeclRef = extractExistentialType->getSpecializedInterfaceDeclRef(); - collectMembersInTypeDeclImpl(context, interfaceDeclRef); - } - else if (auto thisType = as<ThisType>(type)) + auto linkage = version->linkage; + if (linkage->contentAssistInfo.completionSuggestions.scopeKind == + CompletionSuggestions::ScopeKind::Swizzle) { - auto interfaceType = DeclRefType::create(context->astBuilder, thisType->interfaceDeclRef); - collectMembersInType(context, interfaceType); + return createSwizzleCandidates( + linkage->contentAssistInfo.completionSuggestions.swizzleBaseType, + linkage->contentAssistInfo.completionSuggestions.elementCount); } - else if (auto andType = as<AndType>(type)) + List<LanguageServerProtocol::CompletionItem> result; + bool useCommitChars = true; + bool addKeywords = false; + switch (linkage->contentAssistInfo.completionSuggestions.scopeKind) { - auto leftType = andType->left; - auto rightType = andType->right; - collectMembersInType(context, leftType); - collectMembersInType(context, rightType); + case CompletionSuggestions::ScopeKind::Member: + useCommitChars = (commitCharacterBehavior == CommitCharacterBehavior::MembersOnly || commitCharacterBehavior == CommitCharacterBehavior::All); + break; + case CompletionSuggestions::ScopeKind::Expr: + case CompletionSuggestions::ScopeKind::Decl: + case CompletionSuggestions::ScopeKind::Stmt: + useCommitChars = (commitCharacterBehavior == CommitCharacterBehavior::All); + addKeywords = true; + break; + default: + return result; } -} - -void collectMembersInTypeDeclImpl( - MemberCollectingContext* context, - DeclRef<Decl> declRef) -{ - if (declRef.getDecl()->checkState.getState() < DeclCheckState::ReadyForLookup) - return; - - if (auto genericTypeParamDeclRef = declRef.as<GenericTypeParamDecl>()) + HashSet<String> deduplicateSet; + for (Index i = 0; + i < linkage->contentAssistInfo.completionSuggestions.candidateItems.getCount(); + i++) { - // If the type we are doing lookup in is a generic type parameter, - // then the members it provides can only be discovered by looking - // at the constraints that are placed on that type. - auto genericDeclRef = genericTypeParamDeclRef.getParent().as<GenericDecl>(); - assert(genericDeclRef); - - for (auto constraintDeclRef : getMembersOfType<GenericTypeConstraintDecl>(genericDeclRef)) + auto& suggestedItem = linkage->contentAssistInfo.completionSuggestions.candidateItems[i]; + auto member = suggestedItem.declRef.getDecl(); + if (auto genericDecl = as<GenericDecl>(member)) + member = genericDecl->inner; + if (!member) + continue; + if (!member->getName()) + continue; + LanguageServerProtocol::CompletionItem item; + item.label = member->getName()->text; + item.kind = LanguageServerProtocol::kCompletionItemKindKeyword; + if (as<TypeConstraintDecl>(member)) { - if (constraintDeclRef.decl->checkState.getState() < DeclCheckState::ReadyForLookup) - { - continue; - } + continue; + } + if (as<ConstructorDecl>(member)) + { + continue; + } + if (as<SubscriptDecl>(member)) + { + continue; + } + if (item.label.getLength() == 0) + continue; + if (!_isIdentifierChar(item.label[0])) + continue; + if (item.label.startsWith("$")) + continue; + if (!deduplicateSet.Add(item.label)) + continue; - collectMembersInType( - context, - getSup(context->astBuilder, constraintDeclRef)); + if (as<StructDecl>(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindStruct; } - } - else if (declRef.as<AssocTypeDecl>() || declRef.as<GlobalGenericParamDecl>()) - { - for (auto constraintDeclRef : - getMembersOfType<TypeConstraintDecl>(declRef.as<ContainerDecl>())) + else if (as<ClassDecl>(member)) { - if (constraintDeclRef.decl->checkState.getState() < DeclCheckState::ReadyForLookup) - { - continue; - } - collectMembersInType(context, getSup(context->astBuilder, constraintDeclRef)); + item.kind = LanguageServerProtocol::kCompletionItemKindClass; } - } - else if (auto namespaceDecl = declRef.as<NamespaceDecl>()) - { - for (auto member : namespaceDecl.getDecl()->members) + else if (as<InterfaceDecl>(member)) { - if (member->getName()) - { - context->members.add(member); - } + item.kind = LanguageServerProtocol::kCompletionItemKindInterface; + } + else if (as<SimpleTypeDecl>(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindClass; + } + else if (as<PropertyDecl>(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindProperty; + } + else if (as<EnumDecl>(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindEnum; + } + else if (as<VarDeclBase>(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindVariable; + } + else if (as<EnumCaseDecl>(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindEnumMember; + } + else if (as<CallableDecl>(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindMethod; + } + else if (as<AssocTypeDecl>(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindClass; } + item.data = String(i); + result.add(item); } - else if (auto aggTypeDeclBaseRef = declRef.as<AggTypeDeclBase>()) + if (addKeywords) { - // In this case we are peforming lookup in the context of an aggregate - // type or an `extension`, so the first thing to do is to look for - // matching members declared directly in the body of the type/`extension`. - // - for (auto member : aggTypeDeclBaseRef.getDecl()->members) + if (linkage->contentAssistInfo.completionSuggestions.scopeKind == + CompletionSuggestions::ScopeKind::Decl) { - if (member->getName()) + for (auto keyword : kDeclKeywords) { - if (!context->includeInstanceMembers) - { - // Skip non-static members. - if (as<PropertyDecl>(member)) - continue; - if (as<SubscriptDecl>(member)) - continue; - if (as<VarDeclBase>(member) || as<FuncDecl>(member)) - { - if (!member->findModifier<HLSLStaticModifier>()) - { - continue; - } - } - } - context->members.add(member); + if (!deduplicateSet.Add(keyword)) + continue; + LanguageServerProtocol::CompletionItem item; + item.label = keyword; + item.kind = LanguageServerProtocol::kCompletionItemKindKeyword; + item.data = "-1"; + result.add(item); } } - - if (auto aggTypeDeclRef = aggTypeDeclBaseRef.as<AggTypeDecl>()) + else { - auto extensions = - context->semanticsContext.getCandidateExtensionsForTypeDecl(aggTypeDeclRef); - for (auto extDecl : extensions) + for (auto keyword : kStmtKeywords) { - // TODO: check if the extension can be applied before including its members. - // TODO: eventually we need to insert a breadcrumb here so that - // the constructed result can somehow indicate that a member - // was found through an extension. - // - collectMembersInTypeDeclImpl( - context, - DeclRef<Decl>(extDecl, nullptr)); + if (!deduplicateSet.Add(keyword)) + continue; + LanguageServerProtocol::CompletionItem item; + item.label = keyword; + item.kind = LanguageServerProtocol::kCompletionItemKindKeyword; + item.data = "-1"; + result.add(item); } } - - // For both aggregate types and their `extension`s, we want lookup to follow - // through the declared inheritance relationships on each declaration. - // - for (auto inheritanceDeclRef : getMembersOfType<InheritanceDecl>(aggTypeDeclBaseRef)) + + for (auto& def : linkage->contentAssistInfo.preprocessorInfo.macroDefinitions) { - // Some things that are syntactically `InheritanceDecl`s don't actually - // represent a subtype/supertype relationship, and thus we shouldn't - // include members from the base type when doing lookup in the - // derived type. - // - if (inheritanceDeclRef.getDecl()->hasModifier<IgnoreForLookupModifier>()) + if (!def.name) continue; - - collectMembersInType( - context, getSup(context->astBuilder, inheritanceDeclRef)); + auto& text = def.name->text; + if (!deduplicateSet.Add(text)) + continue; + LanguageServerProtocol::CompletionItem item; + item.label = text; + item.kind = LanguageServerProtocol::kCompletionItemKindKeyword; + item.data = "-1"; + result.add(item); } } + if (useCommitChars) + { + for (auto& item : result) + { + for (auto ch : getCommitChars()) + item.commitCharacters.add(ch); + } + } + return result; } -List<LanguageServerProtocol::CompletionItem> CompletionContext::collectMembers(Expr* baseExpr) +List<LanguageServerProtocol::CompletionItem> CompletionContext::createSwizzleCandidates( + Type* type, IntegerLiteralValue elementCount[2]) { List<LanguageServerProtocol::CompletionItem> result; - auto linkage = version->linkage; - Type* type = baseExpr->type.type; - bool isInstance = true; - if (auto typeType = as<TypeType>(type)) + // Hard code members for vector and matrix types. + result.clear(); + if (auto vectorType = as<VectorExpressionType>(type)) { - type = typeType->type; - isInstance = false; + const char* memberNames[4] = {"x", "y", "z", "w"}; + Type* elementType = nullptr; + elementType = vectorType->elementType; + String typeStr; + if (elementType) + typeStr = elementType->toString(); + auto count = Math::Min((int)elementCount[0], 4); + for (int i = 0; i < count; i++) + { + LanguageServerProtocol::CompletionItem item; + item.data = 0; + item.detail = typeStr; + item.kind = LanguageServerProtocol::kCompletionItemKindVariable; + item.label = memberNames[i]; + result.add(item); + } } - version->currentCompletionItems.clear(); - if (type) + else if (auto matrixType = as<MatrixExpressionType>(type)) { - if (isInstance && as<ArithmeticExpressionType>(type)) + Type* elementType = nullptr; + elementType = matrixType->getElementType(); + String typeStr; + if (elementType) { - // Hard code members for vector and matrix types. - result.clear(); - version->currentCompletionItems.clear(); - int elementCount = 0; - Type* elementType = nullptr; - const char* memberNames[4] = {"x", "y", "z", "w"}; - if (auto vectorType = as<VectorExpressionType>(type)) - { - if (auto elementCountVal = as<ConstantIntVal>(vectorType->elementCount)) - { - elementCount = (int)elementCountVal->value; - elementType = vectorType->elementType; - } - } - else if (auto matrixType = as<MatrixExpressionType>(type)) - { - if (auto elementCountVal = as<ConstantIntVal>(matrixType->getRowCount())) - { - elementCount = (int)elementCountVal->value; - elementType = matrixType->getRowType(); - } - } - String typeStr; - if (elementType) - typeStr = elementType->toString(); - elementCount = Math::Min(elementCount, 4); - for (int i = 0; i < elementCount; i++) + typeStr = elementType->toString(); + } + int rowCount = Math::Min((int)elementCount[0], 4); + int colCount = Math::Min((int)elementCount[1], 4); + StringBuilder nameSB; + for (int i = 0; i < rowCount; i++) + { + for (int j = 0; j < colCount; j++) { LanguageServerProtocol::CompletionItem item; item.data = 0; item.detail = typeStr; item.kind = LanguageServerProtocol::kCompletionItemKindVariable; - item.label = memberNames[i]; + nameSB.Clear(); + nameSB << "_m" << i << j; + item.label = nameSB.ToString(); + result.add(item); + nameSB.Clear(); + nameSB << "_" << i + 1 << j + 1; + item.label = nameSB.ToString(); result.add(item); } } - else + } + for (auto& item : result) + { + for (auto ch : getCommitChars()) + item.commitCharacters.add(ch); + } + return result; +} + +List<LanguageServerProtocol::CompletionItem> CompletionContext::collectAttributes() +{ + List<LanguageServerProtocol::CompletionItem> result; + for (auto& item : version->linkage->contentAssistInfo.completionSuggestions.candidateItems) + { + if (auto attrDecl = as<AttributeDecl>(item.declRef.getDecl())) { - DiagnosticSink sink; - MemberCollectingContext context(linkage, parsedModule, &sink); - context.astBuilder = linkage->getASTBuilder(); - context.includeInstanceMembers = isInstance; - collectMembersInType(&context, type); - HashSet<String> deduplicateSet; - for (auto member : context.members) + if (attrDecl->getName()) { - LanguageServerProtocol::CompletionItem item; - item.label = member->getName()->text; - item.kind = 0; - if (as<TypeConstraintDecl>(member)) - { - continue; - } - if (as<ConstructorDecl>(member)) - { - continue; - } - if (as<SubscriptDecl>(member)) - { - continue; - } - - if (item.label.startsWith("$")) - continue; - if (!deduplicateSet.Add(item.label)) - continue; - - if (as<StructDecl>(member)) - { - item.kind = LanguageServerProtocol::kCompletionItemKindStruct; - } - else if (as<ClassDecl>(member)) - { - item.kind = LanguageServerProtocol::kCompletionItemKindClass; - } - else if (as<InterfaceDecl>(member)) - { - item.kind = LanguageServerProtocol::kCompletionItemKindInterface; - } - else if (as<SimpleTypeDecl>(member)) - { - item.kind = LanguageServerProtocol::kCompletionItemKindClass; - } - else if (as<PropertyDecl>(member)) - { - item.kind = LanguageServerProtocol::kCompletionItemKindProperty; - } - else if (as<EnumDecl>(member)) - { - item.kind = LanguageServerProtocol::kCompletionItemKindEnum; - } - else if (as<VarDeclBase>(member)) - { - item.kind = LanguageServerProtocol::kCompletionItemKindVariable; - } - else if (as<EnumCaseDecl>(member)) - { - item.kind = LanguageServerProtocol::kCompletionItemKindEnumMember; - } - else if (as<CallableDecl>(member)) - { - item.kind = LanguageServerProtocol::kCompletionItemKindMethod; - } - else if (as<AssocTypeDecl>(member)) - { - item.kind = LanguageServerProtocol::kCompletionItemKindClass; - } - item.data = String(version->currentCompletionItems.getCount()); - result.add(item); - version->currentCompletionItems.add(member); + LanguageServerProtocol::CompletionItem resultItem; + resultItem.kind = LanguageServerProtocol::kCompletionItemKindKeyword; + resultItem.label = attrDecl->getName()->text; + result.add(resultItem); } } - - for (auto& item : result) + else if (auto decl = as<AggTypeDecl>(item.declRef.getDecl())) { - for (auto ch : getCommitChars()) - item.commitCharacters.add(ch); + if (decl->getName()) + { + LanguageServerProtocol::CompletionItem resultItem; + resultItem.kind = LanguageServerProtocol::kCompletionItemKindStruct; + resultItem.label = decl->getName()->text; + if (resultItem.label.endsWith("Attribute")) + resultItem.label.reduceLength(resultItem.label.getLength() - 9); + result.add(resultItem); + } } } return result; diff --git a/source/slang/slang-language-server-completion.h b/source/slang/slang-language-server-completion.h index 73aad6cd1..8c0c782ee 100644 --- a/source/slang/slang-language-server-completion.h +++ b/source/slang/slang-language-server-completion.h @@ -2,11 +2,19 @@ #pragma once #include "slang-workspace-version.h" +#include "slang-language-server-ast-lookup.h" namespace Slang { class LanguageServer; +enum class CommitCharacterBehavior +{ + Disabled, + MembersOnly, + All +}; + struct CompletionContext { LanguageServer* server; @@ -16,12 +24,17 @@ struct CompletionContext Module* parsedModule; JSONValue responseId; UnownedStringSlice canonicalPath; + CommitCharacterBehavior commitCharacterBehavior; Int line; Int col; - SlangResult tryCompleteMember(); + SlangResult tryCompleteMemberAndSymbol(); SlangResult tryCompleteHLSLSemantic(); - List<LanguageServerProtocol::CompletionItem> collectMembers(Expr* baseExpr); + SlangResult tryCompleteAttributes(); + List<LanguageServerProtocol::CompletionItem> collectMembersAndSymbols(); + List<LanguageServerProtocol::CompletionItem> createSwizzleCandidates( + Type* baseType, IntegerLiteralValue elementCount[2]); + List<LanguageServerProtocol::CompletionItem> collectAttributes(); }; } // namespace Slang diff --git a/source/slang/slang-language-server-semantic-tokens.cpp b/source/slang/slang-language-server-semantic-tokens.cpp index ff9056e56..6b3725828 100644 --- a/source/slang/slang-language-server-semantic-tokens.cpp +++ b/source/slang/slang-language-server-semantic-tokens.cpp @@ -1,423 +1,24 @@ #include "slang-language-server-semantic-tokens.h" #include "slang-visitor.h" #include "slang-ast-support-types.h" +#include "slang-ast-iterator.h" #include "../core/slang-char-util.h" #include <algorithm> namespace Slang { -template<typename Callback> -struct ASTIterator -{ - const Callback& callback; - UnownedStringSlice fileName; - SourceManager* sourceManager; - ASTIterator(const Callback& func, SourceManager* manager, UnownedStringSlice sourceFileName) - : callback(func) - , fileName(sourceFileName) - , sourceManager(manager) - {} - - void visitDecl(DeclBase* decl); - void visitExpr(Expr* expr); - void visitStmt(Stmt* stmt); - - void maybeDispatchCallback(SyntaxNode* node) - { - if (node) - { - callback(node); - } - } - - struct ASTIteratorExprVisitor : public ExprVisitor<ASTIteratorExprVisitor> - { - public: - ASTIterator* iterator; - ASTIteratorExprVisitor(ASTIterator* iter) - : iterator(iter) - {} - void dispatchIfNotNull(Expr* expr) - { - if (!expr) - return; - expr->accept(this, nullptr); - } - bool visitExpr(Expr*) { return false; } - void visitBoolLiteralExpr(BoolLiteralExpr* expr) { iterator->maybeDispatchCallback(expr); } - void visitNullPtrLiteralExpr(NullPtrLiteralExpr* expr) - { - iterator->maybeDispatchCallback(expr); - } - void visitIntegerLiteralExpr(IntegerLiteralExpr* expr) - { - iterator->maybeDispatchCallback(expr); - } - void visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr) - { - iterator->maybeDispatchCallback(expr); - } - void visitStringLiteralExpr(StringLiteralExpr* expr) - { - iterator->maybeDispatchCallback(expr); - } - void visitIncompleteExpr(IncompleteExpr* expr) { iterator->maybeDispatchCallback(expr); } - void visitIndexExpr(IndexExpr* subscriptExpr) - { - iterator->maybeDispatchCallback(subscriptExpr); - dispatchIfNotNull(subscriptExpr->baseExpression); - dispatchIfNotNull(subscriptExpr->indexExpression); - } - - void visitParenExpr(ParenExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->base); - } - - void visitAssignExpr(AssignExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->left); - dispatchIfNotNull(expr->right); - } - - void visitGenericAppExpr(GenericAppExpr* genericAppExpr) - { - iterator->maybeDispatchCallback(genericAppExpr); - - dispatchIfNotNull(genericAppExpr->functionExpr); - for (auto arg : genericAppExpr->arguments) - dispatchIfNotNull(arg); - } - - void visitSharedTypeExpr(SharedTypeExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->base.exp); - } - - void visitTaggedUnionTypeExpr(TaggedUnionTypeExpr* expr) { iterator->maybeDispatchCallback(expr); } - - void visitInvokeExpr(InvokeExpr* expr) - { - iterator->maybeDispatchCallback(expr); - - dispatchIfNotNull(expr->functionExpr); - for (auto arg : expr->arguments) - dispatchIfNotNull(arg); - } - - void visitVarExpr(VarExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->originalExpr); - } - - void visitTryExpr(TryExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->base); - } - - void visitTypeCastExpr(TypeCastExpr* expr) - { - iterator->maybeDispatchCallback(expr); - - dispatchIfNotNull(expr->functionExpr); - for (auto arg : expr->arguments) - dispatchIfNotNull(arg); - } - - void visitDerefExpr(DerefExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->base); - } - void visitMatrixSwizzleExpr(MatrixSwizzleExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->base); - } - void visitSwizzleExpr(SwizzleExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->base); - } - void visitOverloadedExpr(OverloadedExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->base); - } - void visitOverloadedExpr2(OverloadedExpr2* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->base); - for (auto candidate : expr->candidiateExprs) - { - dispatchIfNotNull(candidate); - } - } - void visitAggTypeCtorExpr(AggTypeCtorExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->base.exp); - for (auto arg : expr->arguments) - { - dispatchIfNotNull(arg); - } - } - void visitCastToSuperTypeExpr(CastToSuperTypeExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->valueArg); - } - void visitModifierCastExpr(ModifierCastExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->valueArg); - } - void visitLetExpr(LetExpr* expr) - { - iterator->maybeDispatchCallback(expr); - iterator->visitDecl(expr->decl); - dispatchIfNotNull(expr->body); - } - void visitExtractExistentialValueExpr(ExtractExistentialValueExpr* expr) - { - iterator->maybeDispatchCallback(expr); - } - - void visitDeclRefExpr(DeclRefExpr* expr) { iterator->maybeDispatchCallback(expr); } - - void visitStaticMemberExpr(StaticMemberExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->baseExpression); - } - - void visitMemberExpr(MemberExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->baseExpression); - } - - void visitInitializerListExpr(InitializerListExpr* expr) - { - iterator->maybeDispatchCallback(expr); - for (auto arg : expr->args) - { - dispatchIfNotNull(arg); - } - } - - void visitThisExpr(ThisExpr* expr) { iterator->maybeDispatchCallback(expr); } - void visitThisTypeExpr(ThisTypeExpr* expr) { iterator->maybeDispatchCallback(expr); } - void visitAndTypeExpr(AndTypeExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->left.exp); - dispatchIfNotNull(expr->right.exp); - } - void visitModifiedTypeExpr(ModifiedTypeExpr* expr) - { - iterator->maybeDispatchCallback(expr); - dispatchIfNotNull(expr->base.exp); - } - }; - - struct ASTIteratorStmtVisitor : public StmtVisitor<ASTIteratorStmtVisitor> - { - ASTIterator* iterator; - ASTIteratorStmtVisitor(ASTIterator* iter) - : iterator(iter) - {} - - void dispatchIfNotNull(Stmt* stmt) - { - if (!stmt) - return; - stmt->accept(this, nullptr); - } - - void visitDeclStmt(DeclStmt* stmt) - { - iterator->maybeDispatchCallback(stmt); - iterator->visitDecl(stmt->decl); - } - - void visitBlockStmt(BlockStmt* stmt) - { - iterator->maybeDispatchCallback(stmt); - dispatchIfNotNull(stmt->body); - } - - void visitSeqStmt(SeqStmt* seqStmt) - { - iterator->maybeDispatchCallback(seqStmt); - for (auto stmt : seqStmt->stmts) - dispatchIfNotNull(stmt); - } - - void visitBreakStmt(BreakStmt* stmt) { iterator->maybeDispatchCallback(stmt); } - - void visitContinueStmt(ContinueStmt* stmt) { iterator->maybeDispatchCallback(stmt); } - - void visitDoWhileStmt(DoWhileStmt* stmt) - { - iterator->maybeDispatchCallback(stmt); - iterator->visitExpr(stmt->predicate); - dispatchIfNotNull(stmt->statement); - } - - void visitForStmt(ForStmt* stmt) - { - iterator->maybeDispatchCallback(stmt); - dispatchIfNotNull(stmt->initialStatement); - iterator->visitExpr(stmt->predicateExpression); - iterator->visitExpr(stmt->sideEffectExpression); - dispatchIfNotNull(stmt->statement); - } - - void visitCompileTimeForStmt(CompileTimeForStmt* stmt) - { - iterator->maybeDispatchCallback(stmt); - } - - void visitSwitchStmt(SwitchStmt* stmt) - { - iterator->maybeDispatchCallback(stmt); - iterator->visitExpr(stmt->condition); - dispatchIfNotNull(stmt->body); - } - - void visitCaseStmt(CaseStmt* stmt) - { - iterator->maybeDispatchCallback(stmt); - iterator->visitExpr(stmt->expr); - } - - void visitDefaultStmt(DefaultStmt* stmt) { iterator->maybeDispatchCallback(stmt); } - - void visitIfStmt(IfStmt* stmt) - { - iterator->maybeDispatchCallback(stmt); - iterator->visitExpr(stmt->predicate); - dispatchIfNotNull(stmt->positiveStatement); - dispatchIfNotNull(stmt->negativeStatement); - } - - void visitUnparsedStmt(UnparsedStmt* stmt) { iterator->maybeDispatchCallback(stmt); } - - void visitEmptyStmt(EmptyStmt* stmt) { iterator->maybeDispatchCallback(stmt); } - - void visitDiscardStmt(DiscardStmt* stmt) { iterator->maybeDispatchCallback(stmt); } - - void visitReturnStmt(ReturnStmt* stmt) - { - iterator->maybeDispatchCallback(stmt); - iterator->visitExpr(stmt->expression); - } - - void visitWhileStmt(WhileStmt* stmt) - { - iterator->maybeDispatchCallback(stmt); - iterator->visitExpr(stmt->predicate); - dispatchIfNotNull(stmt->statement); - } - - void visitGpuForeachStmt(GpuForeachStmt* stmt) { iterator->maybeDispatchCallback(stmt); } - - void visitExpressionStmt(ExpressionStmt* stmt) - { - iterator->maybeDispatchCallback(stmt); - iterator->visitExpr(stmt->expression); - } - }; -}; - -template <typename CallbackFunc> -void ASTIterator<CallbackFunc>::visitDecl(DeclBase* decl) -{ - // Don't look at the decl if it is defined in a different file. - if (!as<ModuleDecl>(decl) && - !sourceManager->getHumaneLoc(decl->loc, SourceLocType::Actual) - .pathInfo.foundPath.getUnownedSlice() - .endsWithCaseInsensitive(fileName)) - return; - - maybeDispatchCallback(decl); - if (auto funcDecl = as<FunctionDeclBase>(decl)) - { - visitStmt(funcDecl->body); - visitExpr(funcDecl->returnType.exp); - } - else if (auto propertyDecl = as<PropertyDecl>(decl)) - { - visitExpr(propertyDecl->type.exp); - } - else if (auto varDecl = as<VarDeclBase>(decl)) - { - visitExpr(varDecl->type.exp); - visitExpr(varDecl->initExpr); - } - else if (auto genericDecl = as<GenericDecl>(decl)) - { - visitDecl(genericDecl->inner); - } - else if (auto typeConstraint = as<TypeConstraintDecl>(decl)) - { - visitExpr(typeConstraint->getSup().exp); - } - else if (auto typedefDecl = as<TypeDefDecl>(decl)) - { - visitExpr(typedefDecl->type.exp); - } - else if (auto extDecl = as<ExtensionDecl>(decl)) - { - visitExpr(extDecl->targetType.exp); - } - if (auto container = as<ContainerDecl>(decl)) - { - for (auto member : container->members) - { - visitDecl(member); - } - } -} -template <typename CallbackFunc> -void ASTIterator<CallbackFunc>::visitExpr(Expr* expr) -{ - ASTIteratorExprVisitor visitor(this); - visitor.dispatchIfNotNull(expr); -} -template <typename CallbackFunc> -void ASTIterator<CallbackFunc>::visitStmt(Stmt* stmt) -{ - ASTIteratorStmtVisitor visitor(this); - visitor.dispatchIfNotNull(stmt); -} - -template <typename Func> -void iterateAST(UnownedStringSlice fileName, SourceManager* manager, SyntaxNode* node, const Func& f) -{ - ASTIterator<Func> iter(f, manager, fileName); - if (auto decl = as<Decl>(node)) - { - iter.visitDecl(decl); - } - else if (auto expr = as<Expr>(node)) - { - iter.visitExpr(expr); - } - else if (auto stmt = as<Stmt>(node)) - { - iter.visitStmt(stmt); - } -} const char* kSemanticTokenTypes[] = { - "type", "enumMember", "variable", "parameter", "function", "property", "namespace", "keyword" }; + "type", + "enumMember", + "variable", + "parameter", + "function", + "property", + "namespace", + "keyword", + "macro" +}; static_assert(SLANG_COUNT_OF(kSemanticTokenTypes) == (int)SemanticTokenType::NormalText, "kSemanticTokenTypes must match SemanticTokenType"); @@ -585,6 +186,23 @@ List<SemanticToken> getSemanticTokens(Linkage* linkage, Module* module, UnownedS } } }); + // Insert macro tokens. + auto& preprocessorInfo = linkage->contentAssistInfo.preprocessorInfo; + for (auto& invocation : preprocessorInfo.macroInvocations) + { + if (!invocation.name) + continue; + // Don't look at the expr if it is defined in a different file. + auto humaneLoc = manager->getHumaneLoc(invocation.loc, SourceLocType::Actual); + if (!humaneLoc.pathInfo.foundPath.getUnownedSlice().endsWithCaseInsensitive(fileName)) + continue; + SemanticToken token; + token.line = (int)(humaneLoc.line); + token.col = (int)(humaneLoc.column); + token.length = (int)(invocation.name->text.getLength()); + token.type = SemanticTokenType::Macro; + maybeInsertToken(token); + } return result; } diff --git a/source/slang/slang-language-server-semantic-tokens.h b/source/slang/slang-language-server-semantic-tokens.h index c7cd8b63a..4a7368fcc 100644 --- a/source/slang/slang-language-server-semantic-tokens.h +++ b/source/slang/slang-language-server-semantic-tokens.h @@ -11,7 +11,7 @@ namespace Slang { enum class SemanticTokenType { - Type, EnumMember, Variable, Parameter, Function, Property, Namespace, Keyword, NormalText + Type, EnumMember, Variable, Parameter, Function, Property, Namespace, Keyword, Macro, NormalText }; extern const char* kSemanticTokenTypes[(int)SemanticTokenType::NormalText]; diff --git a/source/slang/slang-language-server.cpp b/source/slang/slang-language-server.cpp index 1ad0e28e9..619aaaacd 100644 --- a/source/slang/slang-language-server.cpp +++ b/source/slang/slang-language-server.cpp @@ -8,7 +8,6 @@ #include <stdlib.h> #include <string.h> #include <time.h> -#include <thread> #include "../core/slang-secure-crt.h" #include "../core/slang-range.h" #include "../../slang-com-helper.h" @@ -23,6 +22,7 @@ #include "slang-language-server-document-symbols.h" #include "slang-ast-print.h" #include "slang-doc-markdown-writer.h" +#include "slang-mangle.h" #include "../../tools/platform/performance-counter.h" namespace Slang @@ -114,6 +114,7 @@ SlangResult LanguageServer::parseNextMessage() result.capabilities.documentSymbolProvider = true; result.capabilities.completionProvider.triggerCharacters.add("."); result.capabilities.completionProvider.triggerCharacters.add(":"); + result.capabilities.completionProvider.triggerCharacters.add("["); result.capabilities.completionProvider.resolveProvider = true; result.capabilities.completionProvider.workDoneToken = ""; for (auto ch : getCommitChars()) @@ -152,11 +153,12 @@ SlangResult LanguageServer::parseNextMessage() if (response.result.getKind() == JSONValue::Kind::Array) { auto arr = m_connection->getContainer()->getArray(response.result); - if (arr.getCount() == 3) + if (arr.getCount() == 4) { updatePredefinedMacros(arr[0]); updateSearchPaths(arr[1]); updateSearchInWorkspace(arr[2]); + updateCommitCharacters(arr[3]); } } break; @@ -223,7 +225,7 @@ static String _formatDocumentation(String doc) auto trimedLine = lines[i].trimStart(); if (i > 0) { - if (trimedLine.startsWith("\\") && lines[i - 1].trim().getLength() != 0) + if ((trimedLine.startsWith("\\") || trimedLine.startsWith("@")) && lines[i - 1].trim().getLength() != 0) { result << " \n"; } @@ -232,15 +234,19 @@ static String _formatDocumentation(String doc) result << "\n"; } } - if (trimedLine.startsWith("\\returns ")) + if (trimedLine.startsWith("\\") || trimedLine.startsWith("@")) { - trimedLine = trimedLine.subString(9, trimedLine.getLength()); - result << "**returns** "; - } - else if (trimedLine.startsWith("\\return ")) - { - trimedLine = trimedLine.subString(8, trimedLine.getLength()); - result << "**Returns** "; + trimedLine = trimedLine.tail(1); + if (trimedLine.startsWith("returns ")) + { + trimedLine = trimedLine.subString(8, trimedLine.getLength()); + result << "**returns** "; + } + else if (trimedLine.startsWith("return ")) + { + trimedLine = trimedLine.subString(7, trimedLine.getLength()); + result << "**Returns** "; + } } result << trimedLine; } @@ -294,6 +300,8 @@ SlangResult LanguageServer::hover( col); if (findResult.getCount() == 0 || findResult[0].path.getCount() == 0) { + if (SLANG_SUCCEEDED(tryGetMacroHoverInfo(version, doc, line, col, responseId))) + return SLANG_OK; m_connection->sendResult(NullResponse::get(), responseId); return SLANG_OK; } @@ -393,6 +401,10 @@ SlangResult LanguageServer::gotoDefinition( col); if (findResult.getCount() == 0 || findResult[0].path.getCount() == 0) { + if (SLANG_SUCCEEDED(tryGotoMacroDefinition(version, doc, line, col, responseId))) + return SLANG_OK; + if (SLANG_SUCCEEDED(tryGotoFileInclude(version, doc, line, responseId))) + return SLANG_OK; m_connection->sendResult(NullResponse::get(), responseId); return SLANG_OK; } @@ -438,7 +450,22 @@ SlangResult LanguageServer::gotoDefinition( locations.add(LocationResult{location, name ? (int)name->text.getLength() : 0}); } } - + } + else if (auto importDecl = as<ImportDecl>(leafNode)) + { + if (importDecl->importedModuleDecl) + { + if (importDecl->importedModuleDecl->members.getCount() && + importDecl->importedModuleDecl->members[0]) + { + auto loc = importDecl->importedModuleDecl->members[0]->loc; + if (loc.isValid()) + { + auto location = version->linkage->getSourceManager()->getHumaneLoc(loc, SourceLocType::Actual); + locations.add(LocationResult{location, 0}); + } + } + } } if (locations.getCount() == 0) { @@ -467,6 +494,16 @@ SlangResult LanguageServer::gotoDefinition( } } +template <typename Func> struct Deferred +{ + Func f; + Deferred(const Func& func) + : f(func) + {} + ~Deferred() { f(); } +}; +template <typename Func> Deferred<Func> makeDeferred(const Func& f) { return Deferred<Func>(f); } + SlangResult LanguageServer::completion( const LanguageServerProtocol::CompletionParams& args, const JSONValue& responseId) { @@ -487,8 +524,28 @@ SlangResult LanguageServer::completion( m_connection->sendResult(NullResponse::get(), responseId); return SLANG_OK; } - - auto version = m_workspace->getCurrentVersion(); + + // Ajust cursor position to the beginning of the current/last identifier. + cursorOffset--; + while (cursorOffset > 0 && _isIdentifierChar(doc->getText()[cursorOffset])) + { + cursorOffset--; + } + + // Insert a completion request token at cursor position. + auto originalText = doc->getText(); + StringBuilder newText; + newText << originalText.getUnownedSlice().head(cursorOffset + 1) << "#?" + << originalText.getUnownedSlice().tail(cursorOffset + 1); + doc->setText(newText.ProduceString()); + auto restoreDocText = makeDeferred([&]() { doc->setText(originalText); }); + + // Always create a new workspace version for the completion request since we + // will use a modified source. + auto version = m_workspace->createVersionForCompletion(); + auto moduleName = getMangledNameFromNameString(canonicalPath.getUnownedSlice()); + version->linkage->contentAssistInfo.cursorLine = utf8Line; + version->linkage->contentAssistInfo.cursorCol = utf8Col; Module* parsedModule = version->getOrLoadModule(canonicalPath); if (!parsedModule) { @@ -506,7 +563,8 @@ SlangResult LanguageServer::completion( context.canonicalPath = canonicalPath.getUnownedSlice(); context.line = utf8Line; context.col = utf8Col; - if (SLANG_SUCCEEDED(context.tryCompleteMember())) + context.commitCharacterBehavior = m_commitCharacterBehavior; + if (SLANG_SUCCEEDED(context.tryCompleteAttributes())) { return SLANG_OK; } @@ -514,6 +572,10 @@ SlangResult LanguageServer::completion( { return SLANG_OK; } + if (SLANG_SUCCEEDED(context.tryCompleteMemberAndSymbol())) + { + return SLANG_OK; + } m_connection->sendResult(NullResponse::get(), responseId); return SLANG_OK; } @@ -523,14 +585,19 @@ SlangResult LanguageServer::completionResolve( { LanguageServerProtocol::CompletionItem resolvedItem = args; int itemId = StringToInt(args.data); - auto version = m_workspace->getCurrentVersion(); - if (itemId >= 0 && itemId < version->currentCompletionItems.getCount()) + auto version = m_workspace->getCurrentCompletionVersion(); + if (!version || !version->linkage) { - auto decl = version->currentCompletionItems[itemId]; - resolvedItem.detail = getDeclSignatureString( - DeclRef<Decl>(decl, nullptr), version->linkage->getASTBuilder()); + m_connection->sendResult(&resolvedItem, responseId); + return SLANG_OK; + } + auto& candidateItems = version->linkage->contentAssistInfo.completionSuggestions.candidateItems; + if (itemId >= 0 && itemId < candidateItems.getCount()) + { + auto declRef = candidateItems[itemId].declRef; + resolvedItem.detail = getDeclSignatureString(declRef, version->linkage->getASTBuilder()); StringBuilder docSB; - _tryGetDocumentation(docSB, version, decl); + _tryGetDocumentation(docSB, version, declRef.getDecl()); resolvedItem.documentation.value = docSB.ProduceString(); resolvedItem.documentation.kind = "markdown"; } @@ -817,14 +884,14 @@ void LanguageServer::updatePredefinedMacros(const JSONValue& macros) } } -void LanguageServer::updateSearchPaths(const JSONValue& macros) +void LanguageServer::updateSearchPaths(const JSONValue& value) { - if (macros.isValid()) + if (value.isValid()) { auto container = m_connection->getContainer(); JSONToNativeConverter converter(container, m_connection->getSink()); List<String> searchPaths; - if (SLANG_SUCCEEDED(converter.convert(macros, &searchPaths))) + if (SLANG_SUCCEEDED(converter.convert(value, &searchPaths))) { if (m_workspace->updateSearchPaths(searchPaths)) { @@ -835,14 +902,14 @@ void LanguageServer::updateSearchPaths(const JSONValue& macros) } } -void LanguageServer::updateSearchInWorkspace(const JSONValue& macros) +void LanguageServer::updateSearchInWorkspace(const JSONValue& value) { - if (macros.isValid()) + if (value.isValid()) { auto container = m_connection->getContainer(); JSONToNativeConverter converter(container, m_connection->getSink()); bool searchPaths; - if (SLANG_SUCCEEDED(converter.convert(macros, &searchPaths))) + if (SLANG_SUCCEEDED(converter.convert(value, &searchPaths))) { if (m_workspace->updateSearchInWorkspace(searchPaths)) { @@ -853,6 +920,32 @@ void LanguageServer::updateSearchInWorkspace(const JSONValue& macros) } } +void LanguageServer::updateCommitCharacters(const JSONValue& jsonValue) +{ + if (jsonValue.isValid()) + { + auto container = m_connection->getContainer(); + JSONToNativeConverter converter(container, m_connection->getSink()); + String value; + if (SLANG_SUCCEEDED(converter.convert(jsonValue, &value))) + { + if (value == "on") + { + m_commitCharacterBehavior = CommitCharacterBehavior::All; + } + else if (value == "off") + { + m_commitCharacterBehavior = CommitCharacterBehavior::Disabled; + } + else + { + m_commitCharacterBehavior = CommitCharacterBehavior::MembersOnly; + } + } + } +} + + void LanguageServer::sendConfigRequest() { ConfigurationParams args; @@ -863,6 +956,8 @@ void LanguageServer::sendConfigRequest() args.items.add(item); item.section = "slang.searchInAllWorkspaceDirectories"; args.items.add(item); + item.section = "slang.enableCommitCharactersInAutoCompletion"; + args.items.add(item); m_connection->sendCall( ConfigurationParams::methodName, &args, @@ -888,6 +983,106 @@ void LanguageServer::logMessage(int type, String message) m_connection->sendCall(LanguageServerProtocol::LogMessageParams::methodName, &args); } +SlangResult LanguageServer::tryGetMacroHoverInfo( + WorkspaceVersion* version, DocumentVersion* doc, Index line, Index col, JSONValue responseId) +{ + Index startOffset = 0; + auto identifier = doc->peekIdentifier(line, col, startOffset); + if (identifier.getLength() == 0) + return SLANG_FAIL; + auto def = version->tryGetMacroDefinition(identifier); + if (!def) + return SLANG_FAIL; + LanguageServerProtocol::Hover hover; + doc->offsetToLineCol(startOffset, line, col); + Index outLine, outCol; + doc->oneBasedUTF8LocToZeroBasedUTF16Loc(line, col, outLine, outCol); + hover.range.start.line = (int)outLine; + hover.range.start.character = (int)outCol; + hover.range.end.line = (int)outLine; + hover.range.end.character = (int)(outCol + identifier.getLength()); + StringBuilder sb; + sb << "```\n#define " << identifier; + if (def->params.getCount()) + { + sb << "("; + bool isFirst = true; + for (auto param : def->params) + { + if (!isFirst) + sb << ", "; + if (param.isVariadic) + sb << "..."; + else if (param.name) + sb << param.name->text; + isFirst = false; + } + sb << ")"; + } + for (auto& token : def->tokenList) + { + sb << " "; + sb << token.getContent(); + } + sb << "\n```\n\n"; + auto humaneLoc = + version->linkage->getSourceManager()->getHumaneLoc(def->loc, SourceLocType::Actual); + sb << "Defined in " << humaneLoc.pathInfo.foundPath << "(" << humaneLoc.line << ")\n"; + hover.contents.kind = "markdown"; + hover.contents.value = sb.ProduceString(); + m_connection->sendResult(&hover, responseId); + return SLANG_OK; +} + +SlangResult LanguageServer::tryGotoMacroDefinition( + WorkspaceVersion* version, DocumentVersion* doc, Index line, Index col, JSONValue responseId) +{ + Index startOffset = 0; + auto identifier = doc->peekIdentifier(line, col, startOffset); + if (identifier.getLength() == 0) + return SLANG_FAIL; + auto def = version->tryGetMacroDefinition(identifier); + if (!def) + return SLANG_FAIL; + auto humaneLoc = + version->linkage->getSourceManager()->getHumaneLoc(def->loc, SourceLocType::Actual); + LanguageServerProtocol::Location result; + result.uri = URI::fromLocalFilePath(humaneLoc.pathInfo.foundPath.getUnownedSlice()).uri; + Index outLine, outCol; + doc->oneBasedUTF8LocToZeroBasedUTF16Loc(humaneLoc.line, humaneLoc.column, outLine, outCol); + result.range.start.line = (int)outLine; + result.range.start.character = (int)outCol; + result.range.end.line = (int)outLine; + result.range.end.character = (int)(outCol + identifier.getLength()); + m_connection->sendResult(&result, responseId); + return SLANG_OK; +} + +SlangResult LanguageServer::tryGotoFileInclude( + WorkspaceVersion* version, DocumentVersion* doc, Index line, JSONValue responseId) +{ + auto lineContent = doc->getLine(line).trim(); + if (!lineContent.startsWith("#") || lineContent.indexOf(UnownedStringSlice("include")) == -1) + return SLANG_FAIL; + for (auto& include : version->linkage->contentAssistInfo.preprocessorInfo.fileIncludes) + { + auto includeLoc = + version->linkage->getSourceManager()->getHumaneLoc(include.loc, SourceLocType::Actual); + if (includeLoc.line == line && includeLoc.pathInfo.foundPath == doc->getPath()) + { + LanguageServerProtocol::Location result; + result.uri = URI::fromLocalFilePath(include.path.getUnownedSlice()).uri; + result.range.start.line = 0; + result.range.start.character = 0; + result.range.end.line = 0; + result.range.end.character = 0; + m_connection->sendResult(&result, responseId); + return SLANG_OK; + } + } + return SLANG_FAIL; +} + SlangResult LanguageServer::queueJSONCall(JSONRPCCall call) { Command cmd; @@ -943,7 +1138,7 @@ SlangResult LanguageServer::queueJSONCall(JSONRPCCall call) } else if (call.method == "completionItem/resolve") { - CompletionItem args; + Slang::LanguageServerProtocol::CompletionItem args; SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); cmd.completionResolveArgs = args; } @@ -1105,23 +1300,23 @@ SlangResult LanguageServer::execute() break; parseNextMessage(); } - auto parseTime = platform::PerformanceCounter::getElapsedTimeInSeconds(start); auto parseEnd = platform::PerformanceCounter::now(); processCommands(); - // Now we can use this time to reparse user's code, report diagnostics, etc. + + // Report diagnostics if it hasn't been updated for a while. update(); + auto workTime = platform::PerformanceCounter::getElapsedTimeInSeconds(parseEnd); if (commands.getCount() > 0 && m_initialized) { StringBuilder msgBuilder; - msgBuilder << "Server processed " << commands.getCount() << " commands, parsed in " - << String(int(parseTime * 1000)) << "ms, executed in " + msgBuilder << "Server processed " << commands.getCount() << " commands, executed in " << String(int(workTime * 1000)) << "ms"; logMessage(3, msgBuilder.ProduceString()); } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + m_connection->getUnderlyingConnection()->waitForResult(1000); } return SLANG_OK; diff --git a/source/slang/slang-language-server.h b/source/slang/slang-language-server.h index b873576e8..17dfc0f16 100644 --- a/source/slang/slang-language-server.h +++ b/source/slang/slang-language-server.h @@ -5,6 +5,7 @@ #include "../compiler-core/slang-json-rpc-connection.h" #include "slang-workspace-version.h" +#include "slang-language-server-completion.h" namespace Slang { @@ -75,6 +76,7 @@ private: public: bool m_initialized = false; + CommitCharacterBehavior m_commitCharacterBehavior = CommitCharacterBehavior::MembersOnly; RefPtr<JSONRPCConnection> m_connection; ComPtr<slang::IGlobalSession> m_session; RefPtr<Workspace> m_workspace; @@ -113,18 +115,41 @@ private: void resetDiagnosticUpdateTime(); void publishDiagnostics(); void updatePredefinedMacros(const JSONValue& macros); - void updateSearchPaths(const JSONValue& macros); - void updateSearchInWorkspace(const JSONValue& macros); + void updateSearchPaths(const JSONValue& value); + void updateSearchInWorkspace(const JSONValue& value); + void updateCommitCharacters(const JSONValue& value); void sendConfigRequest(); void registerCapability(const char* methodName); void logMessage(int type, String message); + SlangResult tryGetMacroHoverInfo( + WorkspaceVersion* version, + DocumentVersion* doc, + Index line, + Index col, + JSONValue responseId); + SlangResult tryGotoMacroDefinition( + WorkspaceVersion* version, + DocumentVersion* doc, + Index line, + Index col, + JSONValue responseId); + SlangResult tryGotoFileInclude( + WorkspaceVersion* version, + DocumentVersion* doc, + Index line, + JSONValue responseId); List<Command> commands; SlangResult queueJSONCall(JSONRPCCall call); SlangResult runCommand(Command& cmd); void processCommands(); }; +inline bool _isIdentifierChar(char ch) +{ + return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_'; +} + SLANG_API SlangResult runLanguageServer(); } // namespace Slang diff --git a/source/slang/slang-lookup.cpp b/source/slang/slang-lookup.cpp index a77478ccb..8c69e2013 100644 --- a/source/slang/slang-lookup.cpp +++ b/source/slang/slang-lookup.cpp @@ -2,6 +2,7 @@ #include "slang-lookup.h" #include "../compiler-core/slang-name.h" +#include "slang-check-impl.h" namespace Slang { @@ -199,27 +200,45 @@ static void _lookUpDirectAndTransparentMembers( { ContainerDecl* containerDecl = containerDeclRef.getDecl(); - // Ensure that the lookup dictionary in the container is up to date - if (!containerDecl->isMemberDictionaryValid()) + + if (request.isCompletionRequest()) { - buildMemberDictionary(containerDecl); + // If we are looking up for completion suggestions, + // return all the members that are available. + for (auto member : containerDecl->members) + { + if (!DeclPassesLookupMask(member, request.mask)) + continue; + AddToLookupResult( + result, + CreateLookupResultItem( + DeclRef<Decl>(member, containerDeclRef.substitutions), inBreadcrumbs)); + } } + else + { + // Ensure that the lookup dictionary in the container is up to date + if (!containerDecl->isMemberDictionaryValid()) + { + buildMemberDictionary(containerDecl); + } - // Look up the declarations with the chosen name in the container. - Decl* firstDecl = nullptr; - containerDecl->memberDictionary.TryGetValue(name, firstDecl); + // Look up the declarations with the chosen name in the container. + Decl* firstDecl = nullptr; + containerDecl->memberDictionary.TryGetValue(name, firstDecl); - // Now iterate over those declarations (if any) and see if - // we find any that meet our filtering criteria. - // For example, we might be filtering so that we only consider - // type declarations. - for (auto m = firstDecl; m; m = m->nextInContainerWithSameName) - { - if (!DeclPassesLookupMask(m, request.mask)) - continue; + // Now iterate over those declarations (if any) and see if + // we find any that meet our filtering criteria. + // For example, we might be filtering so that we only consider + // type declarations. + for (auto m = firstDecl; m; m = m->nextInContainerWithSameName) + { + if (!DeclPassesLookupMask(m, request.mask)) + continue; - // The declaration passed the test, so add it! - AddToLookupResult(result, CreateLookupResultItem(DeclRef<Decl>(m, containerDeclRef.substitutions), inBreadcrumbs)); + // The declaration passed the test, so add it! + AddToLookupResult(result, CreateLookupResultItem(DeclRef<Decl>(m, containerDeclRef.substitutions), inBreadcrumbs)); + } } // TODO(tfoley): should we look up in the transparent decls @@ -937,9 +956,11 @@ static void _lookUpInScopes( if (result.isValid()) { - // If it's overloaded or the decl we have is of an overloadable type then we just keep going + // If it's overloaded or the decl we have is of an overloadable type, or if we are + // looking up for completion suggestions then we just keep going if (result.isOverloaded() || - _isDeclOverloadable(result.item.declRef.getDecl())) + _isDeclOverloadable(result.item.declRef.getDecl()) || + ((int32_t)request.options & (int32_t)LookupOptions::Completion) != 0) { continue; } @@ -964,21 +985,14 @@ LookupResult lookUp( request.semantics = semantics; request.scope = scope; request.mask = mask; - + if (semantics && semantics->getSession() && + name == semantics->getSession()->getCompletionRequestTokenName()) + request.options = (LookupOptions)((int)request.options | (int)LookupOptions::Completion); LookupResult result; _lookUpInScopes(astBuilder, name, request, result); return result; } -void lookUpMemberImpl( - ASTBuilder* astBuilder, - SemanticsVisitor* semantics, - Name* name, - Type* type, - LookupResult& ioResult, - BreadcrumbInfo* inBreadcrumbs, - LookupMask mask); - LookupResult lookUpMember( ASTBuilder* astBuilder, SemanticsVisitor* semantics, @@ -991,6 +1005,9 @@ LookupResult lookUpMember( request.semantics = semantics; request.mask = mask; request.options = options; + if (semantics && semantics->getSession() && + name == semantics->getSession()->getCompletionRequestTokenName()) + request.options = (LookupOptions)((int)request.options | (int)LookupOptions::Completion); LookupResult result; _lookUpMembersInType(astBuilder, name, type, request, result, nullptr); diff --git a/source/slang/slang-mangle.cpp b/source/slang/slang-mangle.cpp index ca6dcecd7..c230776db 100644 --- a/source/slang/slang-mangle.cpp +++ b/source/slang/slang-mangle.cpp @@ -37,11 +37,8 @@ namespace Slang context->sb.append(value); } - void emitName( - ManglingContext* context, - Name* name) + void emitNameImpl(ManglingContext* context, UnownedStringSlice str) { - String str = getText(name); Index length = str.getLength(); // If the name consists of only traditional "identifer characters" @@ -50,10 +47,14 @@ namespace Slang bool allAllowed = true; for (auto c : str) { - if (('a' <= c) && (c <= 'z')) continue; - if (('A' <= c) && (c <= 'Z')) continue; - if (('0' <= c) && (c <= '9')) continue; - if (c == '_') continue; + if (('a' <= c) && (c <= 'z')) + continue; + if (('A' <= c) && (c <= 'Z')) + continue; + if (('0' <= c) && (c <= '9')) + continue; + if (c == '_') + continue; allAllowed = false; break; @@ -84,9 +85,8 @@ namespace Slang // for (auto c : str) { - if (('a' <= c) && (c <= 'z') - || ('A' <= c) && (c <= 'Z') - || ('0' <= c) && (c <= '9')) + if (('a' <= c) && (c <= 'z') || ('A' <= c) && (c <= 'Z') || + ('0' <= c) && (c <= '9')) { encoded.append(c); } @@ -119,6 +119,14 @@ namespace Slang // target, rather than adding complexity here. } + void emitName( + ManglingContext* context, + Name* name) + { + String str = getText(name); + emitNameImpl(context, str.getUnownedSlice()); + } + void emitVal( ManglingContext* context, Val* val); @@ -593,6 +601,13 @@ namespace Slang return context.sb.ProduceString(); } + String getMangledNameFromNameString(const UnownedStringSlice& name) + { + ManglingContext context(nullptr); + emitNameImpl(&context, name); + return context.sb.ProduceString(); + } + String getHashedName(const UnownedStringSlice& mangledName) { HashCode64 hash = getStableHashCode64(mangledName.begin(), mangledName.getLength()); diff --git a/source/slang/slang-mangle.h b/source/slang/slang-mangle.h index e579ebfda..723b7250e 100644 --- a/source/slang/slang-mangle.h +++ b/source/slang/slang-mangle.h @@ -13,6 +13,7 @@ namespace Slang String getMangledName(ASTBuilder* astBuilder, Decl* decl); String getMangledName(ASTBuilder* astBuilder, DeclRef<Decl> const & declRef); String getMangledName(ASTBuilder* astBuilder, DeclRefBase const & declRef); + String getMangledNameFromNameString(const UnownedStringSlice& name); String getHashedName(const UnownedStringSlice& mangledName); diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index c8a990ab1..f6d85ade9 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -854,6 +854,8 @@ namespace Slang { parser->ReadToken(TokenType::Scope); } + if (parser->LookAheadToken(TokenType::CompletionRequest)) + return parser->ReadToken(); const Token firstIdentifier = parser->ReadToken(TokenType::Identifier); if (initialTokenType != TokenType::Scope && parser->tokenReader.peekTokenType() != TokenType::Scope) @@ -942,6 +944,7 @@ namespace Slang break; parser->ReadToken(TokenType::Comma); + } if (hasDoubleBracket) @@ -1133,6 +1136,7 @@ namespace Slang auto decl = parser->astBuilder->create<ImportDecl>(); decl->scope = parser->currentScope; + decl->startLoc = parser->tokenReader.peekLoc(); if (peekTokenType(parser) == TokenType::StringLiteral) { @@ -1162,7 +1166,7 @@ namespace Slang decl->moduleNameAndLoc = moduleNameAndLoc; } - + decl->endLoc = parser->tokenReader.peekLoc(); parser->ReadToken(TokenType::Semicolon); return decl; @@ -1538,7 +1542,10 @@ namespace Slang _parseOptSemantics(parser, decl); decl->body = parseOptBody(parser); - + if (auto block = as<BlockStmt>(decl->body)) + { + decl->closingSourceLoc = block->closingSourceLoc; + } parser->PopScope(); return decl; @@ -2686,6 +2693,13 @@ namespace Slang semantic->name = parser->ReadToken(TokenType::Identifier); return semantic; } + else if (parser->LookAheadToken(TokenType::CompletionRequest)) + { + HLSLSimpleSemantic* semantic = parser->astBuilder->create<HLSLSimpleSemantic>(); + parser->FillPosition(semantic); + semantic->name = parser->ReadToken(); + return semantic; + } else { // expect an identifier, just to produce an error message @@ -2966,6 +2980,9 @@ namespace Slang { InterfaceDecl* decl = parser->astBuilder->create<InterfaceDecl>(); parser->FillPosition(decl); + + AdvanceIf(parser, TokenType::CompletionRequest); + decl->nameAndLoc = NameLoc(parser->ReadToken(TokenType::Identifier)); parseOptionalInheritanceClause(parser, decl); @@ -3147,6 +3164,9 @@ namespace Slang decl->body = parseOptBody(parser); + if (auto block = as<BlockStmt>(decl->body)) + decl->closingSourceLoc = block->closingSourceLoc; + parser->PopScope(); return decl; } @@ -3469,6 +3489,8 @@ namespace Slang decl->returnType = parser->ParseTypeExp(); } decl->body = parseOptBody(parser); + if (auto blockStmt = as<BlockStmt>(decl->body)) + decl->closingSourceLoc = blockStmt->closingSourceLoc; parser->PopScope(); return decl; }); @@ -3914,9 +3936,11 @@ namespace Slang ParseSquareBracketAttributes(this, &modifierLink); } + // Skip completion request token to prevent producing a type named completion request. + AdvanceIf(this, TokenType::CompletionRequest); + // TODO: support `struct` declaration without tag rs->nameAndLoc = expectIdentifier(this); - return parseOptGenericDecl(this, [&](GenericDecl*) { // We allow for an inheritance clause on a `struct` @@ -3931,6 +3955,9 @@ namespace Slang { ClassDecl* rs = astBuilder->create<ClassDecl>(); ReadToken("class"); + + AdvanceIf(this, TokenType::CompletionRequest); + FillPosition(rs); rs->nameAndLoc = expectIdentifier(this); @@ -3969,6 +3996,8 @@ namespace Slang // AdvanceIf(parser, "class"); + AdvanceIf(parser, TokenType::CompletionRequest); + parser->FillPosition(decl); decl->nameAndLoc = expectIdentifier(parser); @@ -5586,7 +5615,7 @@ namespace Slang return constExpr; } - + case TokenType::CompletionRequest: case TokenType::Identifier: { // We will perform name lookup here so that we can find syntax @@ -5608,7 +5637,7 @@ namespace Slang varExpr->scope = parser->currentScope; parser->FillPosition(varExpr); - auto nameAndLoc = expectIdentifier(parser); + auto nameAndLoc = NameLoc(parser->ReadToken()); varExpr->name = nameAndLoc.name; if(peekTokenType(parser) == TokenType::OpLess) @@ -5725,7 +5754,7 @@ namespace Slang parser->ReadToken(TokenType::Dot); parser->FillPosition(memberExpr); memberExpr->name = expectIdentifier(parser).name; - + if (peekTokenType(parser) == TokenType::OpLess) expr = maybeParseGenericApp(parser, memberExpr); else diff --git a/source/slang/slang-preprocessor.cpp b/source/slang/slang-preprocessor.cpp index ea0b06a31..0167b4a58 100644 --- a/source/slang/slang-preprocessor.cpp +++ b/source/slang/slang-preprocessor.cpp @@ -556,7 +556,6 @@ private: // and a macro *invocation*, similar to how we distinguish a function definition // from a call to that function. - /// A definition of a macro struct MacroDefinition { @@ -721,6 +720,10 @@ struct MacroInvocation : InputStream Index getArgCount() { return m_args.getCount(); } + SourceLoc getInvocationLoc() { return m_macroInvocationLoc; } + + MacroDefinition* getMacroDefinition() { return m_macro; } + private: // Macro invocations are created as part of applying macro expansion // to a stream, so the `ExpansionInputStream` type takes responsibility @@ -944,6 +947,8 @@ struct InputFile ExpansionInputStream* getExpansionStream() { return m_expansionStream; } + bool isIncludedFile() { return m_parent != nullptr; } + private: friend struct Preprocessor; @@ -1010,6 +1015,9 @@ struct Preprocessor /// Stores the initiating macro source location. SourceLoc initiatingMacroSourceLoc; + /// Stores macro definition and invocation info for language server. + PreprocessorContentAssistInfo* contentAssistInfo = nullptr; + NamePool* getNamePool() { return namePool; } SourceManager* getSourceManager() { return sourceManager; } @@ -1020,7 +1028,51 @@ struct Preprocessor void popInputFile(); }; +static void reportMacroDefinitionForContentAssist(Preprocessor* preprocessor, MacroDefinition* def) +{ + if (!preprocessor->contentAssistInfo) + return; + + MacroDefinitionContentAssistInfo info; + info.name = def->getName(); + info.loc = def->getLoc(); + info.tokenList = def->tokens.m_tokens; + for (auto param : def->params) + { + MacroDefinitionContentAssistInfo::Param p; + p.isVariadic = param.isVariadic; + p.name = param.nameLoc.name; + info.params.add(p); + } + preprocessor->contentAssistInfo->macroDefinitions.add(info); +} + +static void reportMacroInvocationForContentAssist( + Preprocessor* preprocessor, MacroInvocation* invocation) +{ + if (!preprocessor->contentAssistInfo) + return; + if (preprocessor->m_currentInputFile && preprocessor->m_currentInputFile->isIncludedFile()) + return; + MacroInvocationContentAssistInfo info; + info.name = invocation->getMacroDefinition()->getName(); + info.loc = invocation->getInvocationLoc(); + + preprocessor->contentAssistInfo->macroInvocations.add(info); +} +static void reportIncludeFileForContentAssist(Preprocessor* preprocessor, Token token, String path) +{ + if (!preprocessor->contentAssistInfo) + return; + if (preprocessor->m_currentInputFile && preprocessor->m_currentInputFile->isIncludedFile()) + return; + FileIncludeContentAssistInfo info; + info.loc = token.loc; + info.length = (int)token.getContentLength(); + info.path = path; + preprocessor->contentAssistInfo->fileIncludes.add(info); +} //static Token AdvanceToken(Preprocessor* preprocessor); @@ -1127,6 +1179,8 @@ void MacroInvocation::prime(MacroInvocation* nextBusyMacroInvocation) _initCurrentOpStream(); m_lookaheadToken = _readTokenImpl(); + + reportMacroInvocationForContentAssist(m_preprocessor, this); } void ExpansionInputStream::_pushMacroInvocation( @@ -2969,6 +3023,8 @@ static void HandleIncludeDirective(PreprocessorDirectiveContext* context) return; } + reportIncludeFileForContentAssist(context->m_preprocessor, pathToken, filePathInfo.foundPath); + // Do all checking related to the end of this directive before we push a new stream, // just to avoid complications where that check would need to deal with // a switch of input stream @@ -3318,6 +3374,8 @@ static void HandleDefineDirective(PreprocessorDirectiveContext* context) } _parseMacroOps(context->m_preprocessor, macro, mapParamNameToIndex); + + reportMacroDefinitionForContentAssist(context->m_preprocessor, macro); } // Handle a `#undef` directive @@ -3848,6 +3906,7 @@ static void DefineMacro( } preprocessor->globalEnv.macros[keyName] = macro; + reportMacroDefinitionForContentAssist(preprocessor, macro); } // read the entire input into tokens @@ -3945,6 +4004,10 @@ TokenList preprocessSource( desc.namePool = linkage->getNamePool(); desc.sourceManager = linkage->getSourceManager(); + if (linkage->isInLanguageServer()) + { + desc.contentAssistInfo = &linkage->contentAssistInfo.preprocessorInfo; + } return preprocessSource(file, desc); } @@ -3963,6 +4026,7 @@ TokenList preprocessSource( preprocessor.endOfFileToken.type = TokenType::EndOfFile; preprocessor.endOfFileToken.flags = TokenFlag::AtStartOfLine; + preprocessor.contentAssistInfo = desc.contentAssistInfo; // Add builtin macros { diff --git a/source/slang/slang-preprocessor.h b/source/slang/slang-preprocessor.h index 5f66be405..4d7721d31 100644 --- a/source/slang/slang-preprocessor.h +++ b/source/slang/slang-preprocessor.h @@ -11,6 +11,7 @@ namespace Slang { class DiagnosticSink; class Linkage; +struct PreprocessorContentAssistInfo; namespace preprocessor { @@ -53,6 +54,9 @@ struct PreprocessorDesc /// Optional: handler for callbacks invoked during preprocessing PreprocessorHandler* handler = nullptr; + + /// Optional: additional information for code assist. + PreprocessorContentAssistInfo* contentAssistInfo = nullptr; }; /// Take a source `file` and preprocess it into a list of tokens. diff --git a/source/slang/slang-workspace-version.cpp b/source/slang/slang-workspace-version.cpp index 781d8a3bc..0930ca0de 100644 --- a/source/slang/slang-workspace-version.cpp +++ b/source/slang/slang-workspace-version.cpp @@ -3,6 +3,8 @@ #include "../core/slang-file-system.h" #include "../compiler-core/slang-lexer.h" #include "slang-serialize-container.h" +#include "slang-mangle.h" +#include "slang-check-impl.h" namespace Slang { @@ -30,10 +32,9 @@ DocumentVersion* Workspace::openDoc(String path, String text) { RefPtr<DocumentVersion> doc = new DocumentVersion(); doc->setText(text.getUnownedSlice()); - doc->setURI(URI::fromLocalFilePath(path.getUnownedSlice())); + doc->setPath(path); openedDocuments[path] = doc; workspaceSearchPaths.Add(Path::getParentDirectory(path)); - moduleCache.invalidate(path); invalidate(); return doc.Ptr(); } @@ -51,15 +52,18 @@ void Workspace::changeDoc(const String& path, LanguageServerProtocol::Range rang auto originalText = doc->getText().getUnownedSlice(); StringBuilder newText; newText << originalText.head(startOffset) << text << originalText.tail(endOffset); - doc->setText(newText.ProduceString()); + changeDoc(doc.Ptr(), newText.ProduceString()); } - moduleCache.invalidate(path); +} + +void Workspace::changeDoc(DocumentVersion* doc, const String& newText) +{ + doc->setText(newText); invalidate(); } void Workspace::closeDoc(const String& path) { - moduleCache.invalidate(path); openedDocuments.Remove(path); invalidate(); } @@ -269,7 +273,6 @@ RefPtr<WorkspaceVersion> Workspace::createWorkspaceVersion() slang::SessionDesc desc = {}; desc.fileSystem = this; desc.targetCount = 1; - desc.flags = slang::kSessionFlag_LanguageServer; slang::TargetDesc targetDesc = {}; targetDesc.profile = slangGlobalSession->findProfile("sm_6_6"); desc.targets = &targetDesc; @@ -308,13 +311,7 @@ RefPtr<WorkspaceVersion> Workspace::createWorkspaceVersion() ComPtr<slang::ISession> session; slangGlobalSession->createSession(desc, session.writeRef()); version->linkage = static_cast<Linkage*>(session.get()); - // TODO(yong): module cache does improves performance by 30%. However there are some issues - // that prevents the deserialization to resolve the imported decls from the correct module. - // This doesn't lead to crash, but may cause problems. We can enable this when the issues - // are fixed. -#if 0 - version->linkage->setModuleCache(&moduleCache); -#endif + version->linkage->contentAssistInfo.checkingMode = ContentAssistCheckingMode::General; return version; } @@ -337,7 +334,13 @@ WorkspaceVersion* Workspace::getCurrentVersion() currentVersion = createWorkspaceVersion(); return currentVersion.Ptr(); } - +WorkspaceVersion* Workspace::createVersionForCompletion() +{ + currentCompletionVersion = createWorkspaceVersion(); + currentCompletionVersion->linkage->contentAssistInfo.checkingMode = + ContentAssistCheckingMode::Completion; + return currentCompletionVersion.Ptr(); +} void* Workspace::getInterface(const Guid& uuid) { if (uuid == ISlangUnknown::getTypeGuid() || uuid == ISlangFileSystem::getTypeGuid()) @@ -416,6 +419,21 @@ static bool _isIdentifierChar(char ch) return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || ch == '_'; } +UnownedStringSlice DocumentVersion::peekIdentifier(Index& offset) +{ + Index start = offset; + Index end = offset; + while (start >= 0 && _isIdentifierChar(text[start])) + start--; + while (end < text.getLength() && _isIdentifierChar(text[end])) + end++; + offset = start + 1; + if (end > offset) + return text.getUnownedSlice().subString(start + 1, end - start - 1); + return UnownedStringSlice(""); +} + + int DocumentVersion::getTokenLength(Index line, Index col) { auto offset = getOffset(line, col); @@ -454,8 +472,19 @@ Module* WorkspaceVersion::getOrLoadModule(String path) return nullptr; ComPtr<ISlangBlob> diagnosticBlob; RefPtr<StringBlob> sourceBlob = new StringBlob((*doc)->getText()); + auto moduleName = getMangledNameFromNameString(path.getUnownedSlice()); + linkage->contentAssistInfo.primaryModuleName = linkage->getNamePool()->getName(moduleName); + linkage->contentAssistInfo.primaryModulePath = path; + // Note: + // The module at `path` may have already been loaded into the linkage previously + // due to an `import`. However that module won't get fully checked in when the checker + // is in language server mode to speed things up. + // Therefore, we always call `loadModuleFromSource` to load a fresh module instead of + // trying to reuse the existing one through `findOrImportModule`, this will result in + // redundant parsing and storage, but it saves us from the hassle of handling + // incremental/lazy checking on a previously loaded module. auto parsedModule = linkage->loadModuleFromSource( - Path::getFileNameWithoutExt(path).getBuffer(), + moduleName.getBuffer(), path.getBuffer(), sourceBlob.Ptr(), diagnosticBlob.writeRef()); @@ -474,63 +503,22 @@ Module* WorkspaceVersion::getOrLoadModule(String path) return static_cast<Module*>(parsedModule); } -RefPtr<Module> SerializedModuleCache::tryLoadModule( - Linkage* linkage, String filePath) +MacroDefinitionContentAssistInfo* WorkspaceVersion::tryGetMacroDefinition(UnownedStringSlice name) { - Path::getCanonical(filePath, filePath); - if (List<uint8_t>* rawData = serializedModules.TryGetValue(filePath)) + if (macroDefinitions.Count() == 0) { - RefPtr<MemoryStreamBase> memStream = - new MemoryStreamBase(FileAccess::Read, rawData->getBuffer(), rawData->getCount()); - RiffContainer riffContainer; - RiffUtil::read(memStream.Ptr(), riffContainer); - SerialContainerData outData; - SerialContainerUtil::ReadOptions options; - options.linkage = linkage; - options.namePool = linkage->getNamePool(); - options.session = linkage->getSessionImpl(); - options.sharedASTBuilder = linkage->getASTBuilder()->getSharedASTBuilder(); - options.astBuilder = linkage->getASTBuilder(); - DiagnosticSink sink(linkage->getSourceManager(), Lexer::sourceLocationLexer); - options.sink = &sink; - options.sourceManager = linkage->getSourceManager(); - SLANG_RETURN_NULL_ON_FAIL(SerialContainerUtil::read(&riffContainer, options, outData)); - if (outData.modules.getCount() == 1) + // build dictionary. + for (auto& def : linkage->contentAssistInfo.preprocessorInfo.macroDefinitions) { - RefPtr<Module> module = new Module(linkage, linkage->getASTBuilder()); - auto moduleDecl = as<ModuleDecl>(outData.modules[0].astRootNode); - if (moduleDecl) - { - moduleDecl->module = module.Ptr(); - module->setModuleDecl(moduleDecl); - return module; - } + macroDefinitions[def.name] = &def; } } - return nullptr; -} - -void SerializedModuleCache::storeModule( - Linkage* linkage, String filePath, RefPtr<Module> module) -{ - Path::getCanonical(filePath, filePath); - RiffContainer container; - SerialContainerUtil::WriteOptions options; - options.sourceManager = linkage->getSourceManager(); - options.compressionType = SerialCompressionType::None; - options.optionFlags = SerialOptionFlag::SourceLocation | SerialOptionFlag::ASTModule; - SerialContainerData data; - SerialContainerData::Module moduleData; - moduleData.astBuilder = linkage->getASTBuilder(); - moduleData.astRootNode = module->getModuleDecl(); - moduleData.irModule = nullptr; - data.modules.add(moduleData); - SerialContainerUtil::write(data, options, &container); - RefPtr<OwnedMemoryStream> memStream = new OwnedMemoryStream(FileAccess::Write); - RiffUtil::write(&container, memStream); - List<uint8_t> rawData; - memStream->swapContents(rawData); - serializedModules[filePath] = _Move(rawData); + MacroDefinitionContentAssistInfo* result = nullptr; + auto namePtr = linkage->getNamePool()->tryGetName(name); + if (!namePtr) + return nullptr; + macroDefinitions.TryGetValue(namePtr, result); + return result; } } // namespace Slang diff --git a/source/slang/slang-workspace-version.h b/source/slang/slang-workspace-version.h index dea914bb5..38abb6ef2 100644 --- a/source/slang/slang-workspace-version.h +++ b/source/slang/slang-workspace-version.h @@ -17,15 +17,18 @@ namespace Slang { private: URI uri; + String path; String text; List<UnownedStringSlice> lines; List<List<Index>> utf16CharStarts; public: - void setURI(URI newURI) + void setPath(String filePath) { - uri = newURI; + path = filePath; + uri = URI::fromLocalFilePath(path.getUnownedSlice()); } URI getURI() { return uri; } + String getPath() { return path; } const String& getText() { return text; } void setText(const String& newText); @@ -39,6 +42,14 @@ namespace Slang // Get starting offset of line. Index getLineStart(UnownedStringSlice line) { return line.begin() - text.begin(); } + UnownedStringSlice peekIdentifier(Index line, Index col, Index& offset) + { + offset = getOffset(line, col); + return peekIdentifier(offset); + } + + UnownedStringSlice peekIdentifier(Index& offset); + // Get offset from 1-based, utf-8 encoding location. Index getOffset(Index lineIndex, Index colIndex) { @@ -95,33 +106,20 @@ namespace Slang String originalOutput; }; - - class SerializedModuleCache - : public RefObject - , public IModuleCache - { - public: - Dictionary<String, List<uint8_t>> serializedModules; - - void invalidate(const String& path) { serializedModules.Remove(path); } - virtual RefPtr<Module> tryLoadModule(Linkage* linkage, String filePath) override; - virtual void storeModule(Linkage* linkage, String filePath, RefPtr<Module> module) override; - }; - class WorkspaceVersion : public RefObject { private: Dictionary<String, Module*> modules; Dictionary<ModuleDecl*, RefPtr<ASTMarkup>> markupASTs; + Dictionary<Name*, MacroDefinitionContentAssistInfo*> macroDefinitions; void parseDiagnostics(String compilerOutput); public: Workspace* workspace; RefPtr<Linkage> linkage; Dictionary<String, DocumentDiagnostics> diagnostics; - List<Decl*> currentCompletionItems; ASTMarkup* getOrCreateMarkupAST(ModuleDecl* module); - Module* getOrLoadModule(String path); + MacroDefinitionContentAssistInfo* tryGetMacroDefinition(UnownedStringSlice name); }; struct OwnedPreprocessorMacroDefinition @@ -135,19 +133,21 @@ namespace Slang { private: RefPtr<WorkspaceVersion> currentVersion; + RefPtr<WorkspaceVersion> currentCompletionVersion; RefPtr<WorkspaceVersion> createWorkspaceVersion(); public: List<String> rootDirectories; List<String> additionalSearchPaths; OrderedHashSet<String> workspaceSearchPaths; List<OwnedPreprocessorMacroDefinition> predefinedMacros; - SerializedModuleCache moduleCache; bool searchInWorkspace = true; slang::IGlobalSession* slangGlobalSession; Dictionary<String, RefPtr<DocumentVersion>> openedDocuments; DocumentVersion* openDoc(String path, String text); void changeDoc(const String& path, LanguageServerProtocol::Range range, const String& text); + void changeDoc(DocumentVersion* doc, const String& newText); + void closeDoc(const String& path); // Update predefined macro settings. Returns true if the new settings are different from existing ones. @@ -158,7 +158,8 @@ namespace Slang void init(List<URI> rootDirURI, slang::IGlobalSession* globalSession); void invalidate(); WorkspaceVersion* getCurrentVersion(); - + WorkspaceVersion* getCurrentCompletionVersion() { return currentCompletionVersion.Ptr(); } + WorkspaceVersion* createVersionForCompletion(); public: // Inherited via ISlangFileSystem SLANG_COM_OBJECT_IUNKNOWN_ALL diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 02b1efedf..13ef0cd81 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -133,6 +133,7 @@ void Session::init() // Initialize name pool getNamePool()->setRootNamePool(getRootNamePool()); + m_completionTokenName = getNamePool()->getName("#?"); m_sharedLibraryLoader = DefaultSharedLibraryLoader::getSingleton(); @@ -933,7 +934,7 @@ SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModule( { DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer); - if (m_flag & slang::kSessionFlag_LanguageServer) + if (isInLanguageServer()) { sink.setFlags(DiagnosticSink::Flag::HumaneLoc | DiagnosticSink::Flag::LanguageServer); } @@ -962,7 +963,7 @@ SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModuleFromSource( slang::IBlob** outDiagnostics) { DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer); - if (m_flag & slang::kSessionFlag_LanguageServer) + if (isInLanguageServer()) { sink.setFlags(DiagnosticSink::Flag::HumaneLoc | DiagnosticSink::Flag::LanguageServer); } @@ -2611,11 +2612,6 @@ void Linkage::loadParsedModule( } } loadedModulesList.add(loadedModule); - - if (m_moduleCache) - { - m_moduleCache->storeModule(this, pathInfo.foundPath, loadedModule); - } } Module* Linkage::loadModule(String const& name) @@ -2638,7 +2634,7 @@ void Linkage::_diagnoseErrorInImportedModule( { sink->diagnose(info->importLoc, Diagnostics::errorInImportedModule, info->name); } - if ((m_flag & slang::kSessionFlag_LanguageServer) == 0) + if (!isInLanguageServer()) { sink->diagnose(SourceLoc(), Diagnostics::complationCeased); } @@ -2799,19 +2795,6 @@ RefPtr<Module> Linkage::findOrImportModule( if (mapPathToLoadedModule.TryGetValue(filePathInfo.getMostUniqueIdentity(), loadedModule)) return loadedModule; - // Is this module in user provided cache? - // (yong): module cache is intended to speed up language server reparsing. - // currently it is *not* enabled in language server. - if (m_moduleCache) - { - loadedModule = m_moduleCache->tryLoadModule(this, filePathInfo.foundPath); - if (loadedModule) - { - mapPathToLoadedModule[filePathInfo.getMostUniqueIdentity()] = loadedModule; - return loadedModule; - } - } - // Try to load it ComPtr<ISlangBlob> fileContents; if(SLANG_FAILED(includeSystem.loadFile(filePathInfo, fileContents))) diff --git a/tests/language-server/generic-func-type-arg.slang b/tests/language-server/generic-func-type-arg.slang new file mode 100644 index 000000000..37c44de03 --- /dev/null +++ b/tests/language-server/generic-func-type-arg.slang @@ -0,0 +1,8 @@ +//TEST:LANG_SERVER: +struct MyType{}; +void m() +{ + MyType b; +//HOVER:7,19 + reinterpret<MyType, MyType>(b); +} diff --git a/tests/language-server/generic-func-type-arg.slang.expected.txt b/tests/language-server/generic-func-type-arg.slang.expected.txt new file mode 100644 index 000000000..b8cae46b7 --- /dev/null +++ b/tests/language-server/generic-func-type-arg.slang.expected.txt @@ -0,0 +1,12 @@ +-------- +range: 6,16 - 6,22 +content: +``` +struct MyType +``` + + + +{REDACTED}.slang(2) + + diff --git a/tests/language-server/member-completion-broken-syntax-3.slang b/tests/language-server/member-completion-broken-syntax-3.slang new file mode 100644 index 000000000..316afc541 --- /dev/null +++ b/tests/language-server/member-completion-broken-syntax-3.slang @@ -0,0 +1,9 @@ +//TEST:LANG_SERVER: +//COMPLETE:7,9 + +float3 m() +{ + float3 val = 0; + val. + return val; +}
\ No newline at end of file diff --git a/tests/language-server/member-completion-broken-syntax-3.slang.expected.txt b/tests/language-server/member-completion-broken-syntax-3.slang.expected.txt new file mode 100644 index 000000000..e90d634d8 --- /dev/null +++ b/tests/language-server/member-completion-broken-syntax-3.slang.expected.txt @@ -0,0 +1,5 @@ +-------- +x: 6 float ,.;:()[]<>{}*&^%!-=+|/? +y: 6 float ,.;:()[]<>{}*&^%!-=+|/? +z: 6 float ,.;:()[]<>{}*&^%!-=+|/? + diff --git a/tests/language-server/member-completion-broken-syntax-4.slang b/tests/language-server/member-completion-broken-syntax-4.slang new file mode 100644 index 000000000..5c89182ee --- /dev/null +++ b/tests/language-server/member-completion-broken-syntax-4.slang @@ -0,0 +1,14 @@ +//TEST:LANG_SERVER: +//COMPLETE:12,7 +struct MyType +{ + int member = 0; + int getSum() { return 0; } +} + +void m() +{ + MyType t; + t. + t.member = 2; +} diff --git a/tests/language-server/member-completion-broken-syntax-4.slang.expected.txt b/tests/language-server/member-completion-broken-syntax-4.slang.expected.txt new file mode 100644 index 000000000..dd13b81f6 --- /dev/null +++ b/tests/language-server/member-completion-broken-syntax-4.slang.expected.txt @@ -0,0 +1,4 @@ +-------- +member: 6 ,.;:()[]<>{}*&^%!-=+|/? +getSum: 2 ,.;:()[]<>{}*&^%!-=+|/? + |
