summaryrefslogtreecommitdiffstats
path: root/source/slang
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2022-06-07 14:10:49 -0700
committerGitHub <noreply@github.com>2022-06-07 14:10:49 -0700
commit0c64995ea28febcc7d38e1519da8d93391ce2e7d (patch)
tree8696ab86b29caf80c3ebbd205c700e24b8c20bf3 /source/slang
parent8c4a15c522861d2f30eacc9cd2b03ad793018639 (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')
-rw-r--r--source/slang/slang-ast-decl.h1
-rw-r--r--source/slang/slang-ast-expr.h23
-rw-r--r--source/slang/slang-ast-print.cpp128
-rw-r--r--source/slang/slang-ast-print.h4
-rw-r--r--source/slang/slang-ast-support-types.h1
-rw-r--r--source/slang/slang-check-conversion.cpp6
-rw-r--r--source/slang/slang-check-decl.cpp4
-rw-r--r--source/slang/slang-check-expr.cpp73
-rw-r--r--source/slang/slang-check-impl.h10
-rw-r--r--source/slang/slang-check-overload.cpp12
-rw-r--r--source/slang/slang-check-type.cpp6
-rwxr-xr-xsource/slang/slang-compiler.h7
-rw-r--r--source/slang/slang-diagnostic-defs.h2
-rw-r--r--source/slang/slang-doc-ast.cpp4
-rw-r--r--source/slang/slang-language-server-ast-lookup.cpp566
-rw-r--r--source/slang/slang-language-server-ast-lookup.h24
-rw-r--r--source/slang/slang-language-server-collect-member.cpp156
-rw-r--r--source/slang/slang-language-server-collect-member.h25
-rw-r--r--source/slang/slang-language-server-protocol.cpp481
-rw-r--r--source/slang/slang-language-server-protocol.h636
-rw-r--r--source/slang/slang-language-server-semantic-tokens.cpp611
-rw-r--r--source/slang/slang-language-server-semantic-tokens.h36
-rw-r--r--source/slang/slang-language-server.cpp1044
-rw-r--r--source/slang/slang-language-server.h8
-rw-r--r--source/slang/slang-lower-to-ir.cpp6
-rw-r--r--source/slang/slang-parser.cpp101
-rw-r--r--source/slang/slang-syntax.cpp5
-rw-r--r--source/slang/slang-workspace-version.cpp358
-rw-r--r--source/slang/slang-workspace-version.h135
-rw-r--r--source/slang/slang.cpp60
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, &paramRanges);
+ 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.