fix: Handle execute-dynamic-code transpiler constraints for static locals and byte casts

Merged
HA
3mo ago
+701-3

Summary

Address execute-dynamic-code transpiler constraints (B-9):

  1. CS8421 / static local functions: Fix literal hoister to keep literals inline inside static local function bodies (top-level and nested; expression-bodied and block-bodied).
  2. CS8820 / static lambdas: Add Hint + SKILL.md guidance (hoister fix out of scope; remove static from lambda).
  3. CS1503 / Color32 byte components: Document as known constraint; add compilation Hint + Suggestions and SKILL.md guidance for explicit (byte) casts.

Follow-up (separate PR 3-5, before Group 3 integration): Color32 repro Line=1 / caret on using line is a PR 3-3 lineage bug (using extraction shrinks #line region while Context uses original lines). Track in PR 3-5 with placeholder-line or line-offset mapping.

Pre-implementation verification

Repro 1a: nested static local (before fix)

Command:

uloop execute-dynamic-code --code $'int Compute(int value)\n{\n    static int Double(int x) => x * 2;\n    return Double(value);\n}\nreturn Compute(3);'

Raw Diagnostics[0] (before fix):

{
  "Message": "CS8421: A static local function cannot contain a reference to '__uloop_literal_0'.",
  "Line": 3,
  "Column": 37,
  "ErrorCode": "CS8421",
  "Hint": "",
  "Suggestions": [],
  "Context": "L1:int Compute(int value)\nL2:{\nL3:    static int Double(int x) => x * 2;\n                                       ^\nL4:    return Double(value);\nL5:}\nL6:return Compute(3);\n",
  "PointerColumn": 37
}

Repro 1b: top-level static local — B-9 primary case (before fix)

Command:

uloop execute-dynamic-code --code $'static int Double(int x) => x * 2;\nreturn Double(3);'

Raw Diagnostics[0] (before fix; _braceDepth < 1 guard blocked hoister scope):

{
  "Message": "CS8421: A static local function cannot contain a reference to '__uloop_literal_0'.",
  "Line": 1,
  "Column": 37,
  "ErrorCode": "CS8421",
  "Hint": "",
  "Suggestions": [],
  "Context": "L1:static int Double(int x) => x * 2;\n                                       ^\nL2:return Double(3);\n",
  "PointerColumn": 37
}

Investigation: Integer literals inside static local bodies were hoisted to __uloop_literal_N. Top-level static locals were missed because LiteralHoistScopeTracker required _braceDepth >= 1.

Decision: Fix — remove depth guard; keep literals inline inside recognized static local headers. CS8421 Hint/Suggestion targets remaining scanner gaps (remove static).

Repro 2: Color32 int literals (constraint)

Command:

uloop execute-dynamic-code --code $'using UnityEngine;\nreturn new Color32(255, 0, 0, 255);'

Raw Diagnostics[0] (before fix, no Hint):

{
  "Message": "CS1503: Argument 1: cannot convert from 'int' to 'byte'",
  "Line": 1,
  "Column": 20,
  "ErrorCode": "CS1503",
  "Hint": "",
  "Suggestions": [],
  "Context": "L1:using UnityEngine;\n                      ^\nL2:return new Color32(255, 0, 0, 255);\n",
  "PointerColumn": 20
}

Plain C# note: new Color32(255, 0, 0, 255) compiles in normal Unity scripts because numeric literals are compile-time constants with implicit narrowing to byte. Literal hoisting promotes them to int parameters.

Decision: Known constraint; Hint + SKILL.md + explicit cast workaround. Line/caret mismatch deferred to PR 3-5.

Repro 3: static lambda CS8820 (constraint + Hint)

Command:

uloop execute-dynamic-code --code $'System.Func<int,int> f = static x => x * 2;\nreturn f(3);'

Raw Diagnostics[0] (after Hint fix):

{
  "Message": "CS8820: A static anonymous function cannot contain a reference to '__uloop_literal_0'.",
  "Line": 1,
  "Column": 42,
  "ErrorCode": "CS8820",
  "Hint": "Static lambdas cannot reference hoisted literal parameters. Remove the `static` modifier from the lambda, or use a non-static local function instead.",
  "Suggestions": [
    "Remove the `static` modifier from the lambda."
  ],
  "Context": "L1:System.Func<int,int> f = static x => x * 2;\n                                            ^\nL2:return f(3);\n",
  "PointerColumn": 42
}

Convention checklist

  • No overloads added
  • No try-catch added
  • No out/ref in new APIs (tuple return for hints)
  • Additive only; no protocol bump
  • Source SKILL.md edited; .claude/ / .agents/ regenerated via uloop skills install (byte-identical)
  • All test methods have what-comments; English commit/PR

Implementation

  • LiteralHoistScopeTracker: remove _braceDepth < 1 guard; suppress hoisting inside recognized static local bodies
  • StaticLocalFunctionHeaderScanner: document unsupported header shapes (where, tuple returns, statement lambdas in expression bodies)
  • DynamicCodeLiteralHoister.LiteralParameterPrefix: shared internal constant
  • DynamicCodeTranspilerConstraintHints: CS8421 / CS8820 / CS1503 guidance via tuple return
  • Skill/SKILL.md: updated Known transpiler constraints

Verification

  • dist/darwin-arm64/uloop compile — 0 errors / 0 warnings
  • DynamicCodeLiteralHoisterTests + DynamicCodeTranspilerConstraintHintsTests + DynamicCodeExecutionResponseFactoryTests — 15 passed

Post-fix repro 1b (top-level static local — B-9 primary)

{
  "Success": true,
  "Result": "6",
  "Logs": ["Execution completed successfully"],
  "CompilationErrors": [],
  "ErrorMessage": "",
  "Diagnostics": []
}

Post-fix repro 2 (Color32 — constraint with Hint)

Raw Diagnostics[0]:

{
  "Message": "CS1503: Argument 1: cannot convert from 'int' to 'byte'",
  "Line": 1,
  "Column": 20,
  "ErrorCode": "CS1503",
  "Hint": "Literal hoisting promotes integer literals to int values. Unity APIs such as Color32 expect byte components and do not accept implicit int-to-byte conversion after hoisting, even though plain numeric literals compile in normal Unity scripts.",
  "Suggestions": [
    "Cast each component explicitly, for example: new Color32((byte)255, (byte)0, (byte)0, (byte)255)."
  ],
  "Context": "L1:using UnityEngine;\n                      ^\nL2:return new Color32(255, 0, 0, 255);\n",
  "PointerColumn": 20
}

Workaround verified:

{
  "Success": true,
  "Result": "RGBA(255, 0, 0, 255)",
  "Logs": ["Execution completed successfully"]
}