Skip to content

fix: static readonly fields of immutable types are not static state (0.3.1) - #6

Merged
IvanMurzak merged 3 commits into
mainfrom
fix/static-readonly-immutable
Oct 26, 2026
Merged

IvanMurzak merged 3 commits into
mainfrom
fix/static-readonly-immutable

Conversation

@IvanMurzak

Copy link
Copy Markdown
Owner

Problem

In 0.3.0 StaticState.Has treated every non-const static field as state, static readonly included. A snippet or include with a static readonly lookup table therefore loaded a fresh assembly on every run on a host that cannot unload (Mono / Unity): one leaked assembly per run until the next domain reload.

Fix

  • A static readonly field (or getter-only static auto-property) is not state when its declared type is immutable: string, primitives other than IntPtr/UIntPtr, enums, decimal, DateTime, DateTimeOffset, TimeSpan, Guid, nullables of these, and read-only / immutable collections (IReadOnly*, ReadOnlyCollection/Dictionary, System.Collections.Immutable.*) of immutable elements.
  • Arrays stay state. If the snippet itself implements or derives a collection view type, no view type is trusted.
  • spec/contract.md §3.5 documents the rule and its consequences (an initializer reading live host state keeps its first value on Mono). Changelog v0.3.1.
  • Version 0.3.1. The Python runtime is unchanged (still __version__ = "0.3.0"): python/code_exec has no diff.

Tests

  • StatefulSnippetsNeverSeeAnotherRunsStatics: 11 new cases (5 stateless lookup tables, 6 stateful readonly variants). Plant checks: disabling the exemption turns all 5 stateless cases red; exempting every readonly field turns the 3 mutable-readonly cases red; trusting views turns the snippet-implemented IReadOnlyList case red.
  • scripts/test-all.sh with Roslyn 3.8.0 + 5.9.0 (net8/net10, C# 9 SourceConsumer, pack + PackageConsumer, Python) green; net48 green (116).

Implement stages

  • simplify: Nullable.GetUnderlyingType, !IsNested for immutable collections, shorter docs.
  • code review (report-only agent): distrust snippet-implemented views, IntPtr is not immutable, live-state caveat in §3.5, three more tests.

🤖 Generated with Claude Code

IvanMurzak and others added 3 commits October 26, 2026 15:06
…0.3.1)

A snippet or include with a static readonly lookup table (string, primitive,
enum, read-only / immutable collection of those) loaded a fresh assembly on
every run under a host that cannot unload (Mono/Unity), leaking one assembly
per run until the next domain reload. StaticState.Has now ignores readonly
fields of immutable declared types. Arrays and mutable element types stay
state. Contract 3.5 documents the rule; version 0.3.1 (Python unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ions, one-line docs

Skipped: snippet-declared readonly structs as immutable (behaviour change, out of scope); foreach instead of All (no measurable gain).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… is not immutable, live-state caveat in 3.5

- A snippet type implementing / deriving a read-only or immutable collection type can keep state in its instance:
  then no view type counts as immutable (compiler-generated types excluded).
- IntPtr / UIntPtr point at mutable native memory: state.
- Spec 3.5: initializers reading live host state keep their first value on Mono; host objects behind a read-only type.
- Tests: getter-only mutable auto-property, IntPtr, a snippet-implemented IReadOnlyList.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IvanMurzak
IvanMurzak merged commit 96c2919 into main Oct 26, 2026
13 checks passed
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