diff options
| author | Yong He <yonghe@outlook.com> | 2022-06-07 14:10:49 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-06-07 14:10:49 -0700 |
| commit | 0c64995ea28febcc7d38e1519da8d93391ce2e7d (patch) | |
| tree | 8696ab86b29caf80c3ebbd205c700e24b8c20bf3 /source/slang | |
| parent | 8c4a15c522861d2f30eacc9cd2b03ad793018639 (diff) | |
Major language server features. (#2264)
* Major language server features.
* Include slangd in binary release.
* Fix compiler issues.
* Fix compiler error.
* Completion resolve.
* Various improvements.
* Update diagnostic test expected output.
* Bug fix for source locations.
* Adjust diagnostic update frequency.
* Update github actions to store artifacts.
* Fix infinite parser loop.
* Fix parser recovery.
* Fix parser recovery.
* Update test.
* Fix test.
* Disable IR gen for language server.
* Allow commit characters in auto completion.
* Fix lookup for invoke exprs.
* More parser robustness fixes.
* update solution file
Co-authored-by: Yong He <yhe@nvidia.com>
Diffstat (limited to 'source/slang')
30 files changed, 4458 insertions, 75 deletions
diff --git a/source/slang/slang-ast-decl.h b/source/slang/slang-ast-decl.h index 9c95f3520..433d42d4a 100644 --- a/source/slang/slang-ast-decl.h +++ b/source/slang/slang-ast-decl.h @@ -23,6 +23,7 @@ class ContainerDecl: public Decl SLANG_ABSTRACT_AST_CLASS(ContainerDecl) List<Decl*> members; + SourceLoc closingSourceLoc; template<typename T> FilteredMemberList<T> getMembersOfType() diff --git a/source/slang/slang-ast-expr.h b/source/slang/slang-ast-expr.h index f2fae7ced..647eb37a4 100644 --- a/source/slang/slang-ast-expr.h +++ b/source/slang/slang-ast-expr.h @@ -7,8 +7,13 @@ namespace Slang { // Syntax class definitions for expressions. - -// Base class for expressions that will reference declarations +// + // A placeholder for where an Expr is expected but is missing from source. +class IncompleteExpr : public Expr +{ + SLANG_AST_CLASS(IncompleteExpr) +}; + // Base class for expressions that will reference declarations class DeclRefExpr: public Expr { SLANG_ABSTRACT_AST_CLASS(DeclRefExpr) @@ -20,6 +25,9 @@ class DeclRefExpr: public Expr // The name of the symbol being referenced Name* name = nullptr; + // The original expr before DeclRef resolution. + Expr* originalExpr = nullptr; + SLANG_UNREFLECTED // The scope in which to perform lookup Scope* scope = nullptr; @@ -137,6 +145,13 @@ class AppExprBase : public ExprWithArgsBase SLANG_ABSTRACT_AST_CLASS(AppExprBase) Expr* functionExpr = nullptr; + + // The original function expr before overload resolution. + Expr* originalFunctionExpr = nullptr; + + // The source location of `(`, `)`, and `,` that marks the start/end of the application op and + // each argument expr. This info is used by language server. + List<SourceLoc> argumentDelimeterLocs; }; class InvokeExpr: public AppExprBase @@ -196,6 +211,7 @@ class MemberExpr: public DeclRefExpr { SLANG_AST_CLASS(MemberExpr) Expr* baseExpression = nullptr; + SourceLoc memberOperatorLoc; }; // Member looked up on a type, rather than a value @@ -203,6 +219,7 @@ class StaticMemberExpr: public DeclRefExpr { SLANG_AST_CLASS(StaticMemberExpr) Expr* baseExpression = nullptr; + SourceLoc memberOperatorLoc; }; struct MatrixCoord @@ -220,6 +237,7 @@ class MatrixSwizzleExpr : public Expr Expr* base = nullptr; int elementCount; MatrixCoord elementCoords[4]; + SourceLoc memberOpLoc; }; class SwizzleExpr: public Expr @@ -228,6 +246,7 @@ class SwizzleExpr: public Expr Expr* base = nullptr; int elementCount; int elementIndices[4]; + SourceLoc memberOpLoc; }; // A dereference of a pointer or pointer-like type diff --git a/source/slang/slang-ast-print.cpp b/source/slang/slang-ast-print.cpp index 3ffb481c6..3608e44f1 100644 --- a/source/slang/slang-ast-print.cpp +++ b/source/slang/slang-ast-print.cpp @@ -26,6 +26,40 @@ ASTPrinter::Part::Kind ASTPrinter::Part::getKind(ASTPrinter::Part::Type type) void ASTPrinter::addType(Type* type) { + if (!type) + { + m_builder << "<error>"; + return; + } + if (m_optionFlags & OptionFlag::SimplifiedBuiltinType) + { + if (auto vectorType = as<VectorExpressionType>(type)) + { + if (as<BasicExpressionType>(vectorType->elementType)) + { + vectorType->elementType->toText(m_builder); + if (as<ConstantIntVal>(vectorType->elementCount)) + { + m_builder << vectorType->elementCount; + return; + } + } + } + else if (auto matrixType = as<MatrixExpressionType>(type)) + { + auto elementType = matrixType->getElementType(); + if (as<BasicExpressionType>(elementType)) + { + matrixType->getElementType()->toText(m_builder); + if (as<ConstantIntVal>(matrixType->getRowCount()) && + as<ConstantIntVal>(matrixType->getColumnCount())) + { + m_builder << matrixType->getRowCount() << "x" << matrixType->getColumnCount(); + return; + } + } + } + } type->toText(m_builder); } @@ -223,7 +257,7 @@ void ASTPrinter::addGenericParams(const DeclRef<GenericDecl>& genericDeclRef) sb << ">"; } -void ASTPrinter::addDeclParams(const DeclRef<Decl>& declRef) +void ASTPrinter::addDeclParams(const DeclRef<Decl>& declRef, List<Array<Index, 2>>* outParamRange) { auto& sb = m_builder; @@ -237,6 +271,8 @@ void ASTPrinter::addDeclParams(const DeclRef<Decl>& declRef) { if (!first) sb << ", "; + auto rangeStart = sb.getLength(); + ParamDecl* paramDecl = paramDeclRef; { @@ -278,6 +314,11 @@ void ASTPrinter::addDeclParams(const DeclRef<Decl>& declRef) } } + auto rangeEnd = sb.getLength(); + + if (outParamRange) + outParamRange->add(makeArray<Index>(rangeStart, rangeEnd)); + first = false; } @@ -287,7 +328,7 @@ void ASTPrinter::addDeclParams(const DeclRef<Decl>& declRef) { addGenericParams(genericDeclRef); - addDeclParams(DeclRef<Decl>(getInner(genericDeclRef), genericDeclRef.substitutions)); + addDeclParams(DeclRef<Decl>(getInner(genericDeclRef), genericDeclRef.substitutions), outParamRange); } else { @@ -300,10 +341,85 @@ void ASTPrinter::addDeclKindPrefix(Decl* decl) { decl = genericDecl->inner; } + for (auto modifier : decl->modifiers) + { + if (modifier->getKeywordName()) + { + if (m_optionFlags & OptionFlag::NoInternalKeywords) + { + if (as<TargetIntrinsicModifier>(modifier)) + continue; + if (as<MagicTypeModifier>(modifier)) + continue; + if (as<IntrinsicOpModifier>(modifier)) + continue; + if (as<IntrinsicTypeModifier>(modifier)) + continue; + if (as<BuiltinModifier>(modifier)) + continue; + if (as<BuiltinTypeModifier>(modifier)) + continue; + } + m_builder << modifier->getKeywordName()->text << " "; + } + } if (as<FuncDecl>(decl)) { m_builder << "func "; } + else if (as<StructDecl>(decl)) + { + m_builder << "struct "; + } + else if (as<InterfaceDecl>(decl)) + { + m_builder << "interface "; + } + else if (as<ClassDecl>(decl)) + { + m_builder << "class "; + } + else if (auto typedefDecl = as<TypeDefDecl>(decl)) + { + m_builder << "typedef "; + if (typedefDecl->type.type) + { + addType(typedefDecl->type.type); + m_builder << " "; + } + } + else if (auto propertyDecl = as<PropertyDecl>(decl)) + { + m_builder << "property "; + } + else if (as<NamespaceDecl>(decl)) + { + m_builder << "namespace "; + } + else if (auto varDecl = as<VarDeclBase>(decl)) + { + if (varDecl->getType()) + { + addType(varDecl->getType()); + m_builder << " "; + } + } + else if (as<EnumDecl>(decl)) + { + m_builder << "enum "; + } + else if (auto enumCase = as<EnumCaseDecl>(decl)) + { + if (enumCase->getType()) + { + addType(enumCase->getType()); + m_builder << " "; + } + } + else if (auto assocType = as<AssocTypeDecl>(decl)) + { + m_builder << "associatedtype "; + } } void ASTPrinter::addDeclResultType(const DeclRef<Decl>& inDeclRef) @@ -326,6 +442,14 @@ void ASTPrinter::addDeclResultType(const DeclRef<Decl>& inDeclRef) addType(getResultType(m_astBuilder, callableDeclRef)); } } + else if (auto propertyDecl = declRef.as<PropertyDecl>()) + { + if (propertyDecl.getDecl()->type.type) + { + m_builder << " : "; + addType(declRef.substitute(m_astBuilder, propertyDecl.getDecl()->type.type)); + } + } } /* static */void ASTPrinter::addDeclSignature(const DeclRef<Decl>& declRef) diff --git a/source/slang/slang-ast-print.h b/source/slang/slang-ast-print.h index 15ca58acf..cd8322e2a 100644 --- a/source/slang/slang-ast-print.h +++ b/source/slang/slang-ast-print.h @@ -17,6 +17,8 @@ public: { ParamNames = 0x01, ///< If set will output parameter names ModuleName = 0x02, ///< Writes out module names + NoInternalKeywords = 0x04, ///< Omits internal decoration keywords (e.g. __target_intrinsic). + SimplifiedBuiltinType = 0x08, ///< Prints simplified builtin generic types (e.g. float3) instead of its generic form. }; }; @@ -118,7 +120,7 @@ public: /// Add just the parameters from a declaration. /// Will output the generic parameters (if it's a generic) in <> before the parameters () - void addDeclParams(const DeclRef<Decl>& declRef); + void addDeclParams(const DeclRef<Decl>& declRef, List<Array<Index, 2>>* outParamRange = nullptr); /// Add a prefix that describes the kind of declaration void addDeclKindPrefix(Decl* decl); diff --git a/source/slang/slang-ast-support-types.h b/source/slang/slang-ast-support-types.h index ffe7cd553..eb918f0b9 100644 --- a/source/slang/slang-ast-support-types.h +++ b/source/slang/slang-ast-support-types.h @@ -761,6 +761,7 @@ namespace Slang // Convenience accessors for common properties of declarations Name* getName() const; + SourceLoc getNameLoc() const; SourceLoc getLoc() const; DeclRefBase getParent() const; diff --git a/source/slang/slang-check-conversion.cpp b/source/slang/slang-check-conversion.cpp index 44bb8a610..b4c5ebb4a 100644 --- a/source/slang/slang-check-conversion.cpp +++ b/source/slang/slang-check-conversion.cpp @@ -794,6 +794,12 @@ namespace Slang } } + // Disallow converting to a ParameterGroupType. + if (auto toParameterGroupType = as<ParameterGroupType>(toType)) + { + return _failedCoercion(toType, outToExpr, fromExpr); + } + // We allow implicit conversion of a parameter group type like // `ConstantBuffer<X>` or `ParameterBlock<X>` to its element // type `X`. diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 57704ff88..a2311b186 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -2416,7 +2416,9 @@ namespace Slang requiredMemberDeclRef.getName(), lookupResult, synThis, - requiredMemberDeclRef.getLoc()); + requiredMemberDeclRef.getLoc(), + nullptr); + synMemberRef->loc = requiredMemberDeclRef.getLoc(); // The body of the accessor will depend on the class of the accessor // we are synthesizing (e.g., `get` vs. `set`). diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index 8a80398e9..13552cc61 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -231,10 +231,20 @@ namespace Slang return expr; } + static SourceLoc _getMemberOpLoc(Expr* expr) + { + if (auto m = as<MemberExpr>(expr)) + return m->memberOperatorLoc; + if (auto m = as<StaticMemberExpr>(expr)) + return m->memberOperatorLoc; + return SourceLoc(); + } + Expr* SemanticsVisitor::ConstructDeclRefExpr( DeclRef<Decl> declRef, Expr* baseExpr, - SourceLoc loc) + SourceLoc loc, + Expr* originalExpr) { // Compute the type that this declaration reference will have in context. // @@ -285,6 +295,7 @@ namespace Slang expr->baseExpression = baseExpr; expr->name = declRef.getName(); expr->declRef = declRef; + expr->memberOperatorLoc = _getMemberOpLoc(originalExpr); return expr; } else if(isEffectivelyStatic(declRef.getDecl())) @@ -301,6 +312,7 @@ namespace Slang expr->baseExpression = baseTypeExpr; expr->name = declRef.getName(); expr->declRef = declRef; + expr->memberOperatorLoc = _getMemberOpLoc(originalExpr); return expr; } else @@ -314,6 +326,7 @@ namespace Slang expr->baseExpression = baseExpr; expr->name = declRef.getName(); expr->declRef = declRef; + expr->memberOperatorLoc = _getMemberOpLoc(originalExpr); // When referring to a member through an expression, // the result is only an l-value if both the base @@ -340,6 +353,12 @@ namespace Slang expr->name = declRef.getName(); expr->type = type; expr->declRef = declRef; + // Keep a reference to the original expr if it was a genericApp/member. + // This is needed by the language server to locate the original tokens. + if (as<GenericAppExpr>(originalExpr) || as<MemberExpr>(originalExpr) || as<StaticMemberExpr>(originalExpr)) + { + expr->originalExpr = originalExpr; + } return expr; } } @@ -364,7 +383,8 @@ namespace Slang Expr* SemanticsVisitor::ConstructLookupResultExpr( LookupResultItem const& item, Expr* baseExpr, - SourceLoc loc) + SourceLoc loc, + Expr* originalExpr) { // If we collected any breadcrumbs, then these represent // additional segments of the lookup path that we need @@ -375,7 +395,7 @@ namespace Slang switch (breadcrumb->kind) { case LookupResultItem::Breadcrumb::Kind::Member: - bb = ConstructDeclRefExpr(breadcrumb->declRef, bb, loc); + bb = ConstructDeclRefExpr(breadcrumb->declRef, bb, loc, originalExpr); break; case LookupResultItem::Breadcrumb::Kind::Deref: @@ -491,14 +511,15 @@ namespace Slang } } - return ConstructDeclRefExpr(item.declRef, bb, loc); + return ConstructDeclRefExpr(item.declRef, bb, loc, originalExpr); } Expr* SemanticsVisitor::createLookupResultExpr( Name* name, LookupResult const& lookupResult, Expr* baseExpr, - SourceLoc loc) + SourceLoc loc, + Expr* originalExpr) { if (lookupResult.isOverloaded()) { @@ -513,7 +534,7 @@ namespace Slang } else { - return ConstructLookupResultExpr(lookupResult.item, baseExpr, loc); + return ConstructLookupResultExpr(lookupResult.item, baseExpr, loc, originalExpr); } } @@ -624,7 +645,8 @@ namespace Slang // then we can proceed to use that item alone as the resolved // expression. // - return ConstructLookupResultExpr(lookupResult.item, overloadedExpr->base, overloadedExpr->loc); + return ConstructLookupResultExpr( + lookupResult.item, overloadedExpr->base, overloadedExpr->loc, overloadedExpr); } // Otherwise, we weren't able to resolve the overloading given @@ -690,6 +712,9 @@ namespace Slang Expr* checkedTerm = dispatchExpr(term, withExprLocalScope(&exprLocalScope)); + if (IsErrorExpr(checkedTerm)) + return checkedTerm; + LetExpr* outerMostBinding = exprLocalScope.getOuterMostBinding(); if(!outerMostBinding) { @@ -720,6 +745,10 @@ namespace Slang Expr* SemanticsVisitor::CreateErrorExpr(Expr* expr) { + if (!expr) + { + expr = m_astBuilder->create<IncompleteExpr>(); + } expr->type = QualType(m_astBuilder->getErrorType()); return expr; } @@ -747,6 +776,12 @@ namespace Slang return nullptr; } + Expr* SemanticsExprVisitor::visitIncompleteExpr(IncompleteExpr* expr) + { + expr->type = m_astBuilder->getErrorType(); + return expr; + } + Expr* SemanticsExprVisitor::visitBoolLiteralExpr(BoolLiteralExpr* expr) { expr->type = m_astBuilder->getBoolType(); @@ -1208,7 +1243,11 @@ namespace Slang // case the attempt to call it will trigger overload // resolution. Expr* subscriptFuncExpr = createLookupResultExpr( - name, lookupResult, subscriptExpr->baseExpression, subscriptExpr->loc); + name, + lookupResult, + subscriptExpr->baseExpression, + subscriptExpr->loc, + subscriptExpr); InvokeExpr* subscriptCallExpr = m_astBuilder->create<InvokeExpr>(); subscriptCallExpr->loc = subscriptExpr->loc; @@ -1439,7 +1478,8 @@ namespace Slang expr->name, lookupResult, nullptr, - expr->loc); + expr->loc, + expr); } getSink()->diagnose(expr, Diagnostics::undefinedIdentifier2, expr->name); @@ -1624,6 +1664,7 @@ namespace Slang MatrixSwizzleExpr* swizExpr = m_astBuilder->create<MatrixSwizzleExpr>(); swizExpr->loc = memberRefExpr->loc; swizExpr->base = memberRefExpr->baseExpression; + swizExpr->memberOpLoc = memberRefExpr->memberOperatorLoc; // We can have up to 4 swizzles of two elements each MatrixCoord elementCoords[4]; @@ -1781,7 +1822,7 @@ namespace Slang SwizzleExpr* swizExpr = m_astBuilder->create<SwizzleExpr>(); swizExpr->loc = memberRefExpr->loc; swizExpr->base = memberRefExpr->baseExpression; - + swizExpr->memberOpLoc = memberRefExpr->memberOperatorLoc; IntegerLiteralValue limitElement = baseElementCount; int elementIndices[4]; @@ -1915,7 +1956,8 @@ namespace Slang expr->name, lookupResult, nullptr, - expr->loc); + expr->loc, + expr); } else if (auto typeType = as<TypeType>(baseType)) { @@ -2020,7 +2062,8 @@ namespace Slang expr->name, lookupResult, baseExpression, - expr->loc); + expr->loc, + expr); } else if (as<ErrorType>(baseType)) { @@ -2106,7 +2149,8 @@ namespace Slang overloadedExpr->name, filteredLookupResult, overloadedExpr->base, - overloadedExpr->loc); + overloadedExpr->loc, + overloadedExpr); } // TODO: handle other cases of OverloadedExpr that need filtering. } @@ -2178,7 +2222,8 @@ namespace Slang expr->name, lookupResult, expr->baseExpression, - expr->loc); + expr->loc, + expr); } } diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index 1e549c7da..6fddbc453 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -492,7 +492,8 @@ namespace Slang Expr* ConstructDeclRefExpr( DeclRef<Decl> declRef, Expr* baseExpr, - SourceLoc loc); + SourceLoc loc, + Expr* originalExpr); Expr* ConstructDerefExpr( Expr* base, @@ -501,13 +502,15 @@ namespace Slang Expr* ConstructLookupResultExpr( LookupResultItem const& item, Expr* baseExpr, - SourceLoc loc); + SourceLoc loc, + Expr* originalExpr); Expr* createLookupResultExpr( Name* name, LookupResult const& lookupResult, Expr* baseExpr, - SourceLoc loc); + SourceLoc loc, + Expr* originalExpr); /// Attempt to "resolve" an overloaded `LookupResult` to only include the "best" results LookupResult resolveOverloadedLookup(LookupResult const& lookupResult); @@ -1649,6 +1652,7 @@ namespace Slang : SemanticsVisitor(outer) {} + Expr* visitIncompleteExpr(IncompleteExpr* expr); Expr* visitBoolLiteralExpr(BoolLiteralExpr* expr); Expr* visitNullPtrLiteralExpr(NullPtrLiteralExpr* expr); Expr* visitIntegerLiteralExpr(IntegerLiteralExpr* expr); diff --git a/source/slang/slang-check-overload.cpp b/source/slang/slang-check-overload.cpp index e12f97640..f4a1de3d5 100644 --- a/source/slang/slang-check-overload.cpp +++ b/source/slang/slang-check-overload.cpp @@ -517,7 +517,8 @@ namespace Slang return ConstructDeclRefExpr( innerDeclRef, base, - originalExpr->loc); + originalExpr->loc, + originalExpr); } Expr* SemanticsVisitor::CompleteOverloadCandidate( @@ -556,8 +557,12 @@ namespace Slang goto error; { + auto originalAppExpr = as<AppExprBase>(context.originalExpr); auto baseExpr = ConstructLookupResultExpr( - candidate.item, context.baseExpr, context.funcLoc); + candidate.item, + context.baseExpr, + context.funcLoc, + originalAppExpr ? originalAppExpr->functionExpr : nullptr); switch(candidate.flavor) { @@ -568,12 +573,11 @@ namespace Slang { callExpr = m_astBuilder->create<InvokeExpr>(); callExpr->loc = context.loc; - for(Index aa = 0; aa < context.argCount; ++aa) callExpr->arguments.add(context.getArg(aa)); } - + callExpr->originalFunctionExpr = callExpr->functionExpr; callExpr->functionExpr = baseExpr; callExpr->type = QualType(candidate.resultType); diff --git a/source/slang/slang-check-type.cpp b/source/slang/slang-check-type.cpp index bd7b89e16..dc0d69f04 100644 --- a/source/slang/slang-check-type.cpp +++ b/source/slang/slang-check-type.cpp @@ -131,7 +131,6 @@ namespace Slang // assume that if it is overloaded, we want a type exp = resolveOverloadedExpr(overloadedExpr, LookupMask::type); } - if (auto typeType = as<TypeType>(exp->type)) { return typeType->type; @@ -142,6 +141,10 @@ namespace Slang } else { + if (!exp->type.type) + { + CheckExpr(exp); + } return ExtractGenericArgInteger(exp); } } @@ -269,7 +272,6 @@ namespace Slang // ignore non-parameter members } } - if (outProperType) { *outProperType = InstantiateGenericType(genericDeclRef, args); diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 683e1f7f1..ff1f660a1 100755 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -1685,6 +1685,7 @@ namespace Slang slang::IBlob** outDiagnostics = nullptr) override; SLANG_NO_THROW slang::IModule* SLANG_MCALL loadModuleFromSource( const char* moduleName, + const char* path, slang::IBlob* source, slang::IBlob** outDiagnostics = nullptr) override; SLANG_NO_THROW SlangResult SLANG_MCALL createCompositeComponentType( @@ -1743,6 +1744,10 @@ namespace Slang /// Dtor ~Linkage(); + slang::SessionFlags m_flag = 0; + void setFlags(slang::SessionFlags flags) { m_flag = flags; } + bool isInLanguageServer() { return (m_flag & slang::kSessionFlag_LanguageServer) != 0; } + /// Get the parent session for this linkage Session* getSessionImpl() { return m_session; } @@ -1901,6 +1906,8 @@ namespace Slang SerialCompressionType serialCompressionType = SerialCompressionType::VariableByteLite; + DiagnosticSink::Flags diagnosticSinkFlags = 0; + bool m_requireCacheFileSystem = false; bool m_useFalcorCustomSharedKeywordSemantics = false; diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 9ab34128a..059df4453 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -332,7 +332,7 @@ DIAGNOSTIC(30300, Error, assocTypeInInterfaceOnly, "'associatedtype' can only be DIAGNOSTIC(30301, Error, globalGenParamInGlobalScopeOnly, "'type_param' can only be defined global scope.") // TODO: need to assign numbers to all these extra diagnostics... DIAGNOSTIC(39999, Fatal, cyclicReference, "cyclic reference '$0'.") -DIAGNOSTIC(39999, Fatal, localVariableUsedBeforeDeclared, "local variable '$0' is being used before its declaration.") +DIAGNOSTIC(39999, Error, localVariableUsedBeforeDeclared, "local variable '$0' is being used before its declaration.") DIAGNOSTIC(39999, Error, variableUsedInItsOwnDefinition, "the initial-value expression for variable '$0' depends on the value of the variable itself") // 304xx: generics diff --git a/source/slang/slang-doc-ast.cpp b/source/slang/slang-doc-ast.cpp index 8301b1a63..386c8e6d0 100644 --- a/source/slang/slang-doc-ast.cpp +++ b/source/slang/slang-doc-ast.cpp @@ -55,10 +55,6 @@ static void _addDeclRec(Decl* decl, List<Decl*>& outDecls) { outDecls.add(decl); } - else - { - SLANG_ASSERT(!"Decl without a location!"); - } if (GenericDecl* genericDecl = as<GenericDecl>(decl)) { diff --git a/source/slang/slang-language-server-ast-lookup.cpp b/source/slang/slang-language-server-ast-lookup.cpp new file mode 100644 index 000000000..768c8ef2d --- /dev/null +++ b/source/slang/slang-language-server-ast-lookup.cpp @@ -0,0 +1,566 @@ +#include "slang-language-server-ast-lookup.h" +#include "slang-visitor.h" + +namespace Slang +{ +struct Loc +{ + Int line; + Int col; + bool operator<(const Loc& other) + { + return line < other.line || line == other.line && col < other.col; + } + bool operator<=(const Loc& other) + { + return line < other.line || line == other.line && col <= other.col; + } +}; +struct ASTLookupContext +{ + SourceManager* sourceManager; + List<SyntaxNode*> nodePath; + ASTLookupType findType; + Int line; + Int col; + Loc cursorLoc; + UnownedStringSlice sourceFileName; + List<ASTLookupResult> results; + + Loc getLoc(SourceLoc loc, String* outFileName) + { + auto humaneLoc = sourceManager->getHumaneLoc(loc, SourceLocType::Actual); + if (outFileName) + *outFileName = humaneLoc.pathInfo.foundPath; + return Loc{humaneLoc.line, humaneLoc.column}; + } +}; +struct PushNode +{ + ASTLookupContext* context; + PushNode(ASTLookupContext* ctx, SyntaxNode* node) + { + context = ctx; + context->nodePath.add(node); + } + ~PushNode() { if (context) context->nodePath.removeLast(); } +}; + +static Index _getDeclNameLength(Name* name) +{ + if (!name) + return 0; + if (name->text.startsWith("$")) + return 0; + // HACK: our __subscript functions currently have a name "operator[]". + // Since this isn't the name that actually appears in user's code, + // we need to shorten its reported length to 1 for now. + if (name->text.startsWith("operator")) + { + return 1; + } + return name->text.getLength(); +} + +bool _isLocInRange(ASTLookupContext* context, SourceLoc loc, Int length) +{ + auto humaneLoc = context->sourceManager->getHumaneLoc(loc, SourceLocType::Actual); + return humaneLoc.line == context->line && context->col >= humaneLoc.column && + context->col <= humaneLoc.column + length && + humaneLoc.pathInfo.foundPath.getUnownedSlice().endsWithCaseInsensitive( + context->sourceFileName); +} +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; +} + +bool _findAstNodeImpl(ASTLookupContext& context, SyntaxNode* node); + +struct ASTLookupExprVisitor: public ExprVisitor<ASTLookupExprVisitor, bool> +{ +public: + ASTLookupContext* context; + + ASTLookupExprVisitor(ASTLookupContext* ctx) + : context(ctx) + {} + bool dispatchIfNotNull(Expr* expr) + { + if (!expr) + return false; + return dispatch(expr); + } + bool visitExpr(Expr*) { return false; } + bool visitBoolLiteralExpr(BoolLiteralExpr*) { return false; } + bool visitNullPtrLiteralExpr(NullPtrLiteralExpr*) { return false; } + bool visitIntegerLiteralExpr(IntegerLiteralExpr*) { return false; } + bool visitFloatingPointLiteralExpr(FloatingPointLiteralExpr*) { return false; } + bool visitStringLiteralExpr(StringLiteralExpr*) { return false; } + bool visitIncompleteExpr(IncompleteExpr*) { return false; } + bool visitIndexExpr(IndexExpr* subscriptExpr) + { + if (dispatchIfNotNull(subscriptExpr->indexExpression)) + return true; + return dispatchIfNotNull(subscriptExpr->baseExpression); + } + + bool visitParenExpr(ParenExpr* expr) + { + return dispatchIfNotNull(expr->base); + } + + bool visitAssignExpr(AssignExpr* expr) + { + if (dispatchIfNotNull(expr->left)) + return true; + return dispatchIfNotNull(expr->right); + } + + bool visitGenericAppExpr(GenericAppExpr* genericAppExpr) + { + if (dispatchIfNotNull(genericAppExpr->functionExpr)) + return true; + for (auto arg : genericAppExpr->arguments) + if (dispatchIfNotNull(arg)) + return true; + return false; + } + + bool visitSharedTypeExpr(SharedTypeExpr* expr) { return dispatchIfNotNull(expr->base.exp); } + + bool visitTaggedUnionTypeExpr(TaggedUnionTypeExpr*) + { + return false; + } + + bool visitInvokeExpr(InvokeExpr* expr) + { + PushNode pushNodeRAII(context, expr); + if (dispatchIfNotNull(expr->functionExpr)) + return true; + for (auto arg : expr->arguments) + if (dispatchIfNotNull(arg)) + return true; + if (context->findType == ASTLookupType::Invoke && expr->argumentDelimeterLocs.getCount()) + { + String fileName; + Loc start = context->getLoc(expr->argumentDelimeterLocs.getFirst(), &fileName); + Loc end = context->getLoc(expr->argumentDelimeterLocs.getLast(), nullptr); + if (fileName.getUnownedSlice().endsWithCaseInsensitive(context->sourceFileName) && + start < context->cursorLoc && context->cursorLoc <= end) + { + ASTLookupResult result; + result.path = context->nodePath; + result.path.add(expr); + context->results.add(result); + return true; + } + } + return false; + } + + bool visitVarExpr(VarExpr* expr) + { + if (expr->name && expr->declRef.getDecl() && + _isLocInRange(context, expr->loc, _getDeclNameLength(expr->name))) + { + if (expr->declRef.getDecl()->hasModifier<ImplicitConversionModifier>()) + return false; + ASTLookupResult result; + result.path = context->nodePath; + result.path.add(expr); + context->results.add(result); + return true; + } + return dispatchIfNotNull(expr->originalExpr); + } + + bool visitTypeCastExpr(TypeCastExpr* expr) + { + if (dispatchIfNotNull(expr->functionExpr)) + return true; + for (auto arg : expr->arguments) + if (dispatchIfNotNull(arg)) + return true; + return false; + } + + bool visitDerefExpr(DerefExpr* expr) { return dispatchIfNotNull(expr->base); } + bool visitMatrixSwizzleExpr(MatrixSwizzleExpr* expr) + { + if (_isLocInRange(context, expr->memberOpLoc, 0)) + { + ASTLookupResult result; + result.path = context->nodePath; + result.path.add(expr); + context->results.add(result); + return true; + } + return dispatchIfNotNull(expr->base); + } + bool visitSwizzleExpr(SwizzleExpr* expr) + { + if (_isLocInRange(context, expr->memberOpLoc, 0)) + { + ASTLookupResult result; + result.path = context->nodePath; + result.path.add(expr); + context->results.add(result); + return true; + } + return dispatchIfNotNull(expr->base); + } + bool visitOverloadedExpr(OverloadedExpr* expr) + { + if (dispatchIfNotNull(expr->base)) + return true; + if (expr->lookupResult2.getName() && + _isLocInRange( + context, + expr->loc, + _getDeclNameLength(expr->lookupResult2.getName()))) + { + ASTLookupResult result; + result.path = context->nodePath; + result.path.add(expr); + context->results.add(result); + return true; + } + return false; + } + bool visitOverloadedExpr2(OverloadedExpr2* expr) + { + if (dispatchIfNotNull(expr->base)) + return true; + bool result = false; + for (auto candidate : expr->candidiateExprs) + { + result |= dispatchIfNotNull(candidate); + } + return result; + } + bool visitAggTypeCtorExpr(AggTypeCtorExpr* expr) + { + if (dispatchIfNotNull(expr->base.exp)) + return true; + for (auto arg : expr->arguments) + { + if (dispatchIfNotNull(arg)) + return true; + } + return false; + } + bool visitCastToSuperTypeExpr(CastToSuperTypeExpr* expr) + { + return dispatchIfNotNull(expr->valueArg); + } + bool visitModifierCastExpr(ModifierCastExpr* expr) { return dispatchIfNotNull(expr->valueArg); } + bool visitLetExpr(LetExpr* expr) + { + if (dispatchIfNotNull(expr->body)) + return true; + return _findAstNodeImpl(*context, expr->decl); + } + bool visitExtractExistentialValueExpr(ExtractExistentialValueExpr* expr) + { + if (expr->declRef.getDecl() && expr->declRef.getName() && + _isLocInRange( + context, expr->loc, _getDeclNameLength(expr->declRef.getName()))) + { + ASTLookupResult result; + result.path = context->nodePath; + result.path.add(expr); + context->results.add(result); + return true; + } + return false; + } + + bool visitDeclRefExpr(DeclRefExpr* expr) + { + if (expr->declRef.getDecl() && expr->declRef.getDecl()->getName() && + _isLocInRange( + context, + expr->loc, + _getDeclNameLength(expr->declRef.getDecl()->getName()))) + { + if (expr->declRef.getDecl()->hasModifier<ImplicitConversionModifier>()) + return false; + ASTLookupResult result; + result.path = context->nodePath; + result.path.add(expr); + context->results.add(result); + return true; + } + return false; + } + + bool visitStaticMemberExpr(StaticMemberExpr* expr) + { + if (_isLocInRange(context, expr->memberOperatorLoc, 0)) + { + ASTLookupResult result; + result.path = context->nodePath; + result.path.add(expr); + context->results.add(result); + return true; + } + if (visitDeclRefExpr(expr)) + return true; + return dispatchIfNotNull(expr->baseExpression); + } + + bool visitMemberExpr(MemberExpr* expr) + { + if (_isLocInRange(context, expr->memberOperatorLoc, 0)) + { + ASTLookupResult result; + result.path = context->nodePath; + result.path.add(expr); + context->results.add(result); + return true; + } + if (visitDeclRefExpr(expr)) return true; + return dispatchIfNotNull(expr->baseExpression); + } + + bool visitInitializerListExpr(InitializerListExpr* expr) + { + for (auto arg : expr->args) + { + if (dispatchIfNotNull(arg)) + return true; + } + return false; + } + + bool visitThisExpr(ThisExpr*) { return false; } + bool visitThisTypeExpr(ThisTypeExpr*) { return false; } + bool visitAndTypeExpr(AndTypeExpr* expr) + { + if (dispatchIfNotNull(expr->left.exp)) + return true; + return dispatchIfNotNull(expr->right.exp); + } + bool visitModifiedTypeExpr(ModifiedTypeExpr* expr) { return dispatchIfNotNull(expr->base.exp); } + bool visitTryExpr(TryExpr* expr) { return dispatchIfNotNull(expr->base); } + +}; + +struct ASTLookupStmtVisitor : public StmtVisitor<ASTLookupStmtVisitor, bool> +{ + ASTLookupContext* context; + + ASTLookupStmtVisitor(ASTLookupContext* ctx) + : context(ctx) + {} + + bool dispatchIfNotNull(Stmt* stmt) + { + if (!stmt) + return false; + return dispatch(stmt); + } + + bool checkExpr(Expr* expr) + { + if (!expr) + return false; + ASTLookupExprVisitor visitor(context); + return visitor.dispatch(expr); + } + + bool visitDeclStmt(DeclStmt* stmt) { return _findAstNodeImpl(*context, stmt->decl); } + + bool visitBlockStmt(BlockStmt* stmt) + { + if (!_isLocInRange(context, stmt->loc, stmt->closingSourceLoc)) + return false; + return dispatchIfNotNull(stmt->body); + } + + bool visitSeqStmt(SeqStmt* seqStmt) + { + for (auto stmt : seqStmt->stmts) + if (dispatchIfNotNull(stmt)) + return true; + return false; + } + + bool visitBreakStmt(BreakStmt*) { return false; } + + bool visitContinueStmt(ContinueStmt*) { return false; } + + bool visitDoWhileStmt(DoWhileStmt* stmt) + { + if (checkExpr(stmt->predicate)) + return true; + return dispatchIfNotNull(stmt->statement); + } + + bool visitForStmt(ForStmt* stmt) + { + if (dispatchIfNotNull(stmt->initialStatement)) + return true; + if (checkExpr(stmt->predicateExpression)) + return true; + if (checkExpr(stmt->sideEffectExpression)) + return true; + return dispatchIfNotNull(stmt->statement); + } + + bool visitCompileTimeForStmt(CompileTimeForStmt*) + { + return false; + } + + bool visitSwitchStmt(SwitchStmt* stmt) + { + if (checkExpr(stmt->condition)) + return true; + return dispatchIfNotNull(stmt->body); + } + + bool visitCaseStmt(CaseStmt* stmt) { return checkExpr(stmt->expr); } + + bool visitDefaultStmt(DefaultStmt*) { return false; } + + bool visitIfStmt(IfStmt* stmt) + { + if (checkExpr(stmt->predicate)) + return true; + if (dispatchIfNotNull(stmt->positiveStatement)) + return true; + return dispatchIfNotNull(stmt->negativeStatement); + } + + bool visitUnparsedStmt(UnparsedStmt*) { return false; } + + bool visitEmptyStmt(EmptyStmt*) { return false; } + + bool visitDiscardStmt(DiscardStmt*) { return false; } + + bool visitReturnStmt(ReturnStmt* stmt) { return checkExpr(stmt->expression); } + + bool visitWhileStmt(WhileStmt* stmt) + { + if (checkExpr(stmt->predicate)) + return true; + return dispatchIfNotNull(stmt->statement); + } + + bool visitGpuForeachStmt(GpuForeachStmt*) { return false; } + + bool visitExpressionStmt(ExpressionStmt* stmt) + { + return checkExpr(stmt->expression); + } +}; + +bool _findAstNodeImpl(ASTLookupContext& context, SyntaxNode* node) +{ + if (!node) + return false; + PushNode pushNodeRAII(&context, node); + if (auto decl = as<Decl>(node)) + { + if (decl->getName()) + { + if (_isLocInRange( + &context, + decl->nameAndLoc.loc, + _getDeclNameLength(decl->getName()))) + { + ASTLookupResult result; + result.path = context.nodePath; + context.results.add(_Move(result)); + return true; + } + } + if (auto funcDecl = as<FunctionDeclBase>(node)) + { + ASTLookupStmtVisitor visitor(&context); + if (visitor.dispatchIfNotNull(funcDecl->body)) + return true; + ASTLookupExprVisitor exprVisitor(&context); + if (exprVisitor.dispatchIfNotNull(funcDecl->returnType.exp)) + return true; + } + else if (auto propertyDecl = as<PropertyDecl>(node)) + { + ASTLookupExprVisitor exprVisitor(&context); + if (exprVisitor.dispatchIfNotNull(propertyDecl->type.exp)) + return true; + } + else if (auto varDecl = as<VarDeclBase>(node)) + { + ASTLookupExprVisitor visitor(&context); + if (visitor.dispatchIfNotNull(varDecl->type.exp)) + return true; + if (visitor.dispatchIfNotNull(varDecl->initExpr)) + return true; + } + else if (auto genericDecl = as<GenericDecl>(node)) + { + if (_findAstNodeImpl(context, genericDecl->inner)) + return true; + } + else if (auto typeConstraint = as<TypeConstraintDecl>(node)) + { + ASTLookupExprVisitor visitor(&context); + if (visitor.dispatchIfNotNull(typeConstraint->getSup().exp)) + return true; + } + else if (auto typedefDecl = as<TypeDefDecl>(node)) + { + ASTLookupExprVisitor visitor(&context); + if (visitor.dispatchIfNotNull(typedefDecl->type.exp)) + return true; + } + if (auto container = as<ContainerDecl>(node)) + { + bool shouldInspectChildren = true; + if (auto genericDecl = as<GenericDecl>(node)) + {} + else if (container->closingSourceLoc.getRaw() >= container->loc.getRaw()) + { + if (!_isLocInRange(&context, container->loc, container->closingSourceLoc)) + { + shouldInspectChildren = false; + } + } + if (shouldInspectChildren) + { + for (auto member : container->members) + { + if (_findAstNodeImpl(context, member)) + return true; + } + } + } + } + return false; +} + +List<ASTLookupResult> findASTNodesAt( + SourceManager* sourceManager, ModuleDecl* moduleDecl, ASTLookupType findType, UnownedStringSlice fileName, Int line, Int col) +{ + ASTLookupContext context; + context.sourceManager = sourceManager; + context.line = line; + context.col = col; + context.cursorLoc = Loc{line, col}; + context.findType = findType; + context.sourceFileName = fileName; + _findAstNodeImpl(context, moduleDecl); + return context.results; +} + +} // namespace Slang diff --git a/source/slang/slang-language-server-ast-lookup.h b/source/slang/slang-language-server-ast-lookup.h new file mode 100644 index 000000000..9fad5e8bd --- /dev/null +++ b/source/slang/slang-language-server-ast-lookup.h @@ -0,0 +1,24 @@ +#pragma once + +#include "slang-ast-all.h" + +namespace Slang +{ +struct ASTLookupResult +{ + List<SyntaxNode*> path; +}; +enum class ASTLookupType +{ + Decl, + Invoke, +}; +List<ASTLookupResult> findASTNodesAt( + SourceManager* sourceManager, + ModuleDecl* moduleDecl, + ASTLookupType findType, + UnownedStringSlice fileName, + Int line, + Int col); + +} // namespace LanguageServerProtocol diff --git a/source/slang/slang-language-server-collect-member.cpp b/source/slang/slang-language-server-collect-member.cpp new file mode 100644 index 000000000..73539b9d9 --- /dev/null +++ b/source/slang/slang-language-server-collect-member.cpp @@ -0,0 +1,156 @@ +// slang-language-server-collect-member.cpp + +// This file implements 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. + +#include "slang-language-server-collect-member.h" + +namespace Slang +{ +void collectMembersInType(MemberCollectingContext* context, Type* type) +{ + 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 interfaceType = DeclRefType::create(context->astBuilder, thisType->interfaceDeclRef); + collectMembersInType(context, interfaceType); + } + else if (auto andType = as<AndType>(type)) + { + auto leftType = andType->left; + auto rightType = andType->right; + collectMembersInType(context, leftType); + collectMembersInType(context, rightType); + } +} + +void collectMembersInTypeDeclImpl( + MemberCollectingContext* context, + DeclRef<Decl> declRef) +{ + if (declRef.getDecl()->checkState.getState() < DeclCheckState::ReadyForLookup) + return; + + if (auto genericTypeParamDeclRef = declRef.as<GenericTypeParamDecl>()) + { + // 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)) + { + if (constraintDeclRef.decl->checkState.getState() < DeclCheckState::ReadyForLookup) + { + continue; + } + + collectMembersInType( + context, + getSup(context->astBuilder, constraintDeclRef)); + } + } + else if (declRef.as<AssocTypeDecl>() || declRef.as<GlobalGenericParamDecl>()) + { + for (auto constraintDeclRef : + getMembersOfType<TypeConstraintDecl>(declRef.as<ContainerDecl>())) + { + if (constraintDeclRef.decl->checkState.getState() < DeclCheckState::ReadyForLookup) + { + continue; + } + collectMembersInType(context, getSup(context->astBuilder, constraintDeclRef)); + } + } + else if (auto namespaceDecl = declRef.as<NamespaceDecl>()) + { + for (auto member : namespaceDecl.getDecl()->members) + { + if (member->getName()) + { + context->members.add(member); + } + } + } + else if (auto aggTypeDeclBaseRef = declRef.as<AggTypeDeclBase>()) + { + // 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 (member->getName()) + { + context->members.add(member); + } + } + + if (auto aggTypeDeclRef = aggTypeDeclBaseRef.as<AggTypeDecl>()) + { + auto extensions = + context->semanticsContext.getCandidateExtensionsForTypeDecl(aggTypeDeclRef); + for (auto extDecl : extensions) + { + // 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)); + } + } + + // 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)) + { + // 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>()) + continue; + + collectMembersInType( + context, getSup(context->astBuilder, inheritanceDeclRef)); + } + } +} + +} // namespace Slang diff --git a/source/slang/slang-language-server-collect-member.h b/source/slang/slang-language-server-collect-member.h new file mode 100644 index 000000000..bb48c4d5b --- /dev/null +++ b/source/slang/slang-language-server-collect-member.h @@ -0,0 +1,25 @@ +// slang-language-server-collect-member.h +#pragma once + +#include "slang-ast-all.h" +#include "slang-syntax.h" +#include "slang-check-impl.h" + +namespace Slang +{ + +struct MemberCollectingContext +{ + ASTBuilder* astBuilder; + List<Decl*> members; + 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); + +} // namespace Slang diff --git a/source/slang/slang-language-server-protocol.cpp b/source/slang/slang-language-server-protocol.cpp new file mode 100644 index 000000000..c557be7ec --- /dev/null +++ b/source/slang/slang-language-server-protocol.cpp @@ -0,0 +1,481 @@ +#include "slang-language-server-protocol.h" + +namespace Slang +{ +namespace LanguageServerProtocol +{ +static const StructRttiInfo _makeTextDocumentSyncOptionsRtti() +{ + TextDocumentSyncOptions obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::TextDocumentSyncOptions", nullptr); + builder.addField("change", &obj.change); + builder.addField("openClose", &obj.openClose); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo TextDocumentSyncOptions::g_rttiInfo = _makeTextDocumentSyncOptionsRtti(); + +static const StructRttiInfo _makeCompletionOptionsRtti() +{ + CompletionOptions obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionOptions", nullptr); + builder.addField("triggerCharacters", &obj.triggerCharacters); + builder.addField("resolveProvider", &obj.resolveProvider); + builder.addField("allCommitCharacters", &obj.allCommitCharacters); + builder.addField("workDoneToken", &obj.workDoneToken); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo CompletionOptions::g_rttiInfo = _makeCompletionOptionsRtti(); + +static const StructRttiInfo _makeSemanticTokensLegendRtti() +{ + SemanticTokensLegend obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::SemanticTokensLegend", nullptr); + builder.addField("tokenTypes", &obj.tokenTypes); + builder.addField("tokenModifiers", &obj.tokenModifiers); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo SemanticTokensLegend::g_rttiInfo = _makeSemanticTokensLegendRtti(); + +static const StructRttiInfo _makeSemanticTokensOptionsRtti() +{ + SemanticTokensOptions obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::SemanticTokensOptions", nullptr); + builder.addField("legend", &obj.legend); + builder.addField("range", &obj.range); + builder.addField("full", &obj.full); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo SemanticTokensOptions::g_rttiInfo = _makeSemanticTokensOptionsRtti(); + +static const StructRttiInfo _makeSignatureHelpOptionsRtti() +{ + SignatureHelpOptions obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::SignatureHelpOptions", nullptr); + builder.addField("triggerCharacters", &obj.triggerCharacters); + builder.addField("retriggerCharacters", &obj.retriggerCharacters); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo SignatureHelpOptions::g_rttiInfo = _makeSignatureHelpOptionsRtti(); + +static const StructRttiInfo _makeTextDocumentItemRtti() +{ + TextDocumentItem obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::TextDocumentItem", nullptr); + builder.addField("uri", &obj.uri); + builder.addField("version", &obj.version); + builder.addField("languageId", &obj.languageId); + builder.addField("text", &obj.text); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo TextDocumentItem::g_rttiInfo = _makeTextDocumentItemRtti(); + +static const StructRttiInfo _makeTextDocumentIdentifierRtti() +{ + TextDocumentIdentifier obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::TextDocumentIdentifier", nullptr); + builder.addField("uri", &obj.uri); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo TextDocumentIdentifier::g_rttiInfo = _makeTextDocumentIdentifierRtti(); + +static const StructRttiInfo _makeVersionedTextDocumentIdentifierRtti() +{ + VersionedTextDocumentIdentifier obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::VersionedTextDocumentIdentifier", nullptr); + builder.addField("uri", &obj.uri); + builder.addField("version", &obj.version); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo VersionedTextDocumentIdentifier::g_rttiInfo = + _makeVersionedTextDocumentIdentifierRtti(); + +static const StructRttiInfo _makePositionRtti() +{ + Position obj; + StructRttiBuilder builder( + &obj, "LanguageServerProtocol::Position", nullptr); + builder.addField("line", &obj.line); + builder.addField("character", &obj.character); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo Position::g_rttiInfo = _makePositionRtti(); + +static const StructRttiInfo _makeRangeRtti() +{ + Range obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::Range", nullptr); + builder.addField("start", &obj.start); + builder.addField("end", &obj.end); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo Range::g_rttiInfo = _makeRangeRtti(); + +static const StructRttiInfo _makeDidOpenTextDocumentRtti() +{ + DidOpenTextDocumentParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::DidOpenTextDocumentParams", nullptr); + builder.addField("textDocument", &obj.textDocument); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo DidOpenTextDocumentParams::g_rttiInfo = _makeDidOpenTextDocumentRtti(); +const UnownedStringSlice DidOpenTextDocumentParams::methodName = + UnownedStringSlice::fromLiteral("textDocument/didOpen"); + +static const StructRttiInfo _makeTextDocumentContentChangeEventRtti() +{ + TextDocumentContentChangeEvent obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::TextDocumentContentChangeEvent", nullptr); + builder.addField("range", &obj.range, StructRttiInfo::Flag::Optional); + builder.addField("text", &obj.text); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo TextDocumentContentChangeEvent::g_rttiInfo = + _makeTextDocumentContentChangeEventRtti(); + +static const StructRttiInfo _makeDidChangeTextDocumentParamsRtti() +{ + DidChangeTextDocumentParams obj; + StructRttiBuilder builder( + &obj, "LanguageServerProtocol::DidChangeTextDocumentParams", nullptr); + builder.addField("textDocument", &obj.textDocument); + builder.addField("contentChanges", &obj.contentChanges); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo DidChangeTextDocumentParams::g_rttiInfo = + _makeDidChangeTextDocumentParamsRtti(); +const UnownedStringSlice DidChangeTextDocumentParams::methodName = + UnownedStringSlice::fromLiteral("textDocument/didChange"); + + +static const StructRttiInfo _makeDidCloseTextDocumentParamsRtti() +{ + DidCloseTextDocumentParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::DidCloseTextDocumentParams", nullptr); + builder.addField("textDocument", &obj.textDocument); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo DidCloseTextDocumentParams::g_rttiInfo = _makeDidCloseTextDocumentParamsRtti(); +const UnownedStringSlice DidCloseTextDocumentParams::methodName = + UnownedStringSlice::fromLiteral("textDocument/didClose"); + +static const StructRttiInfo _makeServerCapabilitiesRtti() +{ + ServerCapabilities obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::ServerCapabilities", nullptr); + builder.addField("positionEncoding", &obj.positionEncoding); + builder.addField("textDocumentSync", &obj.textDocumentSync); + builder.addField("hoverProvider", &obj.hoverProvider); + builder.addField("definitionProvider", &obj.definitionProvider); + builder.addField("completionProvider", &obj.completionProvider); + builder.addField("semanticTokensProvider", &obj.semanticTokensProvider); + builder.addField("signatureHelpProvider", &obj.signatureHelpProvider); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo ServerCapabilities::g_rttiInfo = _makeServerCapabilitiesRtti(); + +static const StructRttiInfo _makeServerInfoRtti() +{ + ServerInfo obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::ServerInfo", nullptr); + builder.addField("name", &obj.name); + builder.addField("version", &obj.version); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo ServerInfo::g_rttiInfo = _makeServerInfoRtti(); + + +static const StructRttiInfo _makeInitializeResultRtti() +{ + InitializeResult obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::InitializeResult", nullptr); + builder.addField("capabilities", &obj.capabilities); + builder.addField("serverInfo", &obj.serverInfo); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo InitializeResult::g_rttiInfo = _makeInitializeResultRtti(); + +const UnownedStringSlice InitializeParams::methodName = + UnownedStringSlice::fromLiteral("initialize"); + +const UnownedStringSlice ShutdownParams::methodName = UnownedStringSlice::fromLiteral("shutdown"); + +const UnownedStringSlice ExitParams::methodName = UnownedStringSlice::fromLiteral("exit"); + +static const StructRttiInfo _makeWorkspaceFolderRtti() +{ + WorkspaceFolder obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::WorkspaceFolder", nullptr); + builder.addField("uri", &obj.uri); + builder.addField("name", &obj.name); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo WorkspaceFolder::g_rttiInfo = _makeWorkspaceFolderRtti(); + +static const StructRttiInfo _makeInitializeParamsRtti() +{ + InitializeParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::InitializeParams", nullptr); + builder.addField("workspaceFolders", &obj.workspaceFolders, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo InitializeParams::g_rttiInfo = _makeInitializeParamsRtti(); + +static const StructRttiInfo _makeNullResponseRtti() +{ + NullResponse obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::NullResponse", nullptr); + return builder.make(); +} +const StructRttiInfo NullResponse::g_rttiInfo = _makeNullResponseRtti(); + +NullResponse* NullResponse::get() +{ + static NullResponse result = {}; + return &result; +} + +static const StructRttiInfo _makeLocationRtti() +{ + Location obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::Location", nullptr); + builder.addField("uri", &obj.uri); + builder.addField("range", &obj.range); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo Location::g_rttiInfo = _makeLocationRtti(); + +static const StructRttiInfo _makeDiagnosticRelatedInformationRtti() +{ + DiagnosticRelatedInformation obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::DiagnosticRelatedInformation", nullptr); + builder.addField("location", &obj.location); + builder.addField("message", &obj.message); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo DiagnosticRelatedInformation::g_rttiInfo = + _makeDiagnosticRelatedInformationRtti(); + +static const StructRttiInfo _makeDiagnosticRtti() +{ + Diagnostic obj; + StructRttiBuilder builder( + &obj, "LanguageServerProtocol::Diagnostic", nullptr); + builder.addField("code", &obj.code); + builder.addField("message", &obj.message); + builder.addField("range", &obj.range); + builder.addField("relatedInformation", &obj.relatedInformation); + builder.addField("severity", &obj.severity); + builder.addField("source", &obj.source); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo Diagnostic::g_rttiInfo = _makeDiagnosticRtti(); + +static const StructRttiInfo _makePublishDiagnosticsParamsRtti() +{ + PublishDiagnosticsParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::PublishDiagnosticsParams", nullptr); + builder.addField("uri", &obj.uri); + builder.addField("diagnostics", &obj.diagnostics); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo PublishDiagnosticsParams::g_rttiInfo = _makePublishDiagnosticsParamsRtti(); + +static const StructRttiInfo _makeTextDocumentPositionParamsRtti() +{ + TextDocumentPositionParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::TextDocumentPositionParams", nullptr); + builder.addField("textDocument", &obj.textDocument); + builder.addField("position", &obj.position); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo TextDocumentPositionParams::g_rttiInfo = _makeTextDocumentPositionParamsRtti(); + +static const StructRttiInfo _makeWorkDoneProgressParamsRtti() +{ + WorkDoneProgressParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::WorkDoneProgressParams", nullptr); + builder.addField("workDoneToken", &obj.workDoneToken, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo WorkDoneProgressParams::g_rttiInfo = _makeWorkDoneProgressParamsRtti(); + +static const StructRttiInfo _makeHoverParamsRtti() +{ + HoverParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::HoverParams", nullptr); + builder.addField("textDocument", &obj.textDocument); + builder.addField("position", &obj.position); + builder.addField("workDoneToken", &obj, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo HoverParams::g_rttiInfo = _makeHoverParamsRtti(); +const UnownedStringSlice HoverParams::methodName = + UnownedStringSlice::fromLiteral("textDocument/hover"); + +static const StructRttiInfo _makeMarkupContentRtti() +{ + MarkupContent obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::MarkupContent", nullptr); + builder.addField("kind", &obj.kind); + builder.addField("value", &obj.value); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo MarkupContent::g_rttiInfo = _makeMarkupContentRtti(); + +static const StructRttiInfo _makeHoverRtti() +{ + Hover obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::Hover", nullptr); + builder.addField("contents", &obj.contents); + builder.addField("range", &obj.range); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo Hover::g_rttiInfo = _makeHoverRtti(); + +static const StructRttiInfo _makeDefinitionParamsRtti() +{ + DefinitionParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::DefinitionParams", nullptr); + builder.addField("textDocument", &obj.textDocument); + builder.addField("position", &obj.position); + builder.addField("workDoneToken", &obj, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo DefinitionParams::g_rttiInfo = _makeDefinitionParamsRtti(); +const UnownedStringSlice DefinitionParams::methodName = + UnownedStringSlice::fromLiteral("textDocument/definition"); + +static const StructRttiInfo _makeCompletionParamsRtti() +{ + CompletionParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionParams", nullptr); + builder.addField("textDocument", &obj.textDocument); + builder.addField("position", &obj.position); + builder.addField("workDoneToken", &obj, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo CompletionParams::g_rttiInfo = _makeCompletionParamsRtti(); +const UnownedStringSlice CompletionParams::methodName = + UnownedStringSlice::fromLiteral("textDocument/completion"); + +static const StructRttiInfo _makeCompletionItemRtti() +{ + CompletionItem obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionItem", nullptr); + builder.addField("label", &obj.label, StructRttiInfo::Flag::Optional); + builder.addField("detail", &obj.detail, StructRttiInfo::Flag::Optional); + builder.addField("kind", &obj.kind, StructRttiInfo::Flag::Optional); + builder.addField("documentation", &obj.documentation, StructRttiInfo::Flag::Optional); + builder.addField("data", &obj.data, StructRttiInfo::Flag::Optional); + builder.addField("commitCharacters", &obj.commitCharacters, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo CompletionItem::g_rttiInfo = _makeCompletionItemRtti(); + +static const StructRttiInfo _makeSemanticTokensParamsRtti() +{ + SemanticTokensParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::SemanticTokensParams", nullptr); + builder.addField("textDocument", &obj.textDocument); + builder.addField("workDoneToken", &obj.workDoneToken, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo SemanticTokensParams::g_rttiInfo = _makeSemanticTokensParamsRtti(); +const UnownedStringSlice SemanticTokensParams::methodName = + UnownedStringSlice::fromLiteral("textDocument/semanticTokens/full"); + +static const StructRttiInfo _makeSemanticTokensRtti() +{ + SemanticTokens obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::SemanticTokens", nullptr); + builder.addField("resultId", &obj.resultId); + builder.addField("data", &obj.data); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo SemanticTokens::g_rttiInfo = _makeSemanticTokensRtti(); + +static const StructRttiInfo _makeSignatureHelpParamsRtti() +{ + SignatureHelpParams obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::SignatureHelpParams", nullptr); + builder.addField("textDocument", &obj.textDocument); + builder.addField("position", &obj.position); + builder.addField("workDoneToken", &obj.workDoneToken, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo SignatureHelpParams::g_rttiInfo = _makeSignatureHelpParamsRtti(); +const UnownedStringSlice SignatureHelpParams::methodName = + UnownedStringSlice::fromLiteral("textDocument/signatureHelp"); + +static const StructRttiInfo _makeParameterInformationRtti() +{ + ParameterInformation obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::ParameterInformation", nullptr); + builder.addField("label", &obj.label); + builder.addField("documentation", &obj.documentation, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo ParameterInformation::g_rttiInfo = _makeParameterInformationRtti(); + +static const StructRttiInfo _makeSignatureInformationRtti() +{ + SignatureInformation obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::SignatureInformation", nullptr); + builder.addField("label", &obj.label); + builder.addField("parameters", &obj.parameters); + builder.addField("documentation", &obj.documentation, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo SignatureInformation::g_rttiInfo = _makeSignatureInformationRtti(); + +static const StructRttiInfo _makeSignatureHelpRtti() +{ + SignatureHelp obj; + StructRttiBuilder builder(&obj, "LanguageServerProtocol::SignatureHelp", nullptr); + builder.addField("signatures", &obj.signatures); + builder.addField("activeParameter", &obj.activeParameter); + builder.addField("activeSignature", &obj.activeSignature); + builder.ignoreUnknownFields(); + return builder.make(); +} +const StructRttiInfo SignatureHelp::g_rttiInfo = _makeSignatureHelpRtti(); + +} // namespace LanguageServerProtocol + +} diff --git a/source/slang/slang-language-server-protocol.h b/source/slang/slang-language-server-protocol.h new file mode 100644 index 000000000..29fbaa701 --- /dev/null +++ b/source/slang/slang-language-server-protocol.h @@ -0,0 +1,636 @@ +#pragma once + +#include "../../slang-com-helper.h" +#include "../../slang-com-ptr.h" +#include "../../slang.h" + +#include "../../source/core/slang-rtti-info.h" +#include "../../source/compiler-core/slang-json-value.h" + +namespace Slang +{ +namespace LanguageServerProtocol +{ +struct ServerInfo +{ + String name; + String version; + + static const StructRttiInfo g_rttiInfo; +}; + +enum class TextDocumentSyncKind +{ + None = 0, + Full = 1, + Incremental = 2 +}; + +struct TextDocumentSyncOptions +{ + bool openClose; + int32_t change; // TextDocumentSyncKind + static const StructRttiInfo g_rttiInfo; +}; + +struct WorkDoneProgressParams +{ + /** + * An optional token that a server can use to report work done progress. + */ + String workDoneToken; // optional + + static const StructRttiInfo g_rttiInfo; +}; + +struct CompletionOptions : public WorkDoneProgressParams +{ + /** + * Most tools trigger completion request automatically without explicitly + * requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they + * do so when the user starts to type an identifier. For example if the user + * types `c` in a JavaScript file code complete will automatically pop up + * present `console` besides others as a completion item. Characters that + * make up identifiers don't need to be listed here. + * + * If code complete should automatically be trigger on characters not being + * valid inside an identifier (for example `.` in JavaScript) list them in + * `triggerCharacters`. + */ + List<String> triggerCharacters; + + /** + * The list of all possible characters that commit a completion. This field + * can be used if clients don't support individual commit characters per + * completion item. See client capability + * `completion.completionItem.commitCharactersSupport`. + * + * If a server provides both `allCommitCharacters` and commit characters on + * an individual completion item the ones on the completion item win. + * + * @since 3.2.0 + */ + List<String> allCommitCharacters; + + /** + * The server provides support to resolve additional + * information for a completion item. + */ + bool resolveProvider; + + static const StructRttiInfo g_rttiInfo; +}; + +struct SemanticTokensLegend +{ + /** + * The token types a server uses. + */ + List<String> tokenTypes; + + /** + * The token modifiers a server uses. + */ + List<String> tokenModifiers; + + static const StructRttiInfo g_rttiInfo; +}; + + +struct SemanticTokensOptions +{ + /** + * The legend used by the server + */ + SemanticTokensLegend legend; + + /** + * Server supports providing semantic tokens for a specific range + * of a document. + */ + bool range; + + /** + * Server supports providing semantic tokens for a full document. + */ + bool full; + + static const StructRttiInfo g_rttiInfo; +}; + +struct SignatureHelpOptions +{ + /** + * The characters that trigger signature help + * automatically. + */ + List<String> triggerCharacters; + + /** + * List of characters that re-trigger signature help. + * + * These trigger characters are only active when signature help is already + * showing. All trigger characters are also counted as re-trigger + * characters. + * + * @since 3.15.0 + */ + List<String> retriggerCharacters; + + static const StructRttiInfo g_rttiInfo; +}; + +struct TextDocumentItem +{ + String uri; + String languageId; + int version; + String text; + static const StructRttiInfo g_rttiInfo; +}; + +struct TextDocumentIdentifier +{ + String uri; + static const StructRttiInfo g_rttiInfo; +}; + +struct VersionedTextDocumentIdentifier +{ + String uri; + int version; + static const StructRttiInfo g_rttiInfo; +}; + +struct Position +{ + int line = -1; + int character = -1; + static const StructRttiInfo g_rttiInfo; +}; + +struct Range +{ + Position start; + Position end; + static const StructRttiInfo g_rttiInfo; +}; + +struct DidOpenTextDocumentParams +{ + TextDocumentItem textDocument; + static const StructRttiInfo g_rttiInfo; + static const UnownedStringSlice methodName; +}; + +struct TextDocumentContentChangeEvent +{ + Range range; // optional + String text; + static const StructRttiInfo g_rttiInfo; +}; + +struct DidChangeTextDocumentParams +{ + VersionedTextDocumentIdentifier textDocument; + List<TextDocumentContentChangeEvent> contentChanges; + static const StructRttiInfo g_rttiInfo; + static const UnownedStringSlice methodName; +}; + +struct DidCloseTextDocumentParams +{ + TextDocumentIdentifier textDocument; + static const StructRttiInfo g_rttiInfo; + static const UnownedStringSlice methodName; +}; + +struct ServerCapabilities +{ + String positionEncoding; + TextDocumentSyncOptions textDocumentSync; + bool hoverProvider; + bool definitionProvider; + CompletionOptions completionProvider; + SemanticTokensOptions semanticTokensProvider; + SignatureHelpOptions signatureHelpProvider; + static const StructRttiInfo g_rttiInfo; +}; + +struct WorkspaceFolder +{ + String uri; + String name; + static const StructRttiInfo g_rttiInfo; +}; + +struct InitializeParams +{ + List<WorkspaceFolder> workspaceFolders; + static const UnownedStringSlice methodName; + static const StructRttiInfo g_rttiInfo; +}; + +struct NullResponse +{ + static const StructRttiInfo g_rttiInfo; + static NullResponse* get(); +}; + +struct InitializeResult +{ + ServerCapabilities capabilities; + ServerInfo serverInfo; + + static const StructRttiInfo g_rttiInfo; +}; + +struct ShutdownParams +{ + static const UnownedStringSlice methodName; +}; + +struct ExitParams +{ + static const UnownedStringSlice methodName; +}; + +typedef uint32_t DiagnosticSeverity; +/** + * Reports an error. + */ +const DiagnosticSeverity kDiagnosticsSeverityError = 1; +/** + * Reports a warning. + */ +const DiagnosticSeverity kDiagnosticsSeverityWarning = 2; +/** + * Reports an information. + */ +const DiagnosticSeverity kDiagnosticsSeverityInformation = 3; +/** + * Reports a hint. + */ +const DiagnosticSeverity kDiagnosticsSeverityHint = 4; + + +struct Location +{ + String uri; + Range range; + static const StructRttiInfo g_rttiInfo; +}; + +struct DiagnosticRelatedInformation +{ + /** + * The location of this related diagnostic information. + */ + Location location; + + /** + * The message of this related diagnostic information. + */ + String message; + + static const StructRttiInfo g_rttiInfo; +}; + +struct Diagnostic +{ + /** + * The range at which the message applies. + */ + Range range; + + /** + * The diagnostic's severity. Can be omitted. If omitted it is up to the + * client to interpret diagnostics as error, warning, info or hint. + */ + DiagnosticSeverity severity; + + /** + * The diagnostic's code, which might appear in the user interface. + */ + int32_t code; + + /** + * A human-readable string describing the source of this + * diagnostic, e.g. 'typescript' or 'super lint'. + */ + String source; + + /** + * The diagnostic's message. + */ + String message; + + /** + * An array of related diagnostic information, e.g. when symbol-names within + * a scope collide all definitions can be marked via this property. + */ + List<DiagnosticRelatedInformation> relatedInformation; + + bool operator==(const Diagnostic& other) const + { + return code == other.code && range.start.line == other.range.start.line && + message == other.message; + } + + HashCode getHashCode() const + { + return combineHash( + code, combineHash(range.start.line, message.getHashCode())); + } + + static const StructRttiInfo g_rttiInfo; +}; + +struct PublishDiagnosticsParams +{ + /** + * The URI for which diagnostic information is reported. + */ + String uri; + + /** + * An array of diagnostic information items. + */ + List<Diagnostic> diagnostics; + + static const StructRttiInfo g_rttiInfo; +}; + +struct TextDocumentPositionParams +{ + /** + * The text document. + */ + TextDocumentIdentifier textDocument; + + /** + * The position inside the text document. + */ + Position position; + + static const StructRttiInfo g_rttiInfo; +}; + +struct HoverParams + : TextDocumentPositionParams + , WorkDoneProgressParams +{ + static const StructRttiInfo g_rttiInfo; + static const UnownedStringSlice methodName; +}; + +struct DefinitionParams + : TextDocumentPositionParams + , WorkDoneProgressParams +{ + static const StructRttiInfo g_rttiInfo; + static const UnownedStringSlice methodName; +}; + +struct MarkupContent +{ + /** + * The type of the Markup + */ + String kind; + + /** + * The content itself + */ + String value; + + static const StructRttiInfo g_rttiInfo; +}; + +struct Hover +{ + /** + * The hover's content + */ + MarkupContent contents; + + /** + * An optional range is a range inside a text document + * that is used to visualize a hover, e.g. by changing the background color. + */ + Range range; + + static const StructRttiInfo g_rttiInfo; +}; + +struct CompletionParams + : TextDocumentPositionParams + , WorkDoneProgressParams +{ + static const StructRttiInfo g_rttiInfo; + static const UnownedStringSlice methodName; +}; + +typedef int32_t CompletionItemKind; +const CompletionItemKind kCompletionItemKindText = 1; +const CompletionItemKind kCompletionItemKindMethod = 2; +const CompletionItemKind kCompletionItemKindFunction = 3; +const CompletionItemKind kCompletionItemKindConstructor = 4; +const CompletionItemKind kCompletionItemKindField = 5; +const CompletionItemKind kCompletionItemKindVariable = 6; +const CompletionItemKind kCompletionItemKindClass = 7; +const CompletionItemKind kCompletionItemKindInterface = 8; +const CompletionItemKind kCompletionItemKindModule = 9; +const CompletionItemKind kCompletionItemKindProperty = 10; +const CompletionItemKind kCompletionItemKindUnit = 11; +const CompletionItemKind kCompletionItemKindValue = 12; +const CompletionItemKind kCompletionItemKindEnum = 13; +const CompletionItemKind kCompletionItemKindKeyword = 14; +const CompletionItemKind kCompletionItemKindSnippet = 15; +const CompletionItemKind kCompletionItemKindColor = 16; +const CompletionItemKind kCompletionItemKindFile = 17; +const CompletionItemKind kCompletionItemKindReference = 18; +const CompletionItemKind kCompletionItemKindFolder = 19; +const CompletionItemKind kCompletionItemKindEnumMember = 20; +const CompletionItemKind kCompletionItemKindConstant = 21; +const CompletionItemKind kCompletionItemKindStruct = 22; +const CompletionItemKind kCompletionItemKindEvent = 23; +const CompletionItemKind kCompletionItemKindOperator = 24; +const CompletionItemKind kCompletionItemKindTypeParameter = 25; + +struct CompletionItem +{ + /** + * The label of this completion item. + * + * The label property is also by default the text that + * is inserted when selecting this completion. + * + * If label details are provided the label itself should + * be an unqualified name of the completion item. + */ + String label; + + /** + * The kind of this completion item. Based of the kind + * an icon is chosen by the editor. The standardized set + * of available values is defined in `CompletionItemKind`. + */ + CompletionItemKind kind; + + /** + * A human-readable string with additional information + * about this item, like type or symbol information. + */ + String detail; + + /** + * A human-readable string that represents a doc-comment. + */ + MarkupContent documentation; + + /** + * An optional set of characters that when pressed while this completion is + * active will accept it first and then type that character. *Note* that all + * commit characters should have `length=1` and that superfluous characters + * will be ignored. + */ + List<String> commitCharacters; + + // Additional data. + String data; + + static const StructRttiInfo g_rttiInfo; +}; + +struct SemanticTokensParams : WorkDoneProgressParams +{ + TextDocumentIdentifier textDocument; + + static const UnownedStringSlice methodName; + + static const StructRttiInfo g_rttiInfo; +}; + + +struct SemanticTokens +{ + /** + * An optional result id. If provided and clients support delta updating + * the client will include the result id in the next semantic token request. + * A server can then instead of computing all semantic tokens again simply + * send a delta. + */ + String resultId; + + /** + * The actual tokens. + */ + List<uint32_t> data; + + static const StructRttiInfo g_rttiInfo; +}; + +struct SignatureHelpParams + : TextDocumentPositionParams + , WorkDoneProgressParams +{ + static const UnownedStringSlice methodName; + + static const StructRttiInfo g_rttiInfo; +}; + +/** + * Represents a parameter of a callable-signature. A parameter can + * have a label and a doc-comment. + */ +struct ParameterInformation +{ + /** + * The label of this parameter information. + * + * Either a string or an inclusive start and exclusive end offsets within + * its containing signature label. (see SignatureInformation.label). The + * offsets are based on a UTF-16 string representation as `Position` and + * `Range` does. + * + * *Note*: a label of type string should be a substring of its containing + * signature label. Its intended use case is to highlight the parameter + * label part in the `SignatureInformation.label`. + */ + uint32_t label[2]; + + /** + * The human-readable doc-comment of this parameter. Will be shown + * in the UI but can be omitted. + */ + MarkupContent documentation; + + static const StructRttiInfo g_rttiInfo; +}; + +/** + * Represents the signature of something callable. A signature + * can have a label, like a function-name, a doc-comment, and + * a set of parameters. + */ +struct SignatureInformation +{ + /** + * The label of this signature. Will be shown in + * the UI. + */ + String label; + + /** + * The human-readable doc-comment of this signature. Will be shown + * in the UI but can be omitted. + */ + MarkupContent documentation; + + /** + * The parameters of this signature. + */ + List<ParameterInformation> parameters; + + static const StructRttiInfo g_rttiInfo; +}; + +struct SignatureHelp +{ + /** + * One or more signatures. If no signatures are available the signature help + * request should return `null`. + */ + List<SignatureInformation> signatures; + + /** + * The active signature. If omitted or the value lies outside the + * range of `signatures` the value defaults to zero or is ignore if + * the `SignatureHelp` as no signatures. + * + * Whenever possible implementors should make an active decision about + * the active signature and shouldn't rely on a default value. + * + * In future version of the protocol this property might become + * mandatory to better express this. + */ + uint32_t activeSignature; + + /** + * The active parameter of the active signature. If omitted or the value + * lies outside the range of `signatures[activeSignature].parameters` + * defaults to 0 if the active signature has parameters. If + * the active signature has no parameters it is ignored. + * In future version of the protocol this property might become + * mandatory to better express the active parameter if the + * active signature does have any. + */ + uint32_t activeParameter; + + static const StructRttiInfo g_rttiInfo; +}; + + +} // namespace LanguageServerProtocol +} // namespace Slang diff --git a/source/slang/slang-language-server-semantic-tokens.cpp b/source/slang/slang-language-server-semantic-tokens.cpp new file mode 100644 index 000000000..42042e1c0 --- /dev/null +++ b/source/slang/slang-language-server-semantic-tokens.cpp @@ -0,0 +1,611 @@ +#include "slang-language-server-semantic-tokens.h" +#include "slang-visitor.h" +#include "slang-ast-support-types.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); + } + 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"}; + +static_assert(SLANG_COUNT_OF(kSemanticTokenTypes) == (int)SemanticTokenType::_NormalText, "kSemanticTokenTypes must match SemanticTokenType"); + +SemanticToken _createSemanticToken(SourceManager* manager, SourceLoc loc, Name* name) +{ + SemanticToken token; + auto humaneLoc = manager->getHumaneLoc(loc, SourceLocType::Actual); + token.line = (int)(humaneLoc.line - 1); + token.col = (int)(humaneLoc.column - 1); + token.length = + name ? (int)(name->text.getLength()) : 0; + token.type = SemanticTokenType::_NormalText; + return token; +} + +List<SemanticToken> getSemanticTokens(Linkage* linkage, Module* module, UnownedStringSlice fileName) +{ + auto manager = linkage->getSourceManager(); + + List<SemanticToken> result; + auto maybeInsertToken = [&](const SemanticToken& token) + { + if (token.line >= 0 && token.col >= 0 && token.length > 0 && + token.type != SemanticTokenType::_NormalText) + result.add(token); + }; + + iterateAST( + fileName, + manager, + module->getModuleDecl(), + [&](SyntaxNode* node) + { + if (auto declRef = as<DeclRefExpr>(node)) + { + if (declRef->name) + { + // Don't look at the expr if it is defined in a different file. + if (!manager->getHumaneLoc(declRef->loc, SourceLocType::Actual) + .pathInfo.foundPath.getUnownedSlice() + .endsWithCaseInsensitive(fileName)) + return; + + SemanticToken token = + _createSemanticToken(manager, declRef->loc, declRef->name); + auto target = declRef->declRef.decl; + if (as<AggTypeDecl>(target)) + { + if (target->hasModifier<BuiltinTypeModifier>()) + return; + token.type = SemanticTokenType::Type; + } + else if (as<SimpleTypeDecl>(target)) + { + token.type = SemanticTokenType::Type; + } + else if (as<PropertyDecl>(target)) + { + token.type = SemanticTokenType::Property; + } + else if (as<ParamDecl>(target)) + { + token.type = SemanticTokenType::Parameter; + } + else if (as<VarDecl>(target)) + { + token.type = SemanticTokenType::Variable; + } + else if (as<FunctionDeclBase>(target)) + { + token.type = SemanticTokenType::Function; + } + else if (as<EnumCaseDecl>(target)) + { + token.type = SemanticTokenType::EnumMember; + } + else if (as<NamespaceDecl>(target)) + { + token.type = SemanticTokenType::Namespace; + } + + if (as<CallableDecl>(target)) + { + if (target->hasModifier<ImplicitConversionModifier>()) + return; + } + maybeInsertToken(token); + } + + } + else if (auto typeDecl = as<SimpleTypeDecl>(node)) + { + if (typeDecl->getName()) + { + SemanticToken token = + _createSemanticToken(manager, typeDecl->getNameLoc(), typeDecl->getName()); + token.type = SemanticTokenType::Type; + maybeInsertToken(token); + } + } + else if (auto aggTypeDecl = as<AggTypeDeclBase>(node)) + { + if (aggTypeDecl->getName()) + { + SemanticToken token = _createSemanticToken( + manager, aggTypeDecl->getNameLoc(), aggTypeDecl->getName()); + token.type = SemanticTokenType::Type; + maybeInsertToken(token); + } + } + else if (auto enumCase = as<EnumCaseDecl>(node)) + { + if (enumCase->getName()) + { + SemanticToken token = _createSemanticToken( + manager, enumCase->getNameLoc(), enumCase->getName()); + token.type = SemanticTokenType::EnumMember; + maybeInsertToken(token); + } + } + else if (auto propertyDecl = as<PropertyDecl>(node)) + { + if (propertyDecl->getName()) + { + SemanticToken token = _createSemanticToken( + manager, propertyDecl->getNameLoc(), propertyDecl->getName()); + token.type = SemanticTokenType::Property; + maybeInsertToken(token); + } + } + else if (auto funcDecl = as<FuncDecl>(node)) + { + if (funcDecl->getName()) + { + SemanticToken token = _createSemanticToken( + manager, funcDecl->getNameLoc(), funcDecl->getName()); + token.type = SemanticTokenType::Function; + maybeInsertToken(token); + } + } + else if (auto varDecl = as<VarDeclBase>(node)) + { + if (varDecl->getName()) + { + SemanticToken token = _createSemanticToken( + manager, varDecl->getNameLoc(), varDecl->getName()); + token.type = SemanticTokenType::Variable; + maybeInsertToken(token); + } + } + }); + return result; +} + +List<uint32_t> getEncodedTokens(List<SemanticToken>& tokens) +{ + List<uint32_t> result; + if (tokens.getCount() == 0) + return result; + + std::sort(tokens.begin(), tokens.end()); + + // Encode the first token as is. + result.add((uint32_t)tokens[0].line); + result.add((uint32_t)tokens[0].col); + result.add((uint32_t)tokens[0].length); + result.add((uint32_t)tokens[0].type); + result.add(0); + + // Encode the rest tokens as deltas. + uint32_t prevLine = (uint32_t)tokens[0].line; + uint32_t prevCol = (uint32_t)tokens[0].col; + for (Index i = 1; i < tokens.getCount(); i++) + { + uint32_t thisLine = (uint32_t)tokens[i].line; + uint32_t thisCol = (uint32_t)tokens[i].col; + if (thisLine == prevLine && thisCol == prevCol) + continue; + + uint32_t deltaLine = thisLine - prevLine; + uint32_t deltaCol = deltaLine == 0 ? thisCol - prevCol : thisCol; + + result.add(deltaLine); + result.add(deltaCol); + result.add((uint32_t)tokens[i].length); + result.add((uint32_t)tokens[i].type); + result.add(0); + + prevLine = thisLine; + prevCol = thisCol; + } + + return result; +} + +} // namespace Slang diff --git a/source/slang/slang-language-server-semantic-tokens.h b/source/slang/slang-language-server-semantic-tokens.h new file mode 100644 index 000000000..e0b8064b5 --- /dev/null +++ b/source/slang/slang-language-server-semantic-tokens.h @@ -0,0 +1,36 @@ +#pragma once + +#include "../../slang.h" +#include "../core/slang-basic.h" +#include "slang-ast-all.h" +#include "slang-syntax.h" +#include "slang-compiler.h" + +namespace Slang +{ +enum class SemanticTokenType +{ + Type, EnumMember, Variable, Parameter, Function, Property, Namespace, _NormalText +}; +extern const char* kSemanticTokenTypes[(int)SemanticTokenType::_NormalText]; + +struct SemanticToken +{ + int line; + int col; + int length; + SemanticTokenType type; + bool operator<(const SemanticToken& other) const + { + if (line < other.line) + return true; + if (line == other.line) + return col < other.col; + return false; + } +}; +List<SemanticToken> getSemanticTokens( + Linkage* linkage, Module* module, UnownedStringSlice fileName); +List<uint32_t> getEncodedTokens(List<SemanticToken>& tokens); + +} // namespace Slang diff --git a/source/slang/slang-language-server.cpp b/source/slang/slang-language-server.cpp new file mode 100644 index 000000000..b8708d48e --- /dev/null +++ b/source/slang/slang-language-server.cpp @@ -0,0 +1,1044 @@ +// language-server.cpp + +// This file implements the language server for Slang, conforming to the Language Server Protocol. +// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/ + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <thread> +#include "../core/slang-secure-crt.h" +#include "../../slang-com-helper.h" +#include "../compiler-core/slang-json-rpc-connection.h" +#include "slang-language-server-protocol.h" +#include "slang-language-server.h" +#include "slang-workspace-version.h" +#include "slang-language-server-ast-lookup.h" +#include "slang-language-server-collect-member.h" +#include "slang-language-server-semantic-tokens.h" +#include "slang-ast-print.h" +#include "slang-doc-markdown-writer.h" + +namespace Slang +{ +using namespace LanguageServerProtocol; + +class LanguageServer +{ +public: + RefPtr<JSONRPCConnection> m_connection; + ComPtr<slang::IGlobalSession> m_session; + RefPtr<Workspace> m_workspace; + Dictionary<String, String> m_lastPublishedDiagnostics; + time_t m_lastDiagnosticUpdateTime = 0; + + bool m_quit = false; + List<LanguageServerProtocol::WorkspaceFolder> m_workspaceFolders; + + SlangResult init(const LanguageServerProtocol::InitializeParams& args); + SlangResult execute(); + void update(); + SlangResult didOpenTextDocument(const LanguageServerProtocol::DidOpenTextDocumentParams& args); + SlangResult didCloseTextDocument( + const LanguageServerProtocol::DidCloseTextDocumentParams& args); + SlangResult didChangeTextDocument( + const LanguageServerProtocol::DidChangeTextDocumentParams& args); + SlangResult hover(const LanguageServerProtocol::HoverParams& args, const JSONValue& responseId); + SlangResult gotoDefinition(const LanguageServerProtocol::DefinitionParams& args, const JSONValue& responseId); + SlangResult completion( + const LanguageServerProtocol::CompletionParams& args, const JSONValue& responseId); + SlangResult completionResolve( + const LanguageServerProtocol::CompletionItem& args, const JSONValue& responseId); + SlangResult semanticTokens( + const LanguageServerProtocol::SemanticTokensParams& args, const JSONValue& responseId); + SlangResult signatureHelp( + const LanguageServerProtocol::SignatureHelpParams& args, const JSONValue& responseId); + + List<LanguageServerProtocol::CompletionItem> collectMembers( + WorkspaceVersion* wsVersion, Module* module, Expr* baseExpr); + +private: + SlangResult _executeSingle(); + slang::IGlobalSession* getOrCreateGlobalSession(); + void resetDiagnosticUpdateTime(); + void publishDiagnostics(); +}; + + +SlangResult LanguageServer::init(const InitializeParams& args) +{ + SLANG_RETURN_ON_FAIL(m_connection->initWithStdStreams(JSONRPCConnection::CallStyle::Object)); + m_workspaceFolders = args.workspaceFolders; + m_workspace = new Workspace(); + List<URI> rootUris; + for (auto& wd : m_workspaceFolders) + { + rootUris.add(URI::fromString(wd.uri.getUnownedSlice())); + } + m_workspace->init(rootUris, getOrCreateGlobalSession()); + return SLANG_OK; +} + +slang::IGlobalSession* LanguageServer::getOrCreateGlobalSession() +{ + if (!m_session) + { + // Just create the global session in the regular way if there isn't one set + if (SLANG_FAILED(slang_createGlobalSession(SLANG_API_VERSION, m_session.writeRef()))) + { + return nullptr; + } + } + + return m_session; +} + +void LanguageServer::resetDiagnosticUpdateTime() { time(&m_lastDiagnosticUpdateTime); } + +String uriToCanonicalPath(const String& uri) +{ + String canonnicalPath; + Path::getCanonical(URI::fromString(uri.getUnownedSlice()).getPath(), canonnicalPath); + return canonnicalPath; +} + +SlangResult LanguageServer::_executeSingle() +{ + // If we don't have a message, we can quit for now + if (!m_connection->hasMessage()) + { + return SLANG_OK; + } + + const JSONRPCMessageType msgType = m_connection->getMessageType(); + + switch (msgType) + { + case JSONRPCMessageType::Call: + { + JSONRPCCall call; + SLANG_RETURN_ON_FAIL(m_connection->getRPCOrSendError(&call)); + + // Do different things + if (call.method == ExitParams::methodName) + { + m_quit = true; + return SLANG_OK; + } + else if (call.method == ShutdownParams::methodName) + { + m_connection->sendResult(NullResponse::get(), call.id); + return SLANG_OK; + } + else if (call.method == InitializeParams::methodName) + { + InitializeParams args = {}; + m_connection->toNativeArgsOrSendError(call.params, &args, call.id); + + init(args); + + InitializeResult result = {}; + result.serverInfo.name = "SlangLanguageServer"; + result.serverInfo.version = "1.0"; + result.capabilities.positionEncoding = "utf-8"; + result.capabilities.textDocumentSync.openClose = true; + result.capabilities.textDocumentSync.change = (int)TextDocumentSyncKind::Full; + result.capabilities.hoverProvider = true; + result.capabilities.definitionProvider = true; + const char* commitChars[] = {",", ".", ";", ":", "(", ")", "[", "]", + "<", ">", "{", "}", "*", "&", "^", "%", + "!", "-", "=", "+", "|", "/", "?"}; + for (auto ch : commitChars) + result.capabilities.completionProvider.allCommitCharacters.add(ch); + result.capabilities.completionProvider.triggerCharacters.add("."); + result.capabilities.completionProvider.triggerCharacters.add(":"); + result.capabilities.completionProvider.resolveProvider = true; + result.capabilities.completionProvider.workDoneToken = ""; + result.capabilities.semanticTokensProvider.full = true; + result.capabilities.semanticTokensProvider.range = false; + result.capabilities.signatureHelpProvider.triggerCharacters.add("("); + result.capabilities.signatureHelpProvider.retriggerCharacters.add(","); + for (auto tokenType : kSemanticTokenTypes) + result.capabilities.semanticTokensProvider.legend.tokenTypes.add(tokenType); + m_connection->sendResult(&result, call.id); + return SLANG_OK; + } + else if (call.method == DidOpenTextDocumentParams::methodName) + { + DidOpenTextDocumentParams args; + SLANG_RETURN_ON_FAIL( + m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + return didOpenTextDocument(args); + } + else if (call.method == DidCloseTextDocumentParams::methodName) + { + DidCloseTextDocumentParams args; + SLANG_RETURN_ON_FAIL( + m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + return didCloseTextDocument(args); + } + else if (call.method == DidChangeTextDocumentParams::methodName) + { + DidChangeTextDocumentParams args; + SLANG_RETURN_ON_FAIL( + m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + return didChangeTextDocument(args); + } + else if (call.method == HoverParams::methodName) + { + HoverParams args; + SLANG_RETURN_ON_FAIL( + m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + return hover(args, call.id); + } + else if (call.method == DefinitionParams::methodName) + { + DefinitionParams args; + SLANG_RETURN_ON_FAIL( + m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + return gotoDefinition(args, call.id); + } + else if (call.method == CompletionParams::methodName) + { + CompletionParams args; + SLANG_RETURN_ON_FAIL( + m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + return completion(args, call.id); + } + else if (call.method == SemanticTokensParams::methodName) + { + SemanticTokensParams args; + SLANG_RETURN_ON_FAIL( + m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + return semanticTokens(args, call.id); + } + else if (call.method == SignatureHelpParams::methodName) + { + SignatureHelpParams args; + SLANG_RETURN_ON_FAIL( + m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + return signatureHelp(args, call.id); + } + else if (call.method == "completionItem/resolve") + { + CompletionItem args; + SLANG_RETURN_ON_FAIL( + m_connection->toNativeArgsOrSendError(call.params, &args, call.id)); + return completionResolve(args, call.id); + + } + else if (call.method == "initialized") + { + return SLANG_OK; + } + else if (call.method.startsWith("$/")) + { + // Ignore. + return SLANG_OK; + } + else + { + return m_connection->sendError(JSONRPC::ErrorCode::MethodNotFound, call.id); + } + } + default: + { + return m_connection->sendError( + JSONRPC::ErrorCode::InvalidRequest, m_connection->getCurrentMessageId()); + } + } +} + +SlangResult LanguageServer::didOpenTextDocument(const DidOpenTextDocumentParams& args) +{ + String canonicalPath = uriToCanonicalPath(args.textDocument.uri); + m_workspace->openDoc(canonicalPath, args.textDocument.text); + return SLANG_OK; +} + +String getDeclSignatureString(DeclRef<Decl> declRef, ASTBuilder* astBuilder) +{ + if (declRef.getDecl()) + { + ASTPrinter printer( + astBuilder, + ASTPrinter::OptionFlag::ParamNames | ASTPrinter::OptionFlag::NoInternalKeywords | + ASTPrinter::OptionFlag::SimplifiedBuiltinType); + printer.addDeclSignature(declRef); + return printer.getString(); + } + return "unknown"; +} + + +static String _formatDocumentation(String doc) +{ + // TODO: may want to use DocMarkdownWriter in the future to format the text. + // For now just insert line breaks before `\param` and `\returns` markups. + List<UnownedStringSlice> lines; + StringUtil::split(doc.getUnownedSlice(), '\n', lines); + StringBuilder result; + + for (Index i = 0; i < lines.getCount(); i++) + { + auto trimedLine = lines[i].trimStart(); + if (i > 0) + { + if (trimedLine.startsWith("\\") && lines[i - 1].trim().getLength() != 0) + { + result << " \n"; + } + else + { + result << "\n"; + } + } + if (trimedLine.startsWith("\\returns ")) + { + trimedLine = trimedLine.subString(9, trimedLine.getLength()); + result << "**returns** "; + } + else if (trimedLine.startsWith("\\return ")) + { + trimedLine = trimedLine.subString(8, trimedLine.getLength()); + result << "**Returns** "; + } + result << trimedLine; + } + result << "\n"; + return result.ProduceString(); +} + +static void _tryGetDocumentation(StringBuilder& sb, WorkspaceVersion* workspace, Decl* decl) +{ + auto definingModule = getModuleDecl(decl); + if (definingModule) + { + auto markupAST = workspace->getOrCreateMarkupAST(definingModule); + auto markupEntry = markupAST->getEntry(decl); + if (markupEntry) + { + sb << "\n"; + sb << _formatDocumentation(markupEntry->m_markup); + sb << "\n"; + } + } +} + +SlangResult LanguageServer::hover( + const LanguageServerProtocol::HoverParams& args, const JSONValue& responseId) +{ + String canonicalPath = uriToCanonicalPath(args.textDocument.uri); + auto version = m_workspace->getCurrentVersion(); + Module* parsedModule = version->getOrLoadModule(canonicalPath); + if (!parsedModule) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + auto findResult = findASTNodesAt( + version->linkage->getSourceManager(), + parsedModule->getModuleDecl(), + ASTLookupType::Decl, + canonicalPath.getUnownedSlice(), + args.position.line + 1, + args.position.character + 1); + if (findResult.getCount() == 0 || findResult[0].path.getCount() == 0) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + StringBuilder sb; + Hover hover = {}; + auto leafNode = findResult[0].path.getLast(); + auto fillDeclRefHoverInfo = [&](DeclRef<Decl> declRef) + { + if (declRef.getDecl()) + { + sb << "```\n" + << getDeclSignatureString(declRef, version->linkage->getASTBuilder()) + << "\n```\n"; + + _tryGetDocumentation(sb, version, declRef.getDecl()); + + auto humaneLoc = version->linkage->getSourceManager()->getHumaneLoc( + declRef.getLoc(), SourceLocType::Actual); + sb << "Defined in " << humaneLoc.pathInfo.foundPath << "(" << humaneLoc.line + << ")\n"; + + auto nodeHumaneLoc = + version->linkage->getSourceManager()->getHumaneLoc(leafNode->loc); + hover.range.start.line = int(nodeHumaneLoc.line - 1); + hover.range.end.line = int(nodeHumaneLoc.line - 1); + hover.range.start.character = int(nodeHumaneLoc.column - 1); + if (declRef.getName()) + { + hover.range.end.character = + int(nodeHumaneLoc.column + declRef.getName()->text.getLength() - 1); + } + } + }; + if (auto declRefExpr = as<DeclRefExpr>(leafNode)) + { + fillDeclRefHoverInfo(declRefExpr->declRef); + } + else if (auto overloadedExpr = as<OverloadedExpr>(leafNode)) + { + LookupResultItem& item = overloadedExpr->lookupResult2.item; + fillDeclRefHoverInfo(item.declRef); + } + else if (auto decl = as<Decl>(leafNode)) + { + fillDeclRefHoverInfo(DeclRef<Decl>(decl, nullptr)); + } + if (sb.getLength() == 0) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + else + { + hover.contents.kind = "markdown"; + hover.contents.value = sb.ProduceString(); + m_connection->sendResult(&hover, responseId); + return SLANG_OK; + } +} + +SlangResult LanguageServer::gotoDefinition( + const LanguageServerProtocol::DefinitionParams& args, const JSONValue& responseId) +{ + String canonicalPath = uriToCanonicalPath(args.textDocument.uri); + auto version = m_workspace->getCurrentVersion(); + Module* parsedModule = version->getOrLoadModule(canonicalPath); + if (!parsedModule) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + auto findResult = findASTNodesAt( + version->linkage->getSourceManager(), + parsedModule->getModuleDecl(), + ASTLookupType::Decl, + canonicalPath.getUnownedSlice(), + args.position.line + 1, + args.position.character + 1); + if (findResult.getCount() == 0 || findResult[0].path.getCount() == 0) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + struct LocationResult + { + HumaneSourceLoc loc; + int length; + }; + List<LocationResult> locations; + auto leafNode = findResult[0].path.getLast(); + if (auto declRefExpr = as<DeclRefExpr>(leafNode)) + { + if (declRefExpr->declRef.getDecl()) + { + auto location = version->linkage->getSourceManager()->getHumaneLoc( + declRefExpr->declRef.getNameLoc(), SourceLocType::Actual); + auto name = declRefExpr->declRef.getName(); + locations.add(LocationResult{location, name ? (int)name->text.getLength() : 0}); + } + } + else if (auto overloadedExpr = as<OverloadedExpr>(leafNode)) + { + if (overloadedExpr->lookupResult2.items.getCount()) + { + for (auto item : overloadedExpr->lookupResult2.items) + { + auto location = version->linkage->getSourceManager()->getHumaneLoc( + item.declRef.getNameLoc(), SourceLocType::Actual); + auto name = item.declRef.getName(); + locations.add(LocationResult{location, name ? (int)name->text.getLength() : 0}); + } + } + else + { + LookupResultItem& item = overloadedExpr->lookupResult2.item; + if (item.declRef.getDecl() != nullptr) + { + auto location = version->linkage->getSourceManager()->getHumaneLoc( + item.declRef.getNameLoc(), SourceLocType::Actual); + auto name = item.declRef.getName(); + locations.add(LocationResult{location, name ? (int)name->text.getLength() : 0}); + } + } + + } + if (locations.getCount() == 0) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + else + { + List<Location> results; + for (auto loc : locations) + { + Location result; + result.uri = URI::fromLocalFilePath(loc.loc.pathInfo.foundPath.getUnownedSlice()).uri; + result.range.start.line = int(loc.loc.line - 1); + result.range.start.character = int(loc.loc.column - 1); + result.range.end = result.range.start; + result.range.end.character += loc.length; + results.add(result); + } + m_connection->sendResult(&results, responseId); + return SLANG_OK; + } +} + +bool _isIdentifierChar(char ch) +{ + return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_'; +} + +bool _isWhitespaceChar(char ch) +{ + return ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t'; +} + +SlangResult LanguageServer::completion( + const LanguageServerProtocol::CompletionParams& args, const JSONValue& responseId) +{ + String canonicalPath = uriToCanonicalPath(args.textDocument.uri); + + RefPtr<DocumentVersion> doc; + if (!m_workspace->openedDocuments.TryGetValue(canonicalPath, doc)) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + + auto cursorOffset = doc->getOffset(args.position.line + 1, args.position.character + 1); + if (cursorOffset == -1 || doc->getText().getLength() == 0) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + // Scan backward until we locate a '.' or ':'. + if (cursorOffset == doc->getText().getLength()) + 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] != ':')) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + Index line = 0; + Index col = 0; + doc->offsetToLineCol(cursorOffset, line, col); + auto version = m_workspace->getCurrentVersion(); + Module* parsedModule = version->getOrLoadModule(canonicalPath); + if (!parsedModule) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + auto findResult = findASTNodesAt( + version->linkage->getSourceManager(), + parsedModule->getModuleDecl(), + ASTLookupType::Decl, + canonicalPath.getUnownedSlice(), + line, + col); + if (findResult.getCount() != 1) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + if (findResult[0].path.getCount() == 0) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + 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())) + { + baseExpr = staticMemberExpr->baseExpression; + } + 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())) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + + List<LanguageServerProtocol::CompletionItem> items = collectMembers(version, parsedModule, baseExpr); + m_connection->sendResult(&items, responseId); + return SLANG_OK; +} + +SlangResult LanguageServer::completionResolve( + const LanguageServerProtocol::CompletionItem& args, const JSONValue& responseId) +{ + LanguageServerProtocol::CompletionItem resolvedItem = args; + int itemId = StringToInt(args.data); + auto version = m_workspace->getCurrentVersion(); + if (itemId >= 0 && itemId < version->currentCompletionItems.getCount()) + { + auto decl = version->currentCompletionItems[itemId]; + resolvedItem.detail = getDeclSignatureString( + DeclRef<Decl>(decl, nullptr), version->linkage->getASTBuilder()); + StringBuilder docSB; + _tryGetDocumentation(docSB, version, decl); + resolvedItem.documentation.value = docSB.ProduceString(); + resolvedItem.documentation.kind = "markdown"; + } + m_connection->sendResult(&resolvedItem, responseId); + return SLANG_OK; +} + +SlangResult LanguageServer::semanticTokens( + const LanguageServerProtocol::SemanticTokensParams& args, const JSONValue& responseId) +{ + String canonicalPath = uriToCanonicalPath(args.textDocument.uri); + + auto version = m_workspace->getCurrentVersion(); + Module* parsedModule = version->getOrLoadModule(canonicalPath); + if (!parsedModule) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + + auto tokens = getSemanticTokens(version->linkage, parsedModule, canonicalPath.getUnownedSlice()); + SemanticTokens response; + response.resultId = ""; + response.data = getEncodedTokens(tokens); + m_connection->sendResult(&response, responseId); + return SLANG_OK; +} + +SlangResult LanguageServer::signatureHelp( + const LanguageServerProtocol::SignatureHelpParams& args, const JSONValue& responseId) +{ + String canonicalPath = uriToCanonicalPath(args.textDocument.uri); + + auto version = m_workspace->getCurrentVersion(); + Module* parsedModule = version->getOrLoadModule(canonicalPath); + if (!parsedModule) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + + auto findResult = findASTNodesAt( + version->linkage->getSourceManager(), + parsedModule->getModuleDecl(), + ASTLookupType::Invoke, + canonicalPath.getUnownedSlice(), + args.position.line + 1, + args.position.character + 1); + + if (findResult.getCount() == 0) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + + AppExprBase* appExpr = nullptr; + auto& declPath = findResult[0].path; + for (Index i = declPath.getCount() - 1; i >= 0; i--) + { + if (auto expr = as<AppExprBase>(declPath[i])) + { + // Find the inner most invoke expr that has source token info. + // This allows us to skip the invoke expr nodes for operators/implcit casts. + if (expr->argumentDelimeterLocs.getCount()) + { + appExpr = expr; + break; + } + } + } + if (!appExpr) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + + if (appExpr->argumentDelimeterLocs.getCount() == 0) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + + auto funcExpr = + appExpr->originalFunctionExpr ? appExpr->originalFunctionExpr : appExpr->functionExpr; + if (!funcExpr) + { + m_connection->sendResult(NullResponse::get(), responseId); + return SLANG_OK; + } + + SignatureHelp response; + auto addDeclRef = [&](DeclRef<Decl> declRef) + { + if (!declRef.getDecl()) + return; + + SignatureInformation sigInfo; + + List<Array<Index, 2>> paramRanges; + ASTPrinter printer( + version->linkage->getASTBuilder(), + ASTPrinter::OptionFlag::ParamNames | ASTPrinter::OptionFlag::NoInternalKeywords | + ASTPrinter::OptionFlag::SimplifiedBuiltinType); + + printer.addDeclKindPrefix(declRef.getDecl()); + printer.addDeclPath(declRef); + printer.addDeclParams(declRef, ¶mRanges); + printer.addDeclResultType(declRef); + + sigInfo.label = printer.getString(); + + StringBuilder docSB; + auto humaneLoc = version->linkage->getSourceManager()->getHumaneLoc(declRef.getLoc(), SourceLocType::Actual); + _tryGetDocumentation(docSB, version, declRef.getDecl()); + + docSB << "Defined in " << humaneLoc.pathInfo.foundPath << "(" << humaneLoc.line << ")\n"; + sigInfo.documentation.value = docSB.ProduceString(); + sigInfo.documentation.kind = "markdown"; + + for (auto& range : paramRanges) + { + ParameterInformation paramInfo; + paramInfo.label[0] = (uint32_t)range[0]; + paramInfo.label[1] = (uint32_t)range[1]; + sigInfo.parameters.add(paramInfo); + } + response.signatures.add(sigInfo); + }; + if (auto declRefExpr = as<DeclRefExpr>(funcExpr)) + { + addDeclRef(declRefExpr->declRef); + } + else if (auto overloadedExpr = as<OverloadedExpr>(funcExpr)) + { + for (auto item : overloadedExpr->lookupResult2) + { + addDeclRef(item.declRef); + } + } + response.activeSignature = 0; + response.activeParameter = 0; + for (int i = 1; i < appExpr->argumentDelimeterLocs.getCount(); i++) + { + auto delimLoc = version->linkage->getSourceManager()->getHumaneLoc( + appExpr->argumentDelimeterLocs[i], SourceLocType::Actual); + if (delimLoc.line > args.position.line + 1 || + delimLoc.line == args.position.line + 1 && delimLoc.column >= args.position.character + 1) + { + response.activeParameter = i - 1; + break; + } + } + + m_connection->sendResult(&response, responseId); + return SLANG_OK; +} + + +List<LanguageServerProtocol::CompletionItem> LanguageServer::collectMembers(WorkspaceVersion* version, Module* module, Expr* baseExpr) +{ + List<LanguageServerProtocol::CompletionItem> result; + auto linkage = version->linkage; + Type* type = baseExpr->type.type; + if (auto typeType = as<TypeType>(type)) + { + type = typeType->type; + } + version->currentCompletionItems.clear(); + if (type) + { + if (as<ArithmeticExpressionType>(type)) + { + // 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(); + for (int i = 0; i < elementCount; i++) + { + CompletionItem item; + item.data = 0; + item.detail = typeStr; + item.kind = LanguageServerProtocol::kCompletionItemKindVariable; + item.label = memberNames[i]; + result.add(item); + } + } + else + { + DiagnosticSink sink; + MemberCollectingContext context(linkage, module, &sink); + context.astBuilder = linkage->getASTBuilder(); + collectMembersInType(&context, type); + HashSet<String> deduplicateSet; + for (auto member : context.members) + { + 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); + } + } + + for (auto& item : result) + { + switch (item.kind) + { + case LanguageServerProtocol::kCompletionItemKindMethod: + item.commitCharacters.add("("); + item.commitCharacters.add("["); + item.commitCharacters.add(" "); + break; + default: + item.commitCharacters.add("("); + item.commitCharacters.add(")"); + item.commitCharacters.add("."); + item.commitCharacters.add(";"); + item.commitCharacters.add(":"); + item.commitCharacters.add(","); + item.commitCharacters.add("<"); + item.commitCharacters.add(">"); + item.commitCharacters.add("["); + item.commitCharacters.add("]"); + item.commitCharacters.add("{"); + item.commitCharacters.add("}"); + item.commitCharacters.add("-"); + item.commitCharacters.add("*"); + item.commitCharacters.add("/"); + item.commitCharacters.add("%"); + item.commitCharacters.add("+"); + item.commitCharacters.add("="); + item.commitCharacters.add("&"); + item.commitCharacters.add("|"); + item.commitCharacters.add("!"); + item.commitCharacters.add(" "); + break; + } + } + } + return result; +} + +void LanguageServer::publishDiagnostics() +{ + time_t timeNow = 0; + time(&timeNow); + + if (timeNow - m_lastDiagnosticUpdateTime < 3) + { + return; + } + m_lastDiagnosticUpdateTime = timeNow; + + auto version = m_workspace->getCurrentVersion(); + // Send updates to clear diagnostics for files that no longer have any messages. + List<String> filesToRemove; + for (auto& file : m_lastPublishedDiagnostics) + { + if (!version->diagnostics.ContainsKey(file.Key)) + { + PublishDiagnosticsParams args; + args.uri = URI::fromLocalFilePath(file.Key.getUnownedSlice()).uri; + m_connection->sendCall(UnownedStringSlice("textDocument/publishDiagnostics"), &args); + filesToRemove.add(file.Key); + } + } + for (auto& toRemove : filesToRemove) + { + m_lastPublishedDiagnostics.Remove(toRemove); + } + // Send updates for any files whose diagnostic messages has changed since last update. + for (auto& list : version->diagnostics) + { + auto lastPublished = m_lastPublishedDiagnostics.TryGetValue(list.Key); + if (!lastPublished || *lastPublished != list.Value.originalOutput) + { + PublishDiagnosticsParams args; + args.uri = URI::fromLocalFilePath(list.Key.getUnownedSlice()).uri; + for (auto& d : list.Value.messages) + args.diagnostics.add(d); + m_connection->sendCall(UnownedStringSlice("textDocument/publishDiagnostics"), &args); + m_lastPublishedDiagnostics[list.Key] = list.Value.originalOutput; + } + } +} + +SlangResult LanguageServer::didCloseTextDocument(const DidCloseTextDocumentParams& args) +{ + String canonicalPath = uriToCanonicalPath(args.textDocument.uri); + m_workspace->openedDocuments.Remove(canonicalPath); + m_workspace->invalidate(); + resetDiagnosticUpdateTime(); + return SLANG_OK; +} +SlangResult LanguageServer::didChangeTextDocument(const DidChangeTextDocumentParams& args) +{ + String canonicalPath = uriToCanonicalPath(args.textDocument.uri); + + RefPtr<DocumentVersion> doc; + if (m_workspace->openedDocuments.TryGetValue(canonicalPath, doc)) + { + doc->setText(args.contentChanges[0].text.getUnownedSlice()); + } + m_workspace->invalidate(); + resetDiagnosticUpdateTime(); + return SLANG_OK; +} + +void LanguageServer::update() +{ + if (!m_workspace) + return; + publishDiagnostics(); +} + +SlangResult LanguageServer::execute() +{ + + m_connection = new JSONRPCConnection(); + m_connection->initWithStdStreams(); + + while (m_connection->isActive() && !m_quit) + { + // Consume all messages first. + while (true) + { + m_connection->tryReadMessage(); + if (!m_connection->hasMessage()) + break; + const SlangResult res = _executeSingle(); + + } + + // Now we can use this time to reparse user's code, report diagnostics, etc. + update(); + } + + return SLANG_OK; +} + +SLANG_API SlangResult runLanguageServer() +{ + Slang::LanguageServer server; + SLANG_RETURN_ON_FAIL(server.execute()); + return SLANG_OK; +} + +} // namespace Slang diff --git a/source/slang/slang-language-server.h b/source/slang/slang-language-server.h new file mode 100644 index 000000000..e3abfb2e5 --- /dev/null +++ b/source/slang/slang-language-server.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../../slang.h" + +namespace Slang +{ +SLANG_API SlangResult runLanguageServer(); +} // namespace Slang diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index fc8180e12..5ffb1bf33 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -2922,6 +2922,12 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> return d.dispatch(expr); } + LoweredValInfo visitIncompleteExpr(IncompleteExpr*) + { + SLANG_UNEXPECTED("a valid ast should not contain an IncompleteExpr."); + UNREACHABLE_RETURN(LoweredValInfo()); + } + LoweredValInfo visitVarExpr(VarExpr* expr) { LoweredValInfo info = emitDeclRef( diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index 2604ffd9b..1c32499ef 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -91,6 +91,10 @@ namespace Slang int anonymousCounter = 0; + // Numbers of times we are peeking the same token at `ReadToken` without advancing in + // recover mode. + int sameTokenPeekedTimes = 0; + Scope* outerScope = nullptr; Scope* currentScope = nullptr; @@ -530,6 +534,7 @@ namespace Slang if (tokenReader.peekTokenType() == expected) { isRecovering = false; + sameTokenPeekedTimes = 0; return tokenReader.advanceToken(); } @@ -546,8 +551,22 @@ namespace Slang isRecovering = false; return tokenReader.advanceToken(); } - - return tokenReader.peekToken(); + // This could be dangerous: if `ReadToken()` is being called + // in a loop we may never make forward progress, so we use + // a counter to limit the maximum amount of times we are allowed + // to peek the same token. If the outter parsing logic is + // correct, we will pop back to the right level. If there are + // erroneous parsing logic, this counter is to prevent us + // looping infinitely. + static const int kMaxTokenPeekCount = 64; + sameTokenPeekedTimes++; + if (sameTokenPeekedTimes < kMaxTokenPeekCount) + return tokenReader.peekToken(); + else + { + sameTokenPeekedTimes = 0; + return tokenReader.advanceToken(); + } } } @@ -1238,7 +1257,7 @@ namespace Slang // parent link is set up correctly. static void AddMember(ContainerDecl* container, Decl* member) { - if (container) + if (container && member) { member->parentDecl = container; container->members.add(member); @@ -1325,12 +1344,19 @@ namespace Slang break; } + auto currentCursor = parser->tokenReader.getCursor(); + AddMember(decl, ParseGenericParamDecl(parser, decl)); + // Make sure we make forward progress. + if (parser->tokenReader.getCursor() == currentCursor) + advanceToken(parser); + if (parser->LookAheadToken(TokenType::OpGreater)) break; - parser->ReadToken(TokenType::Comma); + if (!AdvanceIf(parser, TokenType::Comma)) + break; } parser->genericDepth--; parser->ReadToken(TokenType::OpGreater); @@ -1863,7 +1889,7 @@ namespace Slang { GenericAppExpr* genericApp = parser->astBuilder->create<GenericAppExpr>(); - parser->FillPosition(genericApp); // set up scope for lookup + genericApp->loc = base->loc; genericApp->functionExpr = base; parser->ReadToken(TokenType::OpLess); parser->genericDepth++; @@ -1947,12 +1973,12 @@ namespace Slang } return base; } - static Expr* parseMemberType(Parser * parser, Expr* base) + static Expr* parseMemberType(Parser * parser, Expr* base, SourceLoc opLoc) { // When called the :: or . have been consumed, so don't need to consume here. MemberExpr* memberExpr = parser->astBuilder->create<MemberExpr>(); - + memberExpr->memberOperatorLoc = opLoc; parser->FillPosition(memberExpr); memberExpr->baseExpression = base; memberExpr->name = expectIdentifier(parser).name; @@ -2258,13 +2284,17 @@ namespace Slang typeExpr = parseGenericApp(parser, typeExpr); break; case TokenType::Scope: - parser->ReadToken(TokenType::Scope); - typeExpr = parseMemberType(parser, typeExpr); - break; + { + auto opToken = parser->ReadToken(TokenType::Scope); + typeExpr = parseMemberType(parser, typeExpr, opToken.loc); + break; + } case TokenType::Dot: - parser->ReadToken(TokenType::Dot); - typeExpr = parseMemberType(parser, typeExpr); - break; + { + auto opToken = parser->ReadToken(TokenType::Dot); + typeExpr = parseMemberType(parser, typeExpr, opToken.loc); + break; + } default: shouldLoop = false; } @@ -3186,6 +3216,10 @@ namespace Slang { decl->returnType = parser->ParseTypeExp(); } + else + { + decl->returnType.exp = parser->astBuilder->create<IncompleteExpr>(); + } parseStorageDeclBody(parser, decl); @@ -3218,7 +3252,6 @@ namespace Slang static NodeBase* parsePropertyDecl(Parser* parser, void* /*userData*/) { PropertyDecl* decl = parser->astBuilder->create<PropertyDecl>(); - parser->FillPosition(decl); parser->PushScope(decl); // We want to support property declarations with two @@ -3244,6 +3277,7 @@ namespace Slang // if(_peekModernStyleVarDecl(parser)) { + parser->FillPosition(decl); decl->nameAndLoc = expectIdentifier(parser); expect(parser, TokenType::Colon); decl->type = parser->ParseTypeExp(); @@ -3768,10 +3802,12 @@ namespace Slang ContainerDecl* containerDecl, MatchedTokenType matchType) { - while(!AdvanceIfMatch(parser, matchType)) + Token closingBraceToken; + while (!AdvanceIfMatch(parser, matchType, &closingBraceToken)) { ParseDecl(parser, containerDecl); } + containerDecl->closingSourceLoc = closingBraceToken.loc; } static void parseDeclBody( @@ -3823,8 +3859,8 @@ namespace Slang Decl* Parser::ParseStruct() { StructDecl* rs = astBuilder->create<StructDecl>(); - FillPosition(rs); ReadToken("struct"); + FillPosition(rs); // The `struct` keyword may optionally be followed by // attributes that appertain to the struct declaration @@ -3857,8 +3893,8 @@ namespace Slang ClassDecl* Parser::ParseClass() { ClassDecl* rs = astBuilder->create<ClassDecl>(); - FillPosition(rs); ReadToken("class"); + FillPosition(rs); rs->nameAndLoc = expectIdentifier(this); parseOptionalInheritanceClause(this, rs); @@ -3885,7 +3921,6 @@ namespace Slang static Decl* parseEnumDecl(Parser* parser) { EnumDecl* decl = parser->astBuilder->create<EnumDecl>(); - parser->FillPosition(decl); parser->ReadToken("enum"); @@ -3897,6 +3932,8 @@ namespace Slang // AdvanceIf(parser, "class"); + parser->FillPosition(decl); + decl->nameAndLoc = expectIdentifier(parser); @@ -4178,6 +4215,10 @@ namespace Slang { statement = parseCompileTimeStmt(this); } + else if (LookAheadToken("try")) + { + statement = ParseExpressionStatement(); + } else if (LookAheadToken(TokenType::Identifier)) { // We might be looking at a local declaration, or an @@ -5184,7 +5225,7 @@ namespace Slang default: // TODO: should this return an error expression instead of NULL? parser->sink->diagnose(parser->tokenReader.peekLoc(), Diagnostics::syntaxError); - return nullptr; + return parser->astBuilder->create<IncompleteExpr>(); // Either: // - parenthesized expression `(exp)` @@ -5577,6 +5618,10 @@ namespace Slang { indexExpr->indexExpression = parser->ParseExpression(); } + else + { + indexExpr->indexExpression = parser->astBuilder->create<IncompleteExpr>(); + } parser->ReadToken(TokenType::RBracket); expr = indexExpr; @@ -5589,7 +5634,8 @@ namespace Slang InvokeExpr* invokeExpr = parser->astBuilder->create<InvokeExpr>(); invokeExpr->functionExpr = expr; parser->FillPosition(invokeExpr); - parser->ReadToken(TokenType::LParent); + auto lParen = parser->ReadToken(TokenType::LParent); + invokeExpr->argumentDelimeterLocs.add(lParen.loc); while (!parser->tokenReader.isAtEnd()) { if (!parser->LookAheadToken(TokenType::RParent)) @@ -5600,10 +5646,11 @@ namespace Slang } if (!parser->LookAheadToken(TokenType::Comma)) break; - parser->ReadToken(TokenType::Comma); + auto comma = parser->ReadToken(TokenType::Comma); + invokeExpr->argumentDelimeterLocs.add(comma.loc); } - parser->ReadToken(TokenType::RParent); - + auto rParen = parser->ReadToken(TokenType::RParent); + invokeExpr->argumentDelimeterLocs.add(rParen.loc); expr = invokeExpr; } break; @@ -5615,10 +5662,10 @@ namespace Slang // TODO(tfoley): why would a member expression need this? staticMemberExpr->scope = parser->currentScope; - - parser->FillPosition(staticMemberExpr); + staticMemberExpr->memberOperatorLoc = parser->tokenReader.peekLoc(); staticMemberExpr->baseExpression = expr; parser->ReadToken(TokenType::Scope); + parser->FillPosition(staticMemberExpr); staticMemberExpr->name = expectIdentifier(parser).name; if (peekTokenType(parser) == TokenType::OpLess) @@ -5635,10 +5682,10 @@ namespace Slang // TODO(tfoley): why would a member expression need this? memberExpr->scope = parser->currentScope; - - parser->FillPosition(memberExpr); + memberExpr->memberOperatorLoc = parser->tokenReader.peekLoc(); memberExpr->baseExpression = expr; parser->ReadToken(TokenType::Dot); + parser->FillPosition(memberExpr); memberExpr->name = expectIdentifier(parser).name; if (peekTokenType(parser) == TokenType::OpLess) diff --git a/source/slang/slang-syntax.cpp b/source/slang/slang-syntax.cpp index 24ccfa4a4..957d4e661 100644 --- a/source/slang/slang-syntax.cpp +++ b/source/slang/slang-syntax.cpp @@ -930,7 +930,10 @@ Index getFilterCountImpl(const ReflectClassInfo& clsInfo, MemberFilterStyle filt { return decl->nameAndLoc.name; } - + SourceLoc DeclRefBase::getNameLoc() const + { + return decl->nameAndLoc.loc; + } SourceLoc DeclRefBase::getLoc() const { return decl->loc; diff --git a/source/slang/slang-workspace-version.cpp b/source/slang/slang-workspace-version.cpp new file mode 100644 index 000000000..7fef7430e --- /dev/null +++ b/source/slang/slang-workspace-version.cpp @@ -0,0 +1,358 @@ +#include "slang-workspace-version.h" +#include "../core/slang-io.h" +#include "../core/slang-file-system.h" +#include "../compiler-core/slang-lexer.h" + +namespace Slang +{ +struct DirEnumerationContext +{ + List<String> workList; + OrderedHashSet<String> paths; + String currentPath; + String root; + void addSearchPath(String path) + { + while (path.getLength()) + { + String canonicalPath; + Path::getCanonical(path, canonicalPath); + if (!paths.Add(canonicalPath)) + break; + path = Path::getParentDirectory(path); + if (!path.startsWith(root)) + break; + } + } +}; +DocumentVersion* Workspace::openDoc(String path, String text) +{ + RefPtr<DocumentVersion> doc = new DocumentVersion(); + doc->setText(text.getUnownedSlice()); + doc->setURI(URI::fromLocalFilePath(path.getUnownedSlice())); + openedDocuments[path] = doc; + searchPaths.Add(Path::getParentDirectory(path)); + invalidate(); + return doc.Ptr(); +} + +void Workspace::init(List<URI> rootDirURI, slang::IGlobalSession* globalSession) +{ + for (auto uri : rootDirURI) + { + auto path = uri.getPath(); + rootDirectories.add(path); + DirEnumerationContext context; + context.workList.add(path); + context.root = path; + auto fileSystem = Slang::OSFileSystem::getExtSingleton(); + for (int i = 0; i < context.workList.getCount(); i++) + { + context.currentPath = context.workList[i]; + fileSystem->enumeratePathContents( + context.currentPath.getBuffer(), + [](SlangPathType pathType, const char* name, void* userData) + { + auto dirContext = (DirEnumerationContext*)userData; + auto nameSlice = UnownedStringSlice(name); + if (pathType == SLANG_PATH_TYPE_DIRECTORY) + { + dirContext->workList.add(Path::combine(dirContext->currentPath, name)); + } + else if (nameSlice.endsWithCaseInsensitive(".slang") || nameSlice.endsWithCaseInsensitive(".hlsl")) + { + dirContext->addSearchPath(dirContext->currentPath); + } + }, + &context); + } + searchPaths = _Move(context.paths); + } + slangGlobalSession = globalSession; +} + +void Workspace::invalidate() { currentVersion = nullptr; } + +int parseInt(UnownedStringSlice text, Index& pos) +{ + int result = 0; + while (text[pos] == ' ' && pos < text.getLength()) + { + pos++; + continue; + } + while (pos < text.getLength()) + { + if (text[pos] >= '0' && text[pos] <= '9') + { + result *= 10; + result += text[pos] - '0'; + pos++; + } + else + { + break; + } + } + return result; +} + +void parseDiagnostics(Dictionary<String, DocumentDiagnostics>& diagnostics, String compilerOutput) +{ + List<UnownedStringSlice> lines; + StringUtil::split(compilerOutput.getUnownedSlice(), '\n', lines); + for (Index lineIndex = 0; lineIndex < lines.getCount(); lineIndex++) + { + auto line = lines[lineIndex]; + Index colonIndex = line.indexOf(UnownedStringSlice("):")); + if (colonIndex == -1) + continue; + Index lparentIndex = line.indexOf('('); + if (lparentIndex > colonIndex) + continue; + String fileName = line.subString(0, lparentIndex); + Path::getCanonical(fileName, fileName); + auto& diagnosticList = diagnostics.GetOrAddValue(fileName, DocumentDiagnostics()); + + LanguageServerProtocol::Diagnostic diagnostic; + Index pos = lparentIndex + 1; + int lineLoc = parseInt(line, pos); + if (lineLoc == 0) + lineLoc = 1; + diagnostic.range.end.line = diagnostic.range.start.line = lineLoc - 1; + pos++; + int colLoc = parseInt(line, pos); + if (colLoc == 0) + colLoc = 1; + diagnostic.range.end.character = diagnostic.range.start.character = colLoc - 1; + if (pos >= line.getLength()) + continue; + line = line.subString(colonIndex + 3, line.getLength()); + colonIndex = line.indexOf(':'); + if (colonIndex == -1) + continue; + if (line.startsWith("error")) + { + diagnostic.severity = LanguageServerProtocol::kDiagnosticsSeverityError; + } + else if (line.startsWith("warning")) + { + diagnostic.severity = LanguageServerProtocol::kDiagnosticsSeverityWarning; + } + else if (line.startsWith("note")) + { + diagnostic.severity = LanguageServerProtocol::kDiagnosticsSeverityInformation; + } + else + { + continue; + } + pos = line.indexOf(' '); + diagnostic.code = parseInt(line, pos); + diagnostic.message = line.subString(colonIndex + 2, line.getLength()); + if (lineIndex + 1 < lines.getCount() && lines[lineIndex].startsWith("^+")) + { + lineIndex++; + pos = 2; + auto tokenLength = parseInt(lines[lineIndex], pos); + diagnostic.range.end.character += tokenLength; + } + diagnosticList.messages.Add(diagnostic); + } +} + +RefPtr<WorkspaceVersion> Workspace::createWorkspaceVersion() +{ + RefPtr<WorkspaceVersion> version = new WorkspaceVersion(); + version->workspace = this; + 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; + + List<const char*> searchPathsRaw; + + for (auto path : searchPaths) + searchPathsRaw.add(path.getBuffer()); + desc.searchPaths = searchPathsRaw.getBuffer(); + desc.searchPathCount = searchPathsRaw.getCount(); + + ComPtr<slang::ISession> session; + slangGlobalSession->createSession(desc, session.writeRef()); + version->linkage = static_cast<Linkage*>(session.get()); + return version; +} + +SlangResult Workspace::loadFile(const char* path, ISlangBlob** outBlob) +{ + String canonnicalPath; + SLANG_RETURN_ON_FAIL(Path::getCanonical(path, canonnicalPath)); + RefPtr<DocumentVersion> doc; + if (openedDocuments.TryGetValue(canonnicalPath, doc)) + { + RefPtr<StringBlob> stringBlob = new StringBlob(doc->getText()); + *outBlob = stringBlob.detach(); + return SLANG_OK; + } + return Slang::OSFileSystem::getExtSingleton()->loadFile(path, outBlob); +} +WorkspaceVersion* Workspace::getCurrentVersion() +{ + if (!currentVersion) + currentVersion = createWorkspaceVersion(); + return currentVersion.Ptr(); +} + +void* Workspace::getInterface(const Guid& uuid) +{ + if (uuid == ISlangUnknown::getTypeGuid() || uuid == ISlangFileSystem::getTypeGuid()) + { + return static_cast<ISlangFileSystem*>(this); + } + return nullptr; +} + +Int convertHexDigit(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return 0; +} + +String URI::getPath() const +{ + Index startIndex = uri.indexOf("://"); + if (startIndex == -1) return String(); + startIndex += 3; + Index endIndex = uri.indexOf('?'); + if (endIndex == -1) + endIndex = uri.getLength(); + StringBuilder sb; +#if SLANG_WINDOWS_FAMILY + if (uri[startIndex] == '/') + startIndex++; +#endif + for (Index i = startIndex; i < endIndex;) + { + auto ch = uri[i]; + if (ch == '%') + { + Int charVal = convertHexDigit(uri[i + 1]) * 16 + convertHexDigit(uri[i + 2]); + sb.appendChar((char)charVal); + i += 3; + } + else + { + sb.appendChar(uri[i]); + i++; + } + } + return sb.ProduceString(); +} + +bool URI::isSafeURIChar(char ch) +{ + return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || + ch == '-' || ch == '_' || ch == '/' || ch == '.'; +} + +URI URI::fromLocalFilePath(UnownedStringSlice path) +{ + URI uri; + StringBuilder sb; + sb << "file://"; + +#if SLANG_WINDOWS_FAMILY + sb << "/"; +#endif + + for (auto ch : path) + { + if (isSafeURIChar(ch)) + { + sb.appendChar(ch); + } + else if (ch == '\\') + { + sb.appendChar('/'); + } + else + { + char buffer[32]; + int length = IntToAscii(buffer, (int)ch, 16); + ReverseInternalAscii(buffer, length); + sb << "%" << buffer; + } + } + return URI::fromString(sb.getUnownedSlice()); +} + +URI URI::fromString(UnownedStringSlice uriString) +{ + URI uri; + uri.uri = uriString; + return uri; +} +void DocumentVersion::setText(const String& newText) +{ + text = newText; + lineBreaks.clear(); + for (Index i = 0; i < newText.getLength(); i++) + { + if (newText[i] == '\n') + lineBreaks.add(i); + } + lineBreaks.add(newText.getLength()); +} +ASTMarkup* WorkspaceVersion::getOrCreateMarkupAST(ModuleDecl* module) +{ + RefPtr<ASTMarkup> astMarkup; + if (markupASTs.TryGetValue(module, astMarkup)) + return astMarkup.Ptr(); + DiagnosticSink sink; + astMarkup = new ASTMarkup(); + ASTMarkupUtil::extract(module, linkage->getSourceManager(), &sink, astMarkup.Ptr()); + markupASTs[module] = astMarkup; + return astMarkup.Ptr(); +} + +Module* WorkspaceVersion::getOrLoadModule(String path) +{ + Module* module; + if (modules.TryGetValue(path, module)) + { + return module; + } + auto doc = workspace->openedDocuments.TryGetValue(path); + if (!doc) + return nullptr; + ComPtr<ISlangBlob> diagnosticBlob; + RefPtr<StringBlob> sourceBlob = new StringBlob((*doc)->getText()); + auto parsedModule = linkage->loadModuleFromSource( + Path::getFileNameWithoutExt(path).getBuffer(), + path.getBuffer(), + sourceBlob.Ptr(), + diagnosticBlob.writeRef()); + if (parsedModule) + { + modules[path] = static_cast<Module*>(parsedModule); + } + if (diagnosticBlob) + { + auto diagnosticString = String((const char*)diagnosticBlob->getBufferPointer()); + parseDiagnostics(diagnostics, diagnosticString); + auto docDiagnostic = diagnostics.TryGetValue(path); + if (docDiagnostic) + docDiagnostic->originalOutput = diagnosticString; + } + return static_cast<Module*>(parsedModule); +} + +} // namespace Slang diff --git a/source/slang/slang-workspace-version.h b/source/slang/slang-workspace-version.h new file mode 100644 index 000000000..3dcd3d9ce --- /dev/null +++ b/source/slang/slang-workspace-version.h @@ -0,0 +1,135 @@ +#pragma once + +#include "../../slang-com-helper.h" +#include "../../slang-com-ptr.h" +#include "../../slang.h" +#include "../core/slang-basic.h" +#include "../core/slang-com-object.h" +#include "slang-language-server-protocol.h" +#include "slang-compiler.h" +#include "slang-doc-ast.h" + +namespace Slang +{ + struct URI + { + String uri; + bool operator==(const URI& other) const + { + return uri == other.uri; + } + bool operator!=(const URI& other) const { return uri != other.uri; } + + HashCode getHashCode() const { return uri.getHashCode(); } + + bool isLocalFile() { return uri.startsWith("file://"); }; + String getPath() const; + StringSlice getProtocol() const { return uri.subString(0, uri.indexOf("://")); } + + static URI fromLocalFilePath(UnownedStringSlice path); + static URI fromString(UnownedStringSlice uriString); + static bool isSafeURIChar(char ch); + }; + + class Workspace; + + class DocumentVersion : public RefObject + { + private: + URI uri; + String text; + List<Int> lineBreaks; + public: + void setURI(URI newURI) + { + uri = newURI; + } + URI getURI() { return uri; } + const String& getText() { return text; } + void setText(const String& newText); + Index getOffset(Index lineIndex, Index colIndex) + { + if(lineIndex < 0) return -1; + if (lineIndex - 1 >= lineBreaks.getCount()) + return -1; + if (lineBreaks.getCount() == 0) + return -1; + + Index lineStart = lineIndex >= 2 ? lineBreaks[lineIndex - 2] : 0; + return lineStart + colIndex - 1; + } + void offsetToLineCol(Index offset, Index& line, Index& col) + { + auto firstGreater = std::upper_bound(lineBreaks.begin(), lineBreaks.end(), offset); + line = Index(firstGreater - lineBreaks.begin() + 1); + if (firstGreater == lineBreaks.begin()) + { + col = offset + 1; + } + else + { + col = Index(offset - *(firstGreater - 1)); + } + } + UnownedStringSlice getLine(Index lineIndex) + { + if (lineIndex < 0) + return UnownedStringSlice(); + if (lineIndex - 1 >= lineBreaks.getCount()) + return UnownedStringSlice(); + if (lineBreaks.getCount() == 0) + return UnownedStringSlice(); + + Int lineStart = lineIndex >= 2 ? lineBreaks[lineIndex - 2] : 0; + Int lineEnd = lineBreaks[lineIndex - 1]; + return text.getUnownedSlice().subString(lineStart, lineEnd); + } + }; + + struct DocumentDiagnostics + { + OrderedHashSet<LanguageServerProtocol::Diagnostic> messages; + String originalOutput; + }; + + class WorkspaceVersion : public RefObject + { + private: + Dictionary<String, Module*> modules; + Dictionary<ModuleDecl*, RefPtr<ASTMarkup>> markupASTs; + public: + Workspace* workspace; + RefPtr<Linkage> linkage; + Dictionary<String, DocumentDiagnostics> diagnostics; + List<Decl*> currentCompletionItems; + ASTMarkup* getOrCreateMarkupAST(ModuleDecl* module); + + Module* getOrLoadModule(String path); + }; + + class Workspace + : public ISlangFileSystem + , public ComObject + { + private: + RefPtr<WorkspaceVersion> currentVersion; + RefPtr<WorkspaceVersion> createWorkspaceVersion(); + public: + List<String> rootDirectories; + OrderedHashSet<String> searchPaths; + + slang::IGlobalSession* slangGlobalSession; + Dictionary<String, RefPtr<DocumentVersion>> openedDocuments; + DocumentVersion* openDoc(String path, String text); + void init(List<URI> rootDirURI, slang::IGlobalSession* globalSession); + void invalidate(); + WorkspaceVersion* getCurrentVersion(); + + public: + // Inherited via ISlangFileSystem + SLANG_COM_OBJECT_IUNKNOWN_ALL + void* getInterface(const Guid& uuid); + virtual SLANG_NO_THROW SlangResult SLANG_MCALL + loadFile(const char* path, ISlangBlob** outBlob) override; + }; +} // namespace LanguageServerProtocol diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 9379f3b03..7602096d4 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -509,6 +509,8 @@ SLANG_NO_THROW SlangResult SLANG_MCALL Session::createSession( targetDescPtr += targetDesc.structureSize; } + linkage->setFlags(desc.flags); + if(desc.flags & slang::kSessionFlag_FalcorCustomSharedKeywordSemantics) { linkage->m_useFalcorCustomSharedKeywordSemantics = true; @@ -529,6 +531,10 @@ SLANG_NO_THROW SlangResult SLANG_MCALL Session::createSession( linkage->addPreprocessorDefine(macro.name, macro.value); } + if (desc.fileSystem) + { + linkage->setFileSystem(desc.fileSystem); + } *outSession = asExternal(linkage.detach()); return SLANG_OK; } @@ -926,6 +932,12 @@ SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModule( slang::IBlob** outDiagnostics) { DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer); + + if (m_flag & slang::kSessionFlag_LanguageServer) + { + sink.setFlags(DiagnosticSink::Flag::HumaneLoc | DiagnosticSink::Flag::LanguageServer); + } + try { auto name = getNamePool()->getName(moduleName); @@ -945,9 +957,16 @@ SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModule( SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModuleFromSource( const char* moduleName, + const char* path, slang::IBlob* source, slang::IBlob** outDiagnostics) { + DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer); + if (m_flag & slang::kSessionFlag_LanguageServer) + { + sink.setFlags(DiagnosticSink::Flag::HumaneLoc | DiagnosticSink::Flag::LanguageServer); + } + try { auto name = getNamePool()->getName(moduleName); @@ -956,10 +975,9 @@ SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModuleFromSource( { return loadedModule; } - DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer); auto module = loadModule( name, - PathInfo::makeFromString(moduleName), + PathInfo::makeFromString(path), source, SourceLoc(), &sink, @@ -970,6 +988,7 @@ SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModuleFromSource( } catch (const AbortCompilationException&) { + sink.getBlobIfNeeded(outDiagnostics); return nullptr; } } @@ -2567,17 +2586,29 @@ void Linkage::loadParsedModule( int errorCountBefore = sink->getErrorCount(); compileRequest->checkAllTranslationUnits(); int errorCountAfter = sink->getErrorCount(); - - if (errorCountAfter != errorCountBefore) + if (isInLanguageServer()) { - // There must have been an error in the loaded module. + // Don't generate IR as language server. + // This means that we currently cannot report errors that are detected during IR passes. + // Ideally we want to run those passes, but that is too risky for what it is worth right + // now. } else { - // If we didn't run into any errors, then try to generate - // IR code for the imported module. - SLANG_ASSERT(errorCountAfter == 0); - loadedModule->setIRModule(generateIRForTranslationUnit(getASTBuilder(), translationUnit)); + if (errorCountAfter != errorCountBefore) + { + // There must have been an error in the loaded module. + } + else + { + // If we didn't run into any errors, then try to generate + // IR code for the imported module. + if (errorCountAfter == 0) + { + loadedModule->setIRModule( + generateIRForTranslationUnit(getASTBuilder(), translationUnit)); + } + } } loadedModulesList.add(loadedModule); } @@ -2602,7 +2633,10 @@ void Linkage::_diagnoseErrorInImportedModule( { sink->diagnose(info->importLoc, Diagnostics::errorInImportedModule, info->name); } - sink->diagnose(SourceLoc(), Diagnostics::complationCeased); + if ((m_flag & slang::kSessionFlag_LanguageServer) == 0) + { + sink->diagnose(SourceLoc(), Diagnostics::complationCeased); + } } RefPtr<Module> Linkage::loadModule( @@ -2641,11 +2675,11 @@ RefPtr<Module> Linkage::loadModule( frontEndReq->parseTranslationUnit(translationUnit); int errorCountAfter = sink->getErrorCount(); - if( errorCountAfter != errorCountBefore ) + if (errorCountAfter != errorCountBefore && !isInLanguageServer()) { _diagnoseErrorInImportedModule(sink); } - if (errorCountAfter) + if (errorCountAfter && !isInLanguageServer()) { // Something went wrong during the parsing, so we should bail out. return nullptr; @@ -2659,7 +2693,7 @@ RefPtr<Module> Linkage::loadModule( errorCountAfter = sink->getErrorCount(); - if (errorCountAfter != errorCountBefore) + if (errorCountAfter != errorCountBefore && !isInLanguageServer()) { _diagnoseErrorInImportedModule(sink); // Something went wrong during the parsing, so we should bail out. |
