Skip to content

fix(roslyn): install missing System.Runtime.CompilerServices.Unsafe v6 + surface inner errors#1116

Open
sMartz1 wants to merge 2 commits intoCoplayDev:betafrom
sMartz1:fix/roslyn-missing-system-runtime-compilerservices-unsafe
Open

fix(roslyn): install missing System.Runtime.CompilerServices.Unsafe v6 + surface inner errors#1116
sMartz1 wants to merge 2 commits intoCoplayDev:betafrom
sMartz1:fix/roslyn-missing-system-runtime-compilerservices-unsafe

Conversation

@sMartz1
Copy link
Copy Markdown
Contributor

@sMartz1 sMartz1 commented May 9, 2026

Summary

The RoslynInstaller ships only 4 NuGet packages but Microsoft.CodeAnalysis 4.12.0 on netstandard2.0 also references System.Runtime.CompilerServices.Unsafe v6.0.0.0, which is not what Unity ships (Unity bundles a v4.x of that assembly). The reference is unresolved at runtime, so:

Roslyn.Utilities.StringTable..cctor()
  → SegmentedArray<T>..ctor()
    → FileNotFoundException: System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0
→ TypeInitializationException: 'Roslyn.Utilities.StringTable'
  → TypeInitializationException: 'Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree'
    → every parse / compile attempt throws TargetInvocationException

Worse, the failure is invisible: RoslynCompiler.Compile's catch block logs only e.Message, and for TargetInvocationException that string is the generic "Exception has been thrown by the target of an invocation." — the real cause in InnerException is silently dropped, so the install looks fine and IsAvailable returns true, but every actual compile fails with a useless message.

Repro

  1. Fresh project, run Tools → MCP for Unity → Install Roslyn (or however the install is triggered).
  2. Use the MCP execute_code tool with compiler="roslyn" (or auto) on any C# 7+ snippet.
  3. Result: Compilation failed: Roslyn compilation error: Exception has been thrown by the target of an invocation. — no other detail.
  4. Drilling via reflection (replicating Compile step-by-step with proper InnerException walking) reveals the real chain shown above.

Fix

  • MCPForUnity/Editor/Setup/RoslynInstaller.cs — add the missing transitive dependency system.runtime.compilerservices.unsafe v6.0.0 to NuGetEntries so a fresh install drops 5 DLLs into Assets/Plugins/Roslyn/ instead of 4. This makes IsInstalled() accurately reflect "Roslyn will actually load".
  • MCPForUnity/Editor/Tools/ExecuteCode.cs — in RoslynCompiler.Compile's outer catch, walk the InnerException chain to the deepest cause and report Type.Name + Message of that root, instead of the generic wrapper. This makes future bootstrap regressions debuggable from the tool output alone.

Verification

Tested on Unity 2022.3 / Windows / netstandard2.0 build target:

  1. Before patch: Roslyn IsAvailable=True, every parse fails with the generic message.
  2. Removed local Assets/Plugins/Roslyn/, ran the patched installer → 5 DLLs present.
  3. Forced a domain reload (the cached TypeInitializationException would otherwise persist for the AppDomain lifetime).
  4. After patch: execute_code with compiler="roslyn" compiles C# 7+ snippets (var, ?., ??, string interpolation) and runs them successfully.

Notes

  • The transitive dep is also pulled by System.Reflection.Metadata 8.0.0 and System.Memory, but the Roslyn IL references the specific Version=6.0.0.0 ref-assembly — there is no binding redirect, and Unity's bundled System.Runtime.CompilerServices.Unsafe doesn't satisfy a strong-named v6 reference.
  • No behavior changes for users who already have Roslyn working (e.g. via a binding redirect or a different plugins layout) — the new entry is additive.
  • The catch change is purely diagnostic; it never alters whether compilation succeeds, only what gets surfaced when it doesn't.

Test plan

  • On a fresh project (no Assets/Plugins/Roslyn/), run the installer → confirm 5 DLLs are present.
  • Restart the Editor (force domain reload).
  • Execute a Roslyn-only snippet via execute_code → confirm success.
  • (Negative) Manually delete System.Runtime.CompilerServices.Unsafe.dll and try again → confirm the error message now reports FileNotFoundException directly instead of the generic invocation wrapper.

Summary by CodeRabbit

  • Bug Fixes

    • Improved compiler error reporting to surface the root exception type and message for clearer troubleshooting.
    • Installer now verifies on-disk assembly versions and treats missing/outdated or unreadable assemblies as not installed, ensuring correct reinstalls.
  • Chores

    • Added required runtime dependency to the installation list and adjusted related installer configuration.

Review Change Stack

…nd surface inner errors

The RoslynInstaller downloads only 4 NuGet packages but Microsoft.CodeAnalysis 4.12.0
on netstandard2.0 also references System.Runtime.CompilerServices.Unsafe v6.0.0.0,
which is NOT what Unity ships (Unity bundles v4.x). The reference is unresolved at
runtime, so Roslyn's StringTable static cctor throws FileNotFoundException, which
in turn poisons CSharpSyntaxTree's cctor: every parse / compile attempt then
throws TypeInitializationException.

The error is invisible because RoslynCompiler.Compile's catch block only logs
e.Message, and for TargetInvocationException that string is the generic
"Exception has been thrown by the target of an invocation." — the real cause
in InnerException is silently dropped.

Repro:
  1. Trigger Tools > MCP for Unity > Install Roslyn on a fresh project.
  2. Ask the MCP execute_code tool to compile any Roslyn-only snippet.
  3. Observe: "Compilation failed: Roslyn compilation error: Exception has been
     thrown by the target of an invocation." — with no further detail.
  4. Drilling via reflection reveals:
       TypeInitializationException for CSharpSyntaxTree
        └─ TypeInitializationException for Roslyn.Utilities.StringTable
            └─ FileNotFoundException: Could not load file or assembly
                'System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0'

Fix:
  * RoslynInstaller.NuGetEntries: add the missing dependency so a fresh install
    drops all 5 DLLs into Assets/Plugins/Roslyn/.
  * RoslynCompiler.Compile catch: walk the InnerException chain so a future
    bootstrap regression surfaces the actual cause (type + message) instead of
    the generic invocation wrapper.

Verified locally: after dropping System.Runtime.CompilerServices.Unsafe.dll v6
into the plugins folder and forcing a domain reload, the execute_code tool
compiles C# 7+ snippets via the Roslyn backend successfully.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 810adf5d-15b1-49b7-8106-8d3c5ea0d238

📥 Commits

Reviewing files that changed from the base of the PR and between f116493 and 3b1d1aa.

📒 Files selected for processing (1)
  • MCPForUnity/Editor/Setup/RoslynInstaller.cs

📝 Walkthrough

Walkthrough

This PR adds a transitive NuGet dependency required for Roslyn integration (System.Runtime.CompilerServices.Unsafe v6.0.0), strengthens IsInstalled() to validate on-disk assembly versions (using reflection), and improves Roslyn compiler error reporting by extracting the deepest InnerException type and message.

Changes

Roslyn Integration Updates

Layer / File(s) Summary
Dependency Management
MCPForUnity/Editor/Setup/RoslynInstaller.cs
NuGetEntries install list adds System.Runtime.CompilerServices.Unsafe (v6.0.0) and reformats existing Roslyn DLL tuple entries.
On-disk Installation & Version Check
MCPForUnity/Editor/Setup/RoslynInstaller.cs
Added using System.Reflection; and enhanced IsInstalled() to load DLLs and verify assembly versions are >= the declared NuGet versions; unreadable or older assemblies cause IsInstalled() to return false.
Compiler Error Reporting
MCPForUnity/Editor/Tools/ExecuteCode.cs
RoslynCompiler.Compile now traverses the InnerException chain to the root and reports the root exception's type and message in the errors list.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • CoplayDev/unity-mcp#841: Introduced RoslynInstaller functionality; this PR extends NuGetEntries and improves compiler error handling within the same Roslyn integration subsystem.

Poem

🐰 I hop through DLLs with a careful prance,
I add an Unsafe step to Roslyn's dance,
I peek inside assemblies to check their age,
And chase inner errors out of their cage. 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main changes: installing the missing System.Runtime.CompilerServices.Unsafe v6 dependency and improving error reporting in Roslyn compilation.
Description check ✅ Passed The description covers all major template sections including summary, type of change (bug fix), changes made, verification/testing, and related context. The document is comprehensive and complete.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@MCPForUnity/Editor/Setup/RoslynInstaller.cs`:
- Around line 16-23: The IsInstalled() check currently treats the presence of
System.Runtime.CompilerServices.Unsafe.dll as sufficient; change IsInstalled()
to also verify the assembly file's version is at least 6.0.0.0 (the required v6)
before returning true: locate the entry for
("system.runtime.compilerservices.unsafe","6.0.0", ...) and in IsInstalled() use
System.Reflection.AssemblyName.GetAssemblyName(path).Version to read the file's
Version and compare to Version(6,0,0,0); if the file exists but version is less
than 6.0.0.0, treat it as not installed. Add using System.Reflection; at the
top. Optionally apply the same version check pattern to the other package tuples
for defense-in-depth.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c1a8aeb-9dd5-478f-86fd-3fefb2c9c9ef

📥 Commits

Reviewing files that changed from the base of the PR and between a2a5edf and f116493.

📒 Files selected for processing (2)
  • MCPForUnity/Editor/Setup/RoslynInstaller.cs
  • MCPForUnity/Editor/Tools/ExecuteCode.cs

Comment thread MCPForUnity/Editor/Setup/RoslynInstaller.cs
Address PR review: file-existence alone treats a stale v4 of
System.Runtime.CompilerServices.Unsafe (or any other downgraded plugin) as
"installed" and silently masks the bootstrap failure this PR is meant to fix.

Read each on-disk DLL's manifest via AssemblyName.GetAssemblyName(...).Version
and compare to the NuGet entry's declared version; if the file is older than
declared or its manifest can't be read, return false so Install() rewrites it.
Applied uniformly to all entries (defense-in-depth, per the optional suggestion
in the review).

Notes:
- System.Version comparison handles "6.0.0" (Revision=-1) vs "6.0.0.0"
  (Revision=0) correctly: the higher-revision actual passes; a stale v4 fails.
- AssemblyName.GetAssemblyName only reads the manifest, no assembly load into
  the AppDomain, so this is cheap and side-effect-free.
- Validated locally: with the v6 DLL in Plugins/Roslyn/, IsInstalled returns
  true; manually swapping in a v4 placeholder causes it to return false and
  Install() proceeds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant