typeimmutable

package
v0.0.0-...-239813c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package typeimmutable is a nogo analyzer that enforces the immutability RFC-234 relies on: a `values.Type` graph may be written only by the function that built it.

Why this has to be a build-time gate rather than a test

`ExactTypeHandle.Type`, `QuantifiedObjectValue.FlowedType` and `fieldValue.Type` return the thawed graph CACHED ON AN INTERNED HANDLE. They used to return a fresh copy per call, and that copy was the largest single object allocation in the planner — 175.7M objects per sweep, of which those three accessors were 73%. Sharing removed 128.9M of them.

What the copy bought was a permission nothing in production used: a census over 103 packages found 21 writes into a Type field and every one is into a graph its own function had just constructed. But "nothing does this today" is not a property, it is an observation, and the first write to a shared graph does not corrupt one caller's copy — it corrupts every value flowing that shape, process-wide, because the graph hangs off an INTERNED node. That failure is silent, order-dependent, and crosses parallel tests.

Why it covers tests

Because that is where the mutation actually lives. Deduped by position, 60 of the 81 mutation sites in this repo are in `_test.go`, and the test side writes seven fields production never touches at all (`RecordName`, `Fields`, `Nullable`, `TypeCode`, `ElementType`, `InnerType`, `PrimitiveType.Nullable`). A gate scoped to production sources would have watched the half that was already clean. It is not hypothetical either: flipping the accessors with the old tests in place made ONE test's mutation reach TWO unrelated tests through the intern table.

The rule, and what it does NOT catch

The not-covered list first, because a gate's scope sentence written from the code it just matched is how these over-claim.

  • A write reached through a function CALL is invisible: `mutate(rt)` where `mutate` writes `rt.Nullable` is diagnosed at `mutate`'s own body if that body is in an analyzed package, and not at the call site. Interprocedural provenance is not attempted.
  • A write through a value stored in a container (`m[k].Fields[i].Name = …` via a map of pointers, a slice of `*RecordType`) resolves its root to the container, not to the graph, and is refused only if the container itself is not local — which is stricter than the truth, not looser.
  • Reflection, `unsafe`, and `encoding/gob`-style rehydration are not modelled at all.
  • Generated code excluded via nogo_config, as for every analyzer here.

What it DOES catch, by shape:

  1. a write to a field of a Type reached from a PARAMETER, a call result, a receiver's field, or a package-level var — anything the function did not build;
  2. a write THROUGH a reference (a slice index or a pointer deref) where the reference was not itself locally constructed — which is the shallow copy-on-write hazard: `withLegs := *record` copies the struct header and SHARES `Fields`, so `withLegs.Fields[i].Name = …` writes the interned graph while looking local;
  3. `++`/`--`, which is `*ast.IncDecStmt` and not an assignment;
  4. `&t.Field`, which hands a writable pointer out of the function.

A write to a direct field of a locally-copied struct (`withLegs.Legs = legs`) is ALLOWED: that writes the copy's own header and is the sanctioned copy-on-write form.

Index

Constants

This section is empty.

Variables

View Source
var Allowlist = map[string]map[string]string{
	"pkg/recordlayer/query/plan/cascades/values/qov_source_layout.go": {

		"restoreQOVRecordLayout": "writes a parameter; sole external caller passes a private thaw()",
	},
	"pkg/recordlayer/query/plan/cascades/values/ordinal_layout_test.go": {

		"TestOrdinalLayoutSnapshotsEveryMutableInputAndGetter": "mutates its own fixture inputs to prove the snapshot is isolated",
	},
	"pkg/recordlayer/query/plan/cascades/plan_leg_concat_layout_test.go": {

		"TestPlanLegConcatLayout_ReturnsADefensiveCopy": "writes the returned layout to prove it is a copy; the write is the assertion",
	},
	"pkg/relational/core/query/inline_values_translation_test.go": {

		"TestInlineValuesTranslationRejectsCollectionTypeDrift": "mutates a collection graph to simulate drift; declining is the assertion",
	},
	"pkg/recordlayer/query/plan/cascades/values/proto_field_exact_type_test.go": {

		"TestFieldTypeForProtoFieldReturnsIndependentTypeGraphs": "asserts the callee's freshness contract; the mutation IS the test",
	},
}

Allowlist maps a repo-relative file path (matched as a path suffix) to the function names within it that are permitted to write a Type they did not build. Every entry is a deliberate contract that must be stated at the site.

View Source
var Analyzer = newAnalyzer(Allowlist)

Analyzer is the nogo entry point.

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL