From 0c64995ea28febcc7d38e1519da8d93391ce2e7d Mon Sep 17 00:00:00 2001 From: Yong He Date: Tue, 7 Jun 2022 14:10:49 -0700 Subject: 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 --- .github/workflows/linux.yml | 9 + .github/workflows/release-linux.yml | 4 +- .github/workflows/release-windows.yml | 1 + .github/workflows/verify-solution-file.yml | 2 +- .github/workflows/windows.yml | 12 +- .../gfx-unit-test-tool.vcxproj.filters | 12 +- build/visual-studio/slang/slang.vcxproj | 12 + build/visual-studio/slang/slang.vcxproj.filters | 36 + build/visual-studio/slangd/slangd.vcxproj | 7 +- build/visual-studio/slangd/slangd.vcxproj.filters | 16 +- slang.h | 7 + source/compiler-core/slang-diagnostic-sink.cpp | 52 + source/compiler-core/slang-diagnostic-sink.h | 3 +- source/compiler-core/slang-doc-extractor.cpp | 3 +- source/compiler-core/slang-json-native.cpp | 4 +- source/compiler-core/slang-json-rpc-connection.cpp | 2 +- source/compiler-core/slang-json-rpc-connection.h | 2 + source/compiler-core/slang-json-rpc.cpp | 1 + source/compiler-core/slang-source-loc.cpp | 2 +- source/core/slang-rtti-info.h | 3 + source/core/slang-string.cpp | 17 +- source/core/slang-string.h | 3 + source/slang/slang-ast-decl.h | 1 + source/slang/slang-ast-expr.h | 23 +- source/slang/slang-ast-print.cpp | 128 ++- source/slang/slang-ast-print.h | 4 +- source/slang/slang-ast-support-types.h | 1 + source/slang/slang-check-conversion.cpp | 6 + source/slang/slang-check-decl.cpp | 4 +- source/slang/slang-check-expr.cpp | 73 +- source/slang/slang-check-impl.h | 10 +- source/slang/slang-check-overload.cpp | 12 +- source/slang/slang-check-type.cpp | 6 +- source/slang/slang-compiler.h | 7 + source/slang/slang-diagnostic-defs.h | 2 +- source/slang/slang-doc-ast.cpp | 4 - source/slang/slang-language-server-ast-lookup.cpp | 566 +++++++++++ source/slang/slang-language-server-ast-lookup.h | 24 + .../slang/slang-language-server-collect-member.cpp | 156 +++ .../slang/slang-language-server-collect-member.h | 25 + source/slang/slang-language-server-protocol.cpp | 481 +++++++++ source/slang/slang-language-server-protocol.h | 636 ++++++++++++ .../slang-language-server-semantic-tokens.cpp | 611 ++++++++++++ .../slang/slang-language-server-semantic-tokens.h | 36 + source/slang/slang-language-server.cpp | 1044 ++++++++++++++++++++ source/slang/slang-language-server.h | 8 + source/slang/slang-lower-to-ir.cpp | 6 + source/slang/slang-parser.cpp | 101 +- source/slang/slang-syntax.cpp | 5 +- source/slang/slang-workspace-version.cpp | 358 +++++++ source/slang/slang-workspace-version.h | 135 +++ source/slang/slang.cpp | 60 +- .../generic-type-arg-overloaded.slang.expected | 8 +- tests/bugs/parser-infinite-loop.slang | 11 + tests/bugs/parser-infinite-loop.slang.expected | 11 + tests/diagnostics/bad-operator-call.slang.expected | 76 +- .../anyvalue-size-validation.slang.expected | 2 +- tests/diagnostics/matrix-swizzle.slang.expected | 26 +- tests/diagnostics/mismatching-types.slang.expected | 6 +- .../static-ref-to-nonstatic-member.slang.expected | 2 +- .../variable-redeclaration.slang.expected | 4 +- tools/slangd/language-server-protocol.cpp | 191 ---- tools/slangd/language-server-protocol.h | 148 --- tools/slangd/language-server.cpp | 184 ---- tools/slangd/language-server.h | 35 - tools/slangd/main.cpp | 25 + 66 files changed, 4740 insertions(+), 732 deletions(-) create mode 100644 source/slang/slang-language-server-ast-lookup.cpp create mode 100644 source/slang/slang-language-server-ast-lookup.h create mode 100644 source/slang/slang-language-server-collect-member.cpp create mode 100644 source/slang/slang-language-server-collect-member.h create mode 100644 source/slang/slang-language-server-protocol.cpp create mode 100644 source/slang/slang-language-server-protocol.h create mode 100644 source/slang/slang-language-server-semantic-tokens.cpp create mode 100644 source/slang/slang-language-server-semantic-tokens.h create mode 100644 source/slang/slang-language-server.cpp create mode 100644 source/slang/slang-language-server.h create mode 100644 source/slang/slang-workspace-version.cpp create mode 100644 source/slang/slang-workspace-version.h create mode 100644 tests/bugs/parser-infinite-loop.slang create mode 100644 tests/bugs/parser-infinite-loop.slang.expected delete mode 100644 tools/slangd/language-server-protocol.cpp delete mode 100644 tools/slangd/language-server-protocol.h delete mode 100644 tools/slangd/language-server.cpp delete mode 100644 tools/slangd/language-server.h create mode 100644 tools/slangd/main.cpp diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d9d0a7b66..e25d03908 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -26,6 +26,15 @@ jobs: CONFIGURATION=${{matrix.configuration}} ARCH=${{matrix.platform}} source ./github_build.sh + - uses: actions/upload-artifact@v3 + with: + name: slang-build-${{matrix.configuration}}-${{matrix.platform}}-${{matrix.compiler}} + path: | + bin/**/*.dll + bin/**/*.exe + bin/**/*.so + bin/**/slangc + bin/**/slangd - name: test run: CONFIGURATION=${{matrix.configuration}} diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index db8879757..3f5feeaef 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -40,9 +40,9 @@ jobs: export SLANG_BINARY_ARCHIVE=slang-${SLANG_TAG}-${SLANG_OS_NAME}-${SLANG_ARCH_NAME}.zip export SLANG_BINARY_ARCHIVE_TAR=slang-${SLANG_TAG}-${SLANG_OS_NAME}-${SLANG_ARCH_NAME}.tar.gz echo "creating zip" - zip -r ${SLANG_BINARY_ARCHIVE} bin/*/*/slangc bin/*/*/libslang.so bin/*/*/libslang-glslang.so bin/*/*/libgfx.so bin/*/*/libslang-llvm.so docs/*.md README.md LICENSE slang.h slang-com-helper.h slang-com-ptr.h slang-tag-version.h slang-gfx.h prelude/*.h + zip -r ${SLANG_BINARY_ARCHIVE} bin/*/*/slangc bin/*/*/slangd bin/*/*/libslang.so bin/*/*/libslang-glslang.so bin/*/*/libgfx.so bin/*/*/libslang-llvm.so docs/*.md README.md LICENSE slang.h slang-com-helper.h slang-com-ptr.h slang-tag-version.h slang-gfx.h prelude/*.h echo "creating tar" - tar -czf ${SLANG_BINARY_ARCHIVE_TAR} bin/*/*/slangc bin/*/*/libslang.so bin/*/*/libslang-glslang.so bin/*/*/libgfx.so bin/*/*/libslang-llvm.so docs/*.md README.md LICENSE slang.h slang-com-helper.h slang-com-ptr.h slang-tag-version.h slang-gfx.h prelude/*.h + tar -czf ${SLANG_BINARY_ARCHIVE_TAR} bin/*/*/slangc bin/*/*/slangd bin/*/*/libslang.so bin/*/*/libslang-glslang.so bin/*/*/libgfx.so bin/*/*/libslang-llvm.so docs/*.md README.md LICENSE slang.h slang-com-helper.h slang-com-ptr.h slang-tag-version.h slang-gfx.h prelude/*.h echo "::set-output name=SLANG_BINARY_ARCHIVE::${SLANG_BINARY_ARCHIVE}" echo "::set-output name=SLANG_BINARY_ARCHIVE_TAR::${SLANG_BINARY_ARCHIVE_TAR}" - name: UploadBinary diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 9237441ff..fdad2d73e 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -72,6 +72,7 @@ jobs: 7z a "$binArchive" bin\*\*\gfx.dll 7z a "$binArchive" bin\*\*\gfx.lib 7z a "$binArchive" bin\*\*\slangc.exe + 7z a "$binArchive" bin\*\*\slangd.exe 7z a "$binArchive" docs\*.md $srcArchive = "slang-$slangVersion-source.zip" diff --git a/.github/workflows/verify-solution-file.yml b/.github/workflows/verify-solution-file.yml index 7610e05fd..66c2d00a7 100644 --- a/.github/workflows/verify-solution-file.yml +++ b/.github/workflows/verify-solution-file.yml @@ -16,7 +16,7 @@ jobs: fetch-depth: '1' - name: verify run: | - & .\premake.bat vs2019 + & .\premake.bat vs2019 --deps=true $diff = & git diff if ($diff.length -ne 0) { diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 1637640e8..2d6b7c413 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -29,6 +29,15 @@ jobs: - name: build run: MSBuild.exe slang.sln -v:m -m -property:Configuration=${{matrix.configuration}} -property:Platform=${{matrix.platform}} -property:WindowsTargetPlatformVersion=10.0.19041.0 + - uses: actions/upload-artifact@v3 + with: + name: slang-build-${{matrix.configuration}}-${{matrix.platform}}-${{matrix.compiler}} + path: | + bin/**/*.dll + bin/**/*.exe + bin/**/*.so + bin/**/slangc + bin/**/slangd - name: test run: | if ("${{matrix.configuration}}" -eq "Debug") { @@ -50,4 +59,5 @@ jobs: $env:Path += ";$slangTestBinDir"; Invoke-WebRequest -uri "https://github.com/shader-slang/swiftshader/releases/download/v1.0/vk_swiftshader_windows_$testPlatform.zip" -Method "GET" -Outfile "swiftshader.zip"; Expand-Archive "swiftshader.zip" -DestinationPath $slangTestBinDir; - & "$slangTestBinDir\slang-test.exe" -api all-dx12 -appveyor -bindir "$slangTestBinDir\" -platform $testPlatform -configuration ${{matrix.configuration}} -category $testCategory 2>&1; \ No newline at end of file + & "$slangTestBinDir\slang-test.exe" -api all-dx12 -appveyor -bindir "$slangTestBinDir\" -platform $testPlatform -configuration ${{matrix.configuration}} -category $testCategory 2>&1; + \ No newline at end of file diff --git a/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters b/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters index 87f24f154..dffe6e25e 100644 --- a/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters +++ b/build/visual-studio/gfx-unit-test-tool/gfx-unit-test-tool.vcxproj.filters @@ -71,6 +71,9 @@ Source Files + + Source Files + Source Files @@ -98,9 +101,6 @@ Source Files - - Source Files - @@ -124,6 +124,9 @@ Source Files + + Source Files + Source Files @@ -139,8 +142,5 @@ Source Files - - Source Files - \ No newline at end of file diff --git a/build/visual-studio/slang/slang.vcxproj b/build/visual-studio/slang/slang.vcxproj index 89e69b55c..dffc48700 100644 --- a/build/visual-studio/slang/slang.vcxproj +++ b/build/visual-studio/slang/slang.vcxproj @@ -406,6 +406,11 @@ IF EXIST ..\..\..\external\slang-binaries\bin\windows-aarch64\slang-glslang.dll\ + + + + + @@ -438,6 +443,7 @@ IF EXIST ..\..\..\external\slang-binaries\bin\windows-aarch64\slang-glslang.dll\ + @@ -550,6 +556,11 @@ IF EXIST ..\..\..\external\slang-binaries\bin\windows-aarch64\slang-glslang.dll\ + + + + + @@ -579,6 +590,7 @@ IF EXIST ..\..\..\external\slang-binaries\bin\windows-aarch64\slang-glslang.dll\ + diff --git a/build/visual-studio/slang/slang.vcxproj.filters b/build/visual-studio/slang/slang.vcxproj.filters index a414d7e6a..8721de464 100644 --- a/build/visual-studio/slang/slang.vcxproj.filters +++ b/build/visual-studio/slang/slang.vcxproj.filters @@ -315,6 +315,21 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + Header Files @@ -411,6 +426,9 @@ Header Files + + Header Files + @@ -743,6 +761,21 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + Source Files @@ -830,6 +863,9 @@ Source Files + + Source Files + Source Files diff --git a/build/visual-studio/slangd/slangd.vcxproj b/build/visual-studio/slangd/slangd.vcxproj index d52a5b466..920bb2be5 100644 --- a/build/visual-studio/slangd/slangd.vcxproj +++ b/build/visual-studio/slangd/slangd.vcxproj @@ -262,12 +262,7 @@ - - - - - - + diff --git a/build/visual-studio/slangd/slangd.vcxproj.filters b/build/visual-studio/slangd/slangd.vcxproj.filters index b21b4cc7f..5028415fd 100644 --- a/build/visual-studio/slangd/slangd.vcxproj.filters +++ b/build/visual-studio/slangd/slangd.vcxproj.filters @@ -1,26 +1,12 @@ - - {21EB8090-0D4E-1035-B6D3-48EBA215DCB7} - {E9C7FDCE-D52A-8D73-7EB0-C5296AF258F6} - - Header Files - - - Header Files - - - - - Source Files - - + Source Files diff --git a/slang.h b/slang.h index 1c6eb5fed..fbb368a24 100644 --- a/slang.h +++ b/slang.h @@ -4002,6 +4002,10 @@ namespace slang You have been warned. */ kSessionFlag_FalcorCustomSharedKeywordSemantics = 1 << 0, + + /** Indicates that this is a session created by language server. + */ + kSessionFlag_LanguageServer = 1 << 1, }; struct PreprocessorMacroDesc @@ -4036,6 +4040,8 @@ namespace slang PreprocessorMacroDesc const* preprocessorMacros = nullptr; SlangInt preprocessorMacroCount = 0; + + ISlangFileSystem* fileSystem = nullptr; }; enum class ContainerType @@ -4086,6 +4092,7 @@ namespace slang */ virtual SLANG_NO_THROW IModule* SLANG_MCALL loadModuleFromSource( const char* moduleName, + const char* path, slang::IBlob* source, slang::IBlob** outDiagnostics = nullptr) = 0; diff --git a/source/compiler-core/slang-diagnostic-sink.cpp b/source/compiler-core/slang-diagnostic-sink.cpp index 314ec1c2c..0110b16d7 100644 --- a/source/compiler-core/slang-diagnostic-sink.cpp +++ b/source/compiler-core/slang-diagnostic-sink.cpp @@ -138,6 +138,10 @@ static void formatDiagnostic(const HumaneSourceLoc& humaneLoc, Diagnostic const& outBuilder << humaneLoc.pathInfo.foundPath; outBuilder << "("; outBuilder << Int32(humaneLoc.line); + if (flags & DiagnosticSink::Flag::LanguageServer) + { + outBuilder << ", " << humaneLoc.column; + } outBuilder << "): "; } @@ -348,6 +352,47 @@ static void _sourceLocationNoteDiagnostic(DiagnosticSink* sink, SourceView* sour sb << caretLine << "\n"; } +// Output the length of the token at `sourceLoc`. This is used by language server. +static void _tokenLengthNoteDiagnostic( + DiagnosticSink* sink, SourceView* sourceView, SourceLoc sourceLoc, StringBuilder& sb) +{ + SourceFile* sourceFile = sourceView->getSourceFile(); + if (!sourceFile) + { + return; + } + + UnownedStringSlice content = sourceFile->getContent(); + + // Make sure the offset is within content. + // This is important because it's possible to have a 'SourceFile' that doesn't contain any + // content (for example when reconstructed via serialization with just line offsets, the actual + // source text 'content' isn't available). + const int offset = sourceView->getRange().getOffset(sourceLoc); + if (offset < 0 || offset >= content.getLength()) + { + return; + } + + // Work out the position of the SourceLoc in the source + const char* const pos = content.begin() + offset; + + UnownedStringSlice line = _extractLineContainingPosition(content, pos); + + // Trim any trailing white space + line = UnownedStringSlice(line.begin(), line.trim().end()); + + auto lexer = sink->getSourceLocationLexer(); + if (lexer) + { + UnownedStringSlice token = lexer(UnownedStringSlice(pos, line.end())); + + if (token.getLength() > 1) + { + sb << "^+" << token.getLength() << "\n"; + } + } +} static void formatDiagnostic( DiagnosticSink* sink, @@ -365,6 +410,7 @@ static void formatDiagnostic( { humaneLoc = sourceView->getHumaneLoc(sourceLoc); } + formatDiagnostic(humaneLoc, diagnostic, sink->getFlags(), sb); { @@ -404,6 +450,12 @@ static void formatDiagnostic( } } + // If we are a language server, output additional token length info. + if (sourceView && sink->isFlagSet(DiagnosticSink::Flag::LanguageServer)) + { + _tokenLengthNoteDiagnostic(sink, sourceView, sourceLoc, sb); + } + // We don't don't output source line information if this is a 'note' as a note is extra information for one // of the other main severity types, and so the information should already be output on the initial line if (sourceView && sink->isFlagSet(DiagnosticSink::Flag::SourceLocationLine) && diagnostic.severity != Severity::Note) diff --git a/source/compiler-core/slang-diagnostic-sink.h b/source/compiler-core/slang-diagnostic-sink.h index b78023bc8..c4d2e07d3 100644 --- a/source/compiler-core/slang-diagnostic-sink.h +++ b/source/compiler-core/slang-diagnostic-sink.h @@ -150,7 +150,8 @@ public: VerbosePath = 0x1, ///< Will display a more verbose path (if available) - such as a canonical or absolute path SourceLocationLine = 0x2, ///< If set will display the location line if source is available HumaneLoc = 0x4, ///< If set will display humane locs (filename/line number) information - TreatWarningsAsErrors = 0x8 ///< If set will turn all Warning type messages (after overrides) into Error type messages + TreatWarningsAsErrors = 0x8, ///< If set will turn all Warning type messages (after overrides) into Error type messages + LanguageServer = 0x10, ///< If set will format message in a way that is suitable for language server }; }; diff --git a/source/compiler-core/slang-doc-extractor.cpp b/source/compiler-core/slang-doc-extractor.cpp index c2200cf37..363108548 100644 --- a/source/compiler-core/slang-doc-extractor.cpp +++ b/source/compiler-core/slang-doc-extractor.cpp @@ -731,7 +731,8 @@ SlangResult DocMarkupExtractor::extract(const SearchItemInput* inputs, Index inp { // Find the new view sourceView = sourceManager->findSourceView(loc); - SLANG_ASSERT(sourceView); + if (!sourceView) + return SLANG_FAIL; // We want only one view per SourceFile SourceFile* sourceFile = sourceView->getSourceFile(); diff --git a/source/compiler-core/slang-json-native.cpp b/source/compiler-core/slang-json-native.cpp index 6e54457d0..d268fffc2 100644 --- a/source/compiler-core/slang-json-native.cpp +++ b/source/compiler-core/slang-json-native.cpp @@ -136,7 +136,7 @@ SlangResult JSONToNativeConverter::convert(const JSONValue& in, const RttiInfo* Index fieldCount = 0; SLANG_RETURN_ON_FAIL(_structToNative(pairs, structRttiInfo, out, fieldCount)); - if (fieldCount != pairs.getCount()) + if (fieldCount != pairs.getCount() && !structRttiInfo->m_ignoreUnknownFieldsInJson) { // We want to find the fields not found in the type @@ -176,6 +176,8 @@ SlangResult JSONToNativeConverter::convert(const JSONValue& in, const RttiInfo* } case RttiInfo::Kind::List: { + if (in.getKind() == JSONValue::Kind::Null) + return SLANG_OK; if (in.getKind() != JSONValue::Kind::Array) { return SLANG_FAIL; diff --git a/source/compiler-core/slang-json-rpc-connection.cpp b/source/compiler-core/slang-json-rpc-connection.cpp index cad2f8e19..ec45f5b82 100644 --- a/source/compiler-core/slang-json-rpc-connection.cpp +++ b/source/compiler-core/slang-json-rpc-connection.cpp @@ -157,7 +157,7 @@ SlangResult JSONRPCConnection::toNativeOrSendError(const JSONValue& value, const } return SLANG_OK; -} +} SlangResult JSONRPCConnection::sendCall(const UnownedStringSlice& method, const JSONValue& id) { diff --git a/source/compiler-core/slang-json-rpc-connection.h b/source/compiler-core/slang-json-rpc-connection.h index 1436e9601..07b7cc347 100644 --- a/source/compiler-core/slang-json-rpc-connection.h +++ b/source/compiler-core/slang-json-rpc-connection.h @@ -50,6 +50,7 @@ public: /// Convert value to dst. Will write response on fails SlangResult toNativeOrSendError(const JSONValue& value, const RttiInfo* info, void* dst, const JSONValue& id); + template SlangResult toNativeOrSendError(const JSONValue& value, T* data, const JSONValue& id) { return toNativeOrSendError(value, GetRttiInfo::get(), data, id); } @@ -59,6 +60,7 @@ public: /// toNativeOrSendError does not assume the thing being converted is args, and so doesn't allow such a transformation. /// Will write error response on failure. SlangResult toNativeArgsOrSendError(const JSONValue& srcArgs, const RttiInfo* dstArgsRttiInfo, void* dstArgs, const JSONValue& id); + template SlangResult toNativeArgsOrSendError(const JSONValue& srcArgs, T* dstArgs, const JSONValue& id) { return toNativeArgsOrSendError(srcArgs, GetRttiInfo::get(), dstArgs, id); } diff --git a/source/compiler-core/slang-json-rpc.cpp b/source/compiler-core/slang-json-rpc.cpp index 660bbd4b7..f4101be8c 100644 --- a/source/compiler-core/slang-json-rpc.cpp +++ b/source/compiler-core/slang-json-rpc.cpp @@ -54,6 +54,7 @@ static const StructRttiInfo _makeJSONRPCCallResponseRtti() builder.addField("method", &obj.method); builder.addField("params", &obj.params, StructRttiInfo::Flag::Optional); builder.addField("id", &obj.id, StructRttiInfo::Flag::Optional); + builder.ignoreUnknownFields(); return builder.make(); } diff --git a/source/compiler-core/slang-source-loc.cpp b/source/compiler-core/slang-source-loc.cpp index 3033d9626..f245d1e0c 100644 --- a/source/compiler-core/slang-source-loc.cpp +++ b/source/compiler-core/slang-source-loc.cpp @@ -176,7 +176,7 @@ HumaneSourceLoc SourceView::getHumaneLoc(SourceLoc loc, SourceLocType type) // We need the line index from the original source file const int lineIndex = m_sourceFile->calcLineIndexFromOffset(offset); - + // TODO: we should really translate the byte index in the line // to deal with: // diff --git a/source/core/slang-rtti-info.h b/source/core/slang-rtti-info.h index 36651c32b..e9296c3ff 100644 --- a/source/core/slang-rtti-info.h +++ b/source/core/slang-rtti-info.h @@ -170,6 +170,7 @@ struct StructRttiInfo : public NamedRttiInfo Index m_fieldCount; ///< Amount of fields const Field* m_fields; ///< Fields + bool m_ignoreUnknownFieldsInJson = false; }; struct EnumRttiInfo : public NamedRttiInfo @@ -330,6 +331,8 @@ struct StructRttiBuilder m_fields.add(field); } + void ignoreUnknownFields() { m_rttiInfo.m_ignoreUnknownFieldsInJson = true; } + StructRttiInfo make(); void _init(const char* name, const StructRttiInfo* super, const Byte* base); diff --git a/source/core/slang-string.cpp b/source/core/slang-string.cpp index 5f2ba13ba..9b3575ad8 100644 --- a/source/core/slang-string.cpp +++ b/source/core/slang-string.cpp @@ -155,12 +155,27 @@ namespace Slang end() - otherSize, end()) == other; } + bool UnownedStringSlice::endsWithCaseInsensitive(UnownedStringSlice const& other) const + { + UInt thisSize = getLength(); + UInt otherSize = other.getLength(); + + if (otherSize > thisSize) + return false; + + return UnownedStringSlice(end() - otherSize, end()).caseInsensitiveEquals(other); + } + bool UnownedStringSlice::endsWith(char const* str) const { return endsWith(UnownedTerminatedStringSlice(str)); } - + bool UnownedStringSlice::endsWithCaseInsensitive(char const* str) const + { + return endsWithCaseInsensitive(UnownedTerminatedStringSlice(str)); + } + UnownedStringSlice UnownedStringSlice::trim() const { const char* start = m_begin; diff --git a/source/core/slang-string.h b/source/core/slang-string.h index e8ee76397..59d441b76 100644 --- a/source/core/slang-string.h +++ b/source/core/slang-string.h @@ -157,6 +157,9 @@ namespace Slang bool startsWith(UnownedStringSlice const& other) const; bool startsWith(char const* str) const; + bool endsWithCaseInsensitive(UnownedStringSlice const& other) const; + bool endsWithCaseInsensitive(char const* str) const; + bool endsWith(UnownedStringSlice const& other) const; bool endsWith(char const* str) const; 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 members; + SourceLoc closingSourceLoc; template FilteredMemberList 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 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 << ""; + return; + } + if (m_optionFlags & OptionFlag::SimplifiedBuiltinType) + { + if (auto vectorType = as(type)) + { + if (as(vectorType->elementType)) + { + vectorType->elementType->toText(m_builder); + if (as(vectorType->elementCount)) + { + m_builder << vectorType->elementCount; + return; + } + } + } + else if (auto matrixType = as(type)) + { + auto elementType = matrixType->getElementType(); + if (as(elementType)) + { + matrixType->getElementType()->toText(m_builder); + if (as(matrixType->getRowCount()) && + as(matrixType->getColumnCount())) + { + m_builder << matrixType->getRowCount() << "x" << matrixType->getColumnCount(); + return; + } + } + } + } type->toText(m_builder); } @@ -223,7 +257,7 @@ void ASTPrinter::addGenericParams(const DeclRef& genericDeclRef) sb << ">"; } -void ASTPrinter::addDeclParams(const DeclRef& declRef) +void ASTPrinter::addDeclParams(const DeclRef& declRef, List>* outParamRange) { auto& sb = m_builder; @@ -237,6 +271,8 @@ void ASTPrinter::addDeclParams(const DeclRef& declRef) { if (!first) sb << ", "; + auto rangeStart = sb.getLength(); + ParamDecl* paramDecl = paramDeclRef; { @@ -278,6 +314,11 @@ void ASTPrinter::addDeclParams(const DeclRef& declRef) } } + auto rangeEnd = sb.getLength(); + + if (outParamRange) + outParamRange->add(makeArray(rangeStart, rangeEnd)); + first = false; } @@ -287,7 +328,7 @@ void ASTPrinter::addDeclParams(const DeclRef& declRef) { addGenericParams(genericDeclRef); - addDeclParams(DeclRef(getInner(genericDeclRef), genericDeclRef.substitutions)); + addDeclParams(DeclRef(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(modifier)) + continue; + if (as(modifier)) + continue; + if (as(modifier)) + continue; + if (as(modifier)) + continue; + if (as(modifier)) + continue; + if (as(modifier)) + continue; + } + m_builder << modifier->getKeywordName()->text << " "; + } + } if (as(decl)) { m_builder << "func "; } + else if (as(decl)) + { + m_builder << "struct "; + } + else if (as(decl)) + { + m_builder << "interface "; + } + else if (as(decl)) + { + m_builder << "class "; + } + else if (auto typedefDecl = as(decl)) + { + m_builder << "typedef "; + if (typedefDecl->type.type) + { + addType(typedefDecl->type.type); + m_builder << " "; + } + } + else if (auto propertyDecl = as(decl)) + { + m_builder << "property "; + } + else if (as(decl)) + { + m_builder << "namespace "; + } + else if (auto varDecl = as(decl)) + { + if (varDecl->getType()) + { + addType(varDecl->getType()); + m_builder << " "; + } + } + else if (as(decl)) + { + m_builder << "enum "; + } + else if (auto enumCase = as(decl)) + { + if (enumCase->getType()) + { + addType(enumCase->getType()); + m_builder << " "; + } + } + else if (auto assocType = as(decl)) + { + m_builder << "associatedtype "; + } } void ASTPrinter::addDeclResultType(const DeclRef& inDeclRef) @@ -326,6 +442,14 @@ void ASTPrinter::addDeclResultType(const DeclRef& inDeclRef) addType(getResultType(m_astBuilder, callableDeclRef)); } } + else if (auto propertyDecl = declRef.as()) + { + if (propertyDecl.getDecl()->type.type) + { + m_builder << " : "; + addType(declRef.substitute(m_astBuilder, propertyDecl.getDecl()->type.type)); + } + } } /* static */void ASTPrinter::addDeclSignature(const DeclRef& 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& declRef); + void addDeclParams(const DeclRef& declRef, List>* 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(toType)) + { + return _failedCoercion(toType, outToExpr, fromExpr); + } + // We allow implicit conversion of a parameter group type like // `ConstantBuffer` or `ParameterBlock` 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(expr)) + return m->memberOperatorLoc; + if (auto m = as(expr)) + return m->memberOperatorLoc; + return SourceLoc(); + } + Expr* SemanticsVisitor::ConstructDeclRefExpr( DeclRef 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(originalExpr) || as(originalExpr) || as(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(); + } 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(); 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(); 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(); 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(baseType)) { @@ -2020,7 +2062,8 @@ namespace Slang expr->name, lookupResult, baseExpression, - expr->loc); + expr->loc, + expr); } else if (as(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 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(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(); 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(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& outDecls) { outDecls.add(decl); } - else - { - SLANG_ASSERT(!"Decl without a location!"); - } if (GenericDecl* genericDecl = as(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 nodePath; + ASTLookupType findType; + Int line; + Int col; + Loc cursorLoc; + UnownedStringSlice sourceFileName; + List 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 +{ +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()) + 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()) + 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 +{ + 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(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(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(node)) + { + ASTLookupExprVisitor exprVisitor(&context); + if (exprVisitor.dispatchIfNotNull(propertyDecl->type.exp)) + return true; + } + else if (auto varDecl = as(node)) + { + ASTLookupExprVisitor visitor(&context); + if (visitor.dispatchIfNotNull(varDecl->type.exp)) + return true; + if (visitor.dispatchIfNotNull(varDecl->initExpr)) + return true; + } + else if (auto genericDecl = as(node)) + { + if (_findAstNodeImpl(context, genericDecl->inner)) + return true; + } + else if (auto typeConstraint = as(node)) + { + ASTLookupExprVisitor visitor(&context); + if (visitor.dispatchIfNotNull(typeConstraint->getSup().exp)) + return true; + } + else if (auto typedefDecl = as(node)) + { + ASTLookupExprVisitor visitor(&context); + if (visitor.dispatchIfNotNull(typedefDecl->type.exp)) + return true; + } + if (auto container = as(node)) + { + bool shouldInspectChildren = true; + if (auto genericDecl = as(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 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 path; +}; +enum class ASTLookupType +{ + Decl, + Invoke, +}; +List 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(type)) + { + collectMembersInType(context, pointerLikeType->elementType); + return; + } + + if (auto declRefType = as(type)) + { + auto declRef = declRefType->declRef; + + collectMembersInTypeDeclImpl( + context, + declRef); + } + else if (auto nsType = as(type)) + { + auto declRef = nsType->declRef; + + collectMembersInTypeDeclImpl(context, declRef); + } + else if (auto extractExistentialType = as(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(type)) + { + auto interfaceType = DeclRefType::create(context->astBuilder, thisType->interfaceDeclRef); + collectMembersInType(context, interfaceType); + } + else if (auto andType = as(type)) + { + auto leftType = andType->left; + auto rightType = andType->right; + collectMembersInType(context, leftType); + collectMembersInType(context, rightType); + } +} + +void collectMembersInTypeDeclImpl( + MemberCollectingContext* context, + DeclRef declRef) +{ + if (declRef.getDecl()->checkState.getState() < DeclCheckState::ReadyForLookup) + return; + + if (auto genericTypeParamDeclRef = declRef.as()) + { + // 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(); + assert(genericDeclRef); + + for (auto constraintDeclRef : getMembersOfType(genericDeclRef)) + { + if (constraintDeclRef.decl->checkState.getState() < DeclCheckState::ReadyForLookup) + { + continue; + } + + collectMembersInType( + context, + getSup(context->astBuilder, constraintDeclRef)); + } + } + else if (declRef.as() || declRef.as()) + { + for (auto constraintDeclRef : + getMembersOfType(declRef.as())) + { + if (constraintDeclRef.decl->checkState.getState() < DeclCheckState::ReadyForLookup) + { + continue; + } + collectMembersInType(context, getSup(context->astBuilder, constraintDeclRef)); + } + } + else if (auto namespaceDecl = declRef.as()) + { + for (auto member : namespaceDecl.getDecl()->members) + { + if (member->getName()) + { + context->members.add(member); + } + } + } + else if (auto aggTypeDeclBaseRef = declRef.as()) + { + // 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()) + { + 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(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(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()) + 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 members; + SharedSemanticsContext semanticsContext; + MemberCollectingContext(Linkage* linkage, Module* module, DiagnosticSink* sink) + : semanticsContext(linkage, module, sink) + {} +}; + +void collectMembersInTypeDeclImpl(MemberCollectingContext* context, DeclRef 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 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 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 tokenTypes; + + /** + * The token modifiers a server uses. + */ + List 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 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 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 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 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 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 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 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 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 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 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 + +namespace Slang +{ +template +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 + { + 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 + { + 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 +void ASTIterator::visitDecl(DeclBase* decl) +{ + // Don't look at the decl if it is defined in a different file. + if (!as(decl) && + !sourceManager->getHumaneLoc(decl->loc, SourceLocType::Actual) + .pathInfo.foundPath.getUnownedSlice() + .endsWithCaseInsensitive(fileName)) + return; + + maybeDispatchCallback(decl); + if (auto funcDecl = as(decl)) + { + visitStmt(funcDecl->body); + visitExpr(funcDecl->returnType.exp); + } + else if (auto propertyDecl = as(decl)) + { + visitExpr(propertyDecl->type.exp); + } + else if (auto varDecl = as(decl)) + { + visitExpr(varDecl->type.exp); + visitExpr(varDecl->initExpr); + } + else if (auto genericDecl = as(decl)) + { + visitDecl(genericDecl->inner); + } + else if (auto typeConstraint = as(decl)) + { + visitExpr(typeConstraint->getSup().exp); + } + else if (auto typedefDecl = as(decl)) + { + visitExpr(typedefDecl->type.exp); + } + if (auto container = as(decl)) + { + for (auto member : container->members) + { + visitDecl(member); + } + } +} +template +void ASTIterator::visitExpr(Expr* expr) +{ + ASTIteratorExprVisitor visitor(this); + visitor.dispatchIfNotNull(expr); +} +template +void ASTIterator::visitStmt(Stmt* stmt) +{ + ASTIteratorStmtVisitor visitor(this); + visitor.dispatchIfNotNull(stmt); +} + +template +void iterateAST(UnownedStringSlice fileName, SourceManager* manager, SyntaxNode* node, const Func& f) +{ + ASTIterator iter(f, manager, fileName); + if (auto decl = as(node)) + { + iter.visitDecl(decl); + } + else if (auto expr = as(node)) + { + iter.visitExpr(expr); + } + else if (auto stmt = as(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 getSemanticTokens(Linkage* linkage, Module* module, UnownedStringSlice fileName) +{ + auto manager = linkage->getSourceManager(); + + List 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(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(target)) + { + if (target->hasModifier()) + return; + token.type = SemanticTokenType::Type; + } + else if (as(target)) + { + token.type = SemanticTokenType::Type; + } + else if (as(target)) + { + token.type = SemanticTokenType::Property; + } + else if (as(target)) + { + token.type = SemanticTokenType::Parameter; + } + else if (as(target)) + { + token.type = SemanticTokenType::Variable; + } + else if (as(target)) + { + token.type = SemanticTokenType::Function; + } + else if (as(target)) + { + token.type = SemanticTokenType::EnumMember; + } + else if (as(target)) + { + token.type = SemanticTokenType::Namespace; + } + + if (as(target)) + { + if (target->hasModifier()) + return; + } + maybeInsertToken(token); + } + + } + else if (auto typeDecl = as(node)) + { + if (typeDecl->getName()) + { + SemanticToken token = + _createSemanticToken(manager, typeDecl->getNameLoc(), typeDecl->getName()); + token.type = SemanticTokenType::Type; + maybeInsertToken(token); + } + } + else if (auto aggTypeDecl = as(node)) + { + if (aggTypeDecl->getName()) + { + SemanticToken token = _createSemanticToken( + manager, aggTypeDecl->getNameLoc(), aggTypeDecl->getName()); + token.type = SemanticTokenType::Type; + maybeInsertToken(token); + } + } + else if (auto enumCase = as(node)) + { + if (enumCase->getName()) + { + SemanticToken token = _createSemanticToken( + manager, enumCase->getNameLoc(), enumCase->getName()); + token.type = SemanticTokenType::EnumMember; + maybeInsertToken(token); + } + } + else if (auto propertyDecl = as(node)) + { + if (propertyDecl->getName()) + { + SemanticToken token = _createSemanticToken( + manager, propertyDecl->getNameLoc(), propertyDecl->getName()); + token.type = SemanticTokenType::Property; + maybeInsertToken(token); + } + } + else if (auto funcDecl = as(node)) + { + if (funcDecl->getName()) + { + SemanticToken token = _createSemanticToken( + manager, funcDecl->getNameLoc(), funcDecl->getName()); + token.type = SemanticTokenType::Function; + maybeInsertToken(token); + } + } + else if (auto varDecl = as(node)) + { + if (varDecl->getName()) + { + SemanticToken token = _createSemanticToken( + manager, varDecl->getNameLoc(), varDecl->getName()); + token.type = SemanticTokenType::Variable; + maybeInsertToken(token); + } + } + }); + return result; +} + +List getEncodedTokens(List& tokens) +{ + List 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 getSemanticTokens( + Linkage* linkage, Module* module, UnownedStringSlice fileName); +List getEncodedTokens(List& 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 +#include +#include +#include +#include +#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 m_connection; + ComPtr m_session; + RefPtr m_workspace; + Dictionary m_lastPublishedDiagnostics; + time_t m_lastDiagnosticUpdateTime = 0; + + bool m_quit = false; + List 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 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 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 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 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 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(leafNode)) + { + fillDeclRefHoverInfo(declRefExpr->declRef); + } + else if (auto overloadedExpr = as(leafNode)) + { + LookupResultItem& item = overloadedExpr->lookupResult2.item; + fillDeclRefHoverInfo(item.declRef); + } + else if (auto decl = as(leafNode)) + { + fillDeclRefHoverInfo(DeclRef(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 locations; + auto leafNode = findResult[0].path.getLast(); + if (auto declRefExpr = as(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(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 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 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(findResult[0].path.getLast())) + { + baseExpr = memberExpr->baseExpression; + } + else if (auto staticMemberExpr = as(findResult[0].path.getLast())) + { + baseExpr = staticMemberExpr->baseExpression; + } + else if (auto swizzleExpr = as(findResult[0].path.getLast())) + { + baseExpr = swizzleExpr->base; + } + else if (auto matSwizzleExpr = as(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 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, 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(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 declRef) + { + if (!declRef.getDecl()) + return; + + SignatureInformation sigInfo; + + List> paramRanges; + ASTPrinter printer( + version->linkage->getASTBuilder(), + ASTPrinter::OptionFlag::ParamNames | ASTPrinter::OptionFlag::NoInternalKeywords | + ASTPrinter::OptionFlag::SimplifiedBuiltinType); + + printer.addDeclKindPrefix(declRef.getDecl()); + printer.addDeclPath(declRef); + printer.addDeclParams(declRef, ¶mRanges); + printer.addDeclResultType(declRef); + + sigInfo.label = printer.getString(); + + StringBuilder docSB; + auto humaneLoc = version->linkage->getSourceManager()->getHumaneLoc(declRef.getLoc(), SourceLocType::Actual); + _tryGetDocumentation(docSB, version, declRef.getDecl()); + + docSB << "Defined in " << humaneLoc.pathInfo.foundPath << "(" << humaneLoc.line << ")\n"; + sigInfo.documentation.value = docSB.ProduceString(); + sigInfo.documentation.kind = "markdown"; + + for (auto& range : paramRanges) + { + ParameterInformation paramInfo; + paramInfo.label[0] = (uint32_t)range[0]; + paramInfo.label[1] = (uint32_t)range[1]; + sigInfo.parameters.add(paramInfo); + } + response.signatures.add(sigInfo); + }; + if (auto declRefExpr = as(funcExpr)) + { + addDeclRef(declRefExpr->declRef); + } + else if (auto overloadedExpr = as(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 LanguageServer::collectMembers(WorkspaceVersion* version, Module* module, Expr* baseExpr) +{ + List result; + auto linkage = version->linkage; + Type* type = baseExpr->type.type; + if (auto typeType = as(type)) + { + type = typeType->type; + } + version->currentCompletionItems.clear(); + if (type) + { + if (as(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(type)) + { + if (auto elementCountVal = as(vectorType->elementCount)) + { + elementCount = (int)elementCountVal->value; + elementType = vectorType->elementType; + } + } + else if (auto matrixType = as(type)) + { + if (auto elementCountVal = as(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 deduplicateSet; + for (auto member : context.members) + { + CompletionItem item; + item.label = member->getName()->text; + item.kind = 0; + if (as(member)) + { + continue; + } + if (as(member)) + { + continue; + } + if (as(member)) + { + continue; + } + + if (item.label.startsWith("$")) + continue; + if (!deduplicateSet.Add(item.label)) + continue; + + if (as(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindStruct; + } + else if (as(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindClass; + } + else if (as(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindInterface; + } + else if (as(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindClass; + } + else if (as(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindProperty; + } + else if (as(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindEnum; + } + else if (as(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindVariable; + } + else if (as(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindEnumMember; + } + else if (as(member)) + { + item.kind = LanguageServerProtocol::kCompletionItemKindMethod; + } + else if (as(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 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 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 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(); - 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->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(); + } parseStorageDeclBody(parser, decl); @@ -3218,7 +3252,6 @@ namespace Slang static NodeBase* parsePropertyDecl(Parser* parser, void* /*userData*/) { PropertyDecl* decl = parser->astBuilder->create(); - 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(); - 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(); - 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(); - 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(); // Either: // - parenthesized expression `(exp)` @@ -5577,6 +5618,10 @@ namespace Slang { indexExpr->indexExpression = parser->ParseExpression(); } + else + { + indexExpr->indexExpression = parser->astBuilder->create(); + } parser->ReadToken(TokenType::RBracket); expr = indexExpr; @@ -5589,7 +5634,8 @@ namespace Slang InvokeExpr* invokeExpr = parser->astBuilder->create(); 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 workList; + OrderedHashSet 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 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 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& diagnostics, String compilerOutput) +{ + List 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 Workspace::createWorkspaceVersion() +{ + RefPtr 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 searchPathsRaw; + + for (auto path : searchPaths) + searchPathsRaw.add(path.getBuffer()); + desc.searchPaths = searchPathsRaw.getBuffer(); + desc.searchPathCount = searchPathsRaw.getCount(); + + ComPtr session; + slangGlobalSession->createSession(desc, session.writeRef()); + version->linkage = static_cast(session.get()); + return version; +} + +SlangResult Workspace::loadFile(const char* path, ISlangBlob** outBlob) +{ + String canonnicalPath; + SLANG_RETURN_ON_FAIL(Path::getCanonical(path, canonnicalPath)); + RefPtr doc; + if (openedDocuments.TryGetValue(canonnicalPath, doc)) + { + RefPtr 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(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; + 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 diagnosticBlob; + RefPtr 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(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(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 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 messages; + String originalOutput; + }; + + class WorkspaceVersion : public RefObject + { + private: + Dictionary modules; + Dictionary> markupASTs; + public: + Workspace* workspace; + RefPtr linkage; + Dictionary diagnostics; + List currentCompletionItems; + ASTMarkup* getOrCreateMarkupAST(ModuleDecl* module); + + Module* getOrLoadModule(String path); + }; + + class Workspace + : public ISlangFileSystem + , public ComObject + { + private: + RefPtr currentVersion; + RefPtr createWorkspaceVersion(); + public: + List rootDirectories; + OrderedHashSet searchPaths; + + slang::IGlobalSession* slangGlobalSession; + Dictionary> openedDocuments; + DocumentVersion* openDoc(String path, String text); + void init(List 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 Linkage::loadModule( @@ -2641,11 +2675,11 @@ RefPtr 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 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. diff --git a/tests/bugs/generic-type-arg-overloaded.slang.expected b/tests/bugs/generic-type-arg-overloaded.slang.expected index 126dcfa60..390c4fe00 100644 --- a/tests/bugs/generic-type-arg-overloaded.slang.expected +++ b/tests/bugs/generic-type-arg-overloaded.slang.expected @@ -2,16 +2,16 @@ result code = -1 standard error = { tests/bugs/generic-type-arg-overloaded.slang(14): error 30200: declaration of 'Stuff' conflicts with existing declaration struct Stuff {} -^~~~~~ + ^~~~~ tests/bugs/generic-type-arg-overloaded.slang(11): note: see previous declaration of 'Stuff' tests/bugs/generic-type-arg-overloaded.slang(26): error 39999: ambiguous reference to 'Stuff' return util() ^~~~~ -tests/bugs/generic-type-arg-overloaded.slang(14): note 39999: candidate: Stuff -tests/bugs/generic-type-arg-overloaded.slang(11): note 39999: candidate: Stuff +tests/bugs/generic-type-arg-overloaded.slang(14): note 39999: candidate: struct Stuff +tests/bugs/generic-type-arg-overloaded.slang(11): note 39999: candidate: struct Stuff tests/bugs/generic-type-arg-overloaded.slang(32): error 39999: expected a generic when using '<...>' (found: '() -> int') + nonGeneric(); - ^ + ^~~~~~~~~~ } standard output = { } diff --git a/tests/bugs/parser-infinite-loop.slang b/tests/bugs/parser-infinite-loop.slang new file mode 100644 index 000000000..70abc9260 --- /dev/null +++ b/tests/bugs/parser-infinite-loop.slang @@ -0,0 +1,11 @@ +//DIAGNOSTIC_TEST:SIMPLE: + +struct test +{ + float3 field; +} +void f() +{ + test x; x + vector v; +} diff --git a/tests/bugs/parser-infinite-loop.slang.expected b/tests/bugs/parser-infinite-loop.slang.expected new file mode 100644 index 000000000..df1d731bc --- /dev/null +++ b/tests/bugs/parser-infinite-loop.slang.expected @@ -0,0 +1,11 @@ +result code = -1 +standard error = { +tests/bugs/parser-infinite-loop.slang(10): error 20001: unexpected integer literal, expected identifier + vector v; + ^ +tests/bugs/parser-infinite-loop.slang(10): error 20001: unexpected identifier, expected '(' + vector v; + ^ +} +standard output = { +} diff --git a/tests/diagnostics/bad-operator-call.slang.expected b/tests/diagnostics/bad-operator-call.slang.expected index 6b8f250d3..e7dc23739 100644 --- a/tests/diagnostics/bad-operator-call.slang.expected +++ b/tests/diagnostics/bad-operator-call.slang.expected @@ -3,36 +3,36 @@ standard error = { tests/diagnostics/bad-operator-call.slang(18): error 39999: no overload for '+=' applicable to arguments of type (int, S) a += b; ^~ -core.meta.slang(1937): note 39999: candidate: func +=(out matrix, T) -> matrix -core.meta.slang(1929): note 39999: candidate: func +=(out matrix, matrix) -> matrix -core.meta.slang(1921): note 39999: candidate: func +=(out vector, T) -> vector -core.meta.slang(1913): note 39999: candidate: func +=(out vector, vector) -> vector -core.meta.slang(1905): note 39999: candidate: func +=(out T, T) -> T +core.meta.slang(1940): note 39999: candidate: __unsafeForceInlineEarly func +=(out matrix, T) -> matrix +core.meta.slang(1932): note 39999: candidate: __unsafeForceInlineEarly func +=(out matrix, matrix) -> matrix +core.meta.slang(1924): note 39999: candidate: __unsafeForceInlineEarly func +=(out vector, T) -> vector +core.meta.slang(1916): note 39999: candidate: __unsafeForceInlineEarly func +=(out vector, vector) -> vector +core.meta.slang(1908): note 39999: candidate: __unsafeForceInlineEarly func +=(out T, T) -> T tests/diagnostics/bad-operator-call.slang(20): error 39999: no overload for '+' applicable to arguments of type (int, S) a = a + b; ^ -core.meta.slang(1743): note 39999: candidate: func +(uint64_t, uint64_t) -> uint64_t -core.meta.slang(1736): note 39999: candidate: func +(uint, uint) -> uint -core.meta.slang(1729): note 39999: candidate: func +(uint16_t, uint16_t) -> uint16_t -core.meta.slang(1722): note 39999: candidate: func +(uint8_t, uint8_t) -> uint8_t -core.meta.slang(1715): note 39999: candidate: func +(double, double) -> double -core.meta.slang(1708): note 39999: candidate: func +(float, float) -> float -core.meta.slang(1701): note 39999: candidate: func +(half, half) -> half -core.meta.slang(1694): note 39999: candidate: func +(int64_t, int64_t) -> int64_t -core.meta.slang(1687): note 39999: candidate: func +(int, int) -> int -core.meta.slang(1680): note 39999: candidate: func +(int16_t, int16_t) -> int16_t +core.meta.slang(1746): note 39999: candidate: __intrinsic_op func +(uint64_t, uint64_t) -> uint64_t +core.meta.slang(1739): note 39999: candidate: __intrinsic_op func +(uint, uint) -> uint +core.meta.slang(1732): note 39999: candidate: __intrinsic_op func +(uint16_t, uint16_t) -> uint16_t +core.meta.slang(1725): note 39999: candidate: __intrinsic_op func +(uint8_t, uint8_t) -> uint8_t +core.meta.slang(1718): note 39999: candidate: __intrinsic_op func +(double, double) -> double +core.meta.slang(1711): note 39999: candidate: __intrinsic_op func +(float, float) -> float +core.meta.slang(1704): note 39999: candidate: __intrinsic_op func +(half, half) -> half +core.meta.slang(1697): note 39999: candidate: __intrinsic_op func +(int64_t, int64_t) -> int64_t +core.meta.slang(1690): note 39999: candidate: __intrinsic_op func +(int, int) -> int +core.meta.slang(1683): note 39999: candidate: __intrinsic_op func +(int16_t, int16_t) -> int16_t tests/diagnostics/bad-operator-call.slang(20): note 39999: 1 more overload candidates tests/diagnostics/bad-operator-call.slang(22): error 39999: no overload for '~' applicable to arguments of type (S) a = ~b; ^ -core.meta.slang(1629): note 39999: candidate: func ~(uint64_t) -> uint64_t -core.meta.slang(1626): note 39999: candidate: func ~(uint) -> uint -core.meta.slang(1623): note 39999: candidate: func ~(uint16_t) -> uint16_t -core.meta.slang(1620): note 39999: candidate: func ~(uint8_t) -> uint8_t -core.meta.slang(1617): note 39999: candidate: func ~(int64_t) -> int64_t -core.meta.slang(1614): note 39999: candidate: func ~(int) -> int -core.meta.slang(1611): note 39999: candidate: func ~(int16_t) -> int16_t -core.meta.slang(1608): note 39999: candidate: func ~(int8_t) -> int8_t +core.meta.slang(1632): note 39999: candidate: __prefix __intrinsic_op func ~(uint64_t) -> uint64_t +core.meta.slang(1629): note 39999: candidate: __prefix __intrinsic_op func ~(uint) -> uint +core.meta.slang(1626): note 39999: candidate: __prefix __intrinsic_op func ~(uint16_t) -> uint16_t +core.meta.slang(1623): note 39999: candidate: __prefix __intrinsic_op func ~(uint8_t) -> uint8_t +core.meta.slang(1620): note 39999: candidate: __prefix __intrinsic_op func ~(int64_t) -> int64_t +core.meta.slang(1617): note 39999: candidate: __prefix __intrinsic_op func ~(int) -> int +core.meta.slang(1614): note 39999: candidate: __prefix __intrinsic_op func ~(int16_t) -> int16_t +core.meta.slang(1611): note 39999: candidate: __prefix __intrinsic_op func ~(int8_t) -> int8_t tests/diagnostics/bad-operator-call.slang(27): error 30047: argument passed to parameter '0' must be l-value. a += c; ^ @@ -40,24 +40,24 @@ tests/diagnostics/bad-operator-call.slang(27): note 30048: argument was implicit tests/diagnostics/bad-operator-call.slang(31): error 39999: no overload for '+=' applicable to arguments of type (vector, vector) d += c; ^~ -core.meta.slang(1937): note 39999: candidate: func +=(out matrix, T) -> matrix -core.meta.slang(1929): note 39999: candidate: func +=(out matrix, matrix) -> matrix -core.meta.slang(1921): note 39999: candidate: func +=(out vector, T) -> vector -core.meta.slang(1913): note 39999: candidate: func +=(out vector, vector) -> vector -core.meta.slang(1905): note 39999: candidate: func +=(out T, T) -> T +core.meta.slang(1940): note 39999: candidate: __unsafeForceInlineEarly func +=(out matrix, T) -> matrix +core.meta.slang(1932): note 39999: candidate: __unsafeForceInlineEarly func +=(out matrix, matrix) -> matrix +core.meta.slang(1924): note 39999: candidate: __unsafeForceInlineEarly func +=(out vector, T) -> vector +core.meta.slang(1916): note 39999: candidate: __unsafeForceInlineEarly func +=(out vector, vector) -> vector +core.meta.slang(1908): note 39999: candidate: __unsafeForceInlineEarly func +=(out T, T) -> T tests/diagnostics/bad-operator-call.slang(33): error 39999: no overload for '+' applicable to arguments of type (vector, vector) d = c + d; ^ -core.meta.slang(1748): note 39999: candidate: func +<4>(vector, uint64_t) -> vector -core.meta.slang(1746): note 39999: candidate: func +<3>(uint64_t, vector) -> vector -core.meta.slang(1743): note 39999: candidate: func +(uint64_t, uint64_t) -> uint64_t -core.meta.slang(1741): note 39999: candidate: func +<4>(vector, uint) -> vector -core.meta.slang(1739): note 39999: candidate: func +<3>(uint, vector) -> vector -core.meta.slang(1736): note 39999: candidate: func +(uint, uint) -> uint -core.meta.slang(1734): note 39999: candidate: func +<4>(vector, uint16_t) -> vector -core.meta.slang(1732): note 39999: candidate: func +<3>(uint16_t, vector) -> vector -core.meta.slang(1729): note 39999: candidate: func +(uint16_t, uint16_t) -> uint16_t -core.meta.slang(1727): note 39999: candidate: func +<4>(vector, uint8_t) -> vector +core.meta.slang(1751): note 39999: candidate: __intrinsic_op func +<4>(vector, uint64_t) -> vector +core.meta.slang(1749): note 39999: candidate: __intrinsic_op func +<3>(uint64_t, vector) -> vector +core.meta.slang(1746): note 39999: candidate: __intrinsic_op func +(uint64_t, uint64_t) -> uint64_t +core.meta.slang(1744): note 39999: candidate: __intrinsic_op func +<4>(vector, uint) -> vector +core.meta.slang(1742): note 39999: candidate: __intrinsic_op func +<3>(uint, vector) -> vector +core.meta.slang(1739): note 39999: candidate: __intrinsic_op func +(uint, uint) -> uint +core.meta.slang(1737): note 39999: candidate: __intrinsic_op func +<4>(vector, uint16_t) -> vector +core.meta.slang(1735): note 39999: candidate: __intrinsic_op func +<3>(uint16_t, vector) -> vector +core.meta.slang(1732): note 39999: candidate: __intrinsic_op func +(uint16_t, uint16_t) -> uint16_t +core.meta.slang(1730): note 39999: candidate: __intrinsic_op func +<4>(vector, uint8_t) -> vector tests/diagnostics/bad-operator-call.slang(33): note 39999: 23 more overload candidates } standard output = { diff --git a/tests/diagnostics/interfaces/anyvalue-size-validation.slang.expected b/tests/diagnostics/interfaces/anyvalue-size-validation.slang.expected index 930e71c5b..c3cb9e9c1 100644 --- a/tests/diagnostics/interfaces/anyvalue-size-validation.slang.expected +++ b/tests/diagnostics/interfaces/anyvalue-size-validation.slang.expected @@ -2,7 +2,7 @@ result code = -1 standard error = { tests/diagnostics/interfaces/anyvalue-size-validation.slang(11): error 41011: type 'S' does not fit in the size required by its conforming interface. struct S : IInterface -^~~~~~ + ^ } standard output = { } diff --git a/tests/diagnostics/matrix-swizzle.slang.expected b/tests/diagnostics/matrix-swizzle.slang.expected index 6c9a14a00..832ddd739 100644 --- a/tests/diagnostics/matrix-swizzle.slang.expected +++ b/tests/diagnostics/matrix-swizzle.slang.expected @@ -2,43 +2,43 @@ result code = -1 standard error = { tests/diagnostics/matrix-swizzle.slang(8): error 30052: invalid swizzle pattern '_14' on type 'int' int c = m1._14; // Out of bounds - ^ + ^~~ tests/diagnostics/matrix-swizzle.slang(9): error 30052: invalid swizzle pattern '_32' on type 'int' c = m1._32; - ^ + ^~~ tests/diagnostics/matrix-swizzle.slang(10): error 30052: invalid swizzle pattern '_m22' on type 'int' c = m2._m22; - ^ + ^~~~ tests/diagnostics/matrix-swizzle.slang(11): error 30052: invalid swizzle pattern '_' on type 'int' c = m2._; // unfinished - ^ + ^ tests/diagnostics/matrix-swizzle.slang(12): error 30052: invalid swizzle pattern '_m' on type 'int' c = m2._m; - ^ + ^~ tests/diagnostics/matrix-swizzle.slang(13): error 30052: invalid swizzle pattern '_1' on type 'int' c = m2._1; - ^ + ^~ tests/diagnostics/matrix-swizzle.slang(14): error 30052: invalid swizzle pattern '_m1' on type 'int' c = m2._m1; - ^ + ^~~ tests/diagnostics/matrix-swizzle.slang(15): error 30052: invalid swizzle pattern '_m12_' on type 'int' c = m2._m12_; - ^ + ^~~~~ tests/diagnostics/matrix-swizzle.slang(16): error 30052: invalid swizzle pattern '_m11_11' on type 'int' int2 c2 = m1._m11_11; // Mixing of 1 and 0-indexing - ^ + ^~~~~~~ tests/diagnostics/matrix-swizzle.slang(17): error 30052: invalid swizzle pattern '_11_11_11_11_11' on type 'int' c = m1._11_11_11_11_11; // More than 4 elements - ^ + ^~~~~~~~~~~~~~~ tests/diagnostics/matrix-swizzle.slang(18): error 30052: invalid swizzle pattern 'x' on type 'int' c = m1.x; // Invalid character - ^ + ^ tests/diagnostics/matrix-swizzle.slang(19): error 30052: invalid swizzle pattern '_x' on type 'int' c = m1._x; - ^ + ^~ tests/diagnostics/matrix-swizzle.slang(20): error 30052: invalid swizzle pattern 'x123' on type 'int' c = m1.x123; - ^ + ^~~~ } standard output = { } diff --git a/tests/diagnostics/mismatching-types.slang.expected b/tests/diagnostics/mismatching-types.slang.expected index 2186a8d10..65a6df32e 100644 --- a/tests/diagnostics/mismatching-types.slang.expected +++ b/tests/diagnostics/mismatching-types.slang.expected @@ -11,16 +11,16 @@ tests/diagnostics/mismatching-types.slang(55): error 30019: expected an expressi ^ tests/diagnostics/mismatching-types.slang(57): error 30019: expected an expression of type 'GenericOuter.GenericInner', got 'GenericOuter.GenericInner' a.g = b.g; - ^ + ^ tests/diagnostics/mismatching-types.slang(59): error 30019: expected an expression of type 'GenericOuter.NonGenericInner', got 'GenericOuter.NonGenericInner' a.ng = b.ng; - ^ + ^~ tests/diagnostics/mismatching-types.slang(61): error 30019: expected an expression of type 'NonGenericOuter.GenericInner', got 'int' c.i = 0; ^ tests/diagnostics/mismatching-types.slang(63): error 30019: expected an expression of type 'NonGenericOuter.GenericInner', got 'NonGenericOuter.GenericInner' c.i = c.f; - ^ + ^ tests/diagnostics/mismatching-types.slang(65): error 30019: expected an expression of type 'NonGenericOuter.GenericInner.ReallyNested', got 'int' c.i.n = 0; ^ diff --git a/tests/diagnostics/static-ref-to-nonstatic-member.slang.expected b/tests/diagnostics/static-ref-to-nonstatic-member.slang.expected index dc6629470..27d8152f1 100644 --- a/tests/diagnostics/static-ref-to-nonstatic-member.slang.expected +++ b/tests/diagnostics/static-ref-to-nonstatic-member.slang.expected @@ -2,7 +2,7 @@ result code = -1 standard error = { tests/diagnostics/static-ref-to-nonstatic-member.slang(11): error 30100: type 'Color' cannot be used to refer to non-static member 'Red' int x = Color.Red; - ^ + ^~~ } standard output = { } diff --git a/tests/diagnostics/variable-redeclaration.slang.expected b/tests/diagnostics/variable-redeclaration.slang.expected index 03bed580b..d18e5fb2a 100644 --- a/tests/diagnostics/variable-redeclaration.slang.expected +++ b/tests/diagnostics/variable-redeclaration.slang.expected @@ -19,8 +19,8 @@ tests/diagnostics/variable-redeclaration.slang(20): note: see previous declarati tests/diagnostics/variable-redeclaration.slang(53): error 39999: ambiguous reference to 'size' return size; ^~~~ -tests/diagnostics/variable-redeclaration.slang(51): note 39999: candidate: size -tests/diagnostics/variable-redeclaration.slang(50): note 39999: candidate: size +tests/diagnostics/variable-redeclaration.slang(51): note 39999: candidate: float size +tests/diagnostics/variable-redeclaration.slang(50): note 39999: candidate: int size } standard output = { } diff --git a/tools/slangd/language-server-protocol.cpp b/tools/slangd/language-server-protocol.cpp deleted file mode 100644 index eca89fd86..000000000 --- a/tools/slangd/language-server-protocol.cpp +++ /dev/null @@ -1,191 +0,0 @@ -#include "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); - return builder.make(); -} -const StructRttiInfo TextDocumentSyncOptions::g_rttiInfo = _makeTextDocumentSyncOptionsRtti(); - -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); - 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); - 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); - 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); - 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); - 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); - 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); - 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); - 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); - 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); - 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); - 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); - 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); - 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); - 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; -} - -} // namespace LanguageServerProtocol - -} diff --git a/tools/slangd/language-server-protocol.h b/tools/slangd/language-server-protocol.h deleted file mode 100644 index c76f91d5c..000000000 --- a/tools/slangd/language-server-protocol.h +++ /dev/null @@ -1,148 +0,0 @@ -#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 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 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; - static const StructRttiInfo g_rttiInfo; - }; - - struct WorkspaceFolder - { - String uri; - String name; - static const StructRttiInfo g_rttiInfo; - }; - - struct InitializeParams - { - List 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; - }; - -} -} // namespace LanguageServerProtocol diff --git a/tools/slangd/language-server.cpp b/tools/slangd/language-server.cpp deleted file mode 100644 index 7cb744ecd..000000000 --- a/tools/slangd/language-server.cpp +++ /dev/null @@ -1,184 +0,0 @@ -// 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 -#include -#include -#include - -#include "../../source/core/slang-secure-crt.h" -#include "../../slang-com-helper.h" - -#include "language-server-protocol.h" -#include "language-server.h" - -namespace Slang -{ -using namespace LanguageServerProtocol; - -SlangResult LanguageServer::init(const InitializeParams& args) -{ - SLANG_RETURN_ON_FAIL(m_connection->initWithStdStreams()); - m_workspaceFolders = args.workspaceFolders; - 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; -} - -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 = {}; - SLANG_RETURN_ON_FAIL( - 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; - 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 == "initialized") - { - return SLANG_OK; - } - else - { - return m_connection->sendError(JSONRPC::ErrorCode::MethodNotFound, call.id); - } - } - default: - { - return m_connection->sendError( - JSONRPC::ErrorCode::InvalidRequest, m_connection->getCurrentMessageId()); - } - } - - return SLANG_OK; -} - -SlangResult LanguageServer::didOpenTextDocument(const DidOpenTextDocumentParams& args) -{ - return SLANG_OK; -} -SlangResult LanguageServer::didCloseTextDocument(const DidCloseTextDocumentParams& args) -{ - return SLANG_OK; -} -SlangResult LanguageServer::didChangeTextDocument(const DidChangeTextDocumentParams& args) -{ - return SLANG_OK; -} - -void LanguageServer::update() -{ - -} - -SlangResult LanguageServer::execute() -{ - m_connection = new JSONRPCConnection(); - m_connection->initWithStdStreams(); - while (m_connection->isActive() && !m_quit) - { - // Consume all messages first. - while (m_connection->tryReadMessage() == SLANG_OK) - { - const SlangResult res = _executeSingle(); - } - - // Now we can use this time to reparse user's code, report diagnostics, etc. - update(); - } - - return SLANG_OK; -} - -} // namespace LanguageServer - -int main(int argc, const char* const* argv) -{ - bool isDebug = false; - for (auto i = 1; i < argc; i++) - { - if (Slang::UnownedStringSlice(argv[i]) == "--debug") - { - isDebug = true; - } - } - if (isDebug) - { - std::this_thread::sleep_for(std::chrono::seconds(10)); - } - Slang::LanguageServer server; - SLANG_RETURN_ON_FAIL(server.execute()); - return 0; -} diff --git a/tools/slangd/language-server.h b/tools/slangd/language-server.h deleted file mode 100644 index 829a629b1..000000000 --- a/tools/slangd/language-server.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "../../source/core/slang-io.h" -#include "../../source/core/slang-process-util.h" -#include "../../source/core/slang-string-util.h" -#include "../../source/core/slang-string.h" -#include "../../source/core/slang-writer.h" -#include "../../source/compiler-core/slang-json-rpc-connection.h" -#include "language-server-protocol.h" - -namespace Slang -{ - class LanguageServer - { - public: - RefPtr m_connection; - ComPtr m_session; - bool m_quit = false; - List 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); - - private: - SlangResult _executeSingle(); - slang::IGlobalSession* getOrCreateGlobalSession(); - }; -} diff --git a/tools/slangd/main.cpp b/tools/slangd/main.cpp new file mode 100644 index 000000000..4e3bfd029 --- /dev/null +++ b/tools/slangd/main.cpp @@ -0,0 +1,25 @@ +// main.cpp + +// This file implements the entry point for `slangd`, the daemon process of Slang's language server. + +#include + +#include "../../source/core/slang-basic.h" +#include "../../source/slang/slang-language-server.h" + +int main(int argc, const char* const* argv) +{ + bool isDebug = false; + for (auto i = 1; i < argc; i++) + { + if (Slang::UnownedStringSlice(argv[i]) == "--debug") + { + isDebug = true; + } + } + if (isDebug) + { + std::this_thread::sleep_for(std::chrono::seconds(10)); + } + return Slang::runLanguageServer(); +} -- cgit v1.2.3