V2034 refined_field_assignment
A field assignment changes a field of a var whose type, or the type of a prefix of the path, is a refined type (design §17.135).
An error from the type phase. sakura explain V2034 prints this explanation offline.
A field assignment changes a field of a var whose type, or the type of a prefix of the path,
is a refined type (design §17.135). x.n = v on var x is sugar for
x = PosRepr { ..x, n: v } (§8.1), and a record update yields the base type, so the statement
would put an unchecked PosRepr where a Pos is promised and skip the predicate. The rule
holds in every module, the declaring one included. Rebuild the value through the refinement:
Pos.from(…) for a value the program trusts (it is checked, and sakura audit lists the site)
or Pos.validate(…)? for data. Whole reassignment (x = next(x)) and a field assignment on a
plain record whose field is refined (r.total = Decimal.add(r.total, y)) are unaffected.
Wrong:
type Pos = PosRepr where it.n > 0
var x = p
x.n = -1Fixed:
x = Pos.from(PosRepr { ..x, n: -1 })
x = Pos.validate(PosRepr { ..x, n: input })?Wrong program
compiler/tests/ui/V2034_refined_field_assignment.sakura, which the compiler's ui suite runs through sakura check:
// `x.n = -1` is sugar for `x = PosRepr { ..x, n: -1 }`, a `PosRepr`, so it would hand back a
// `Pos` whose predicate never ran. Rebuild through `Pos.from` or `Pos.validate` instead.
type PosRepr = { n: Int }
type Pos = PosRepr where it.n > 0
fn f(p: Pos) -> Pos {
var x = p
x.n = -1
x
}sakura check --json reports this record (pretty-printed; $DIR is the fixture's directory):
{
"schema_version": "1.0",
"kind": "diagnostic",
"id": "d:18fe4b10b6b70ff1",
"code": "V2034",
"severity": "error",
"phase": "type",
"message": "`x.n = …` rebuilds `x` as `PosRepr`, but `x` is a `Pos`; a field assignment would skip its predicate. Rebuild it through `Pos.from(…)` (trusted, listed by `sakura audit`) or `Pos.validate(…)?` for data (design §17.135)",
"snapshot": "sha256:$SNAPSHOT",
"symbol_id": "main::V2034_refined_field_assignment::f",
"spans": [
{
"uri": "file://$DIR/V2034_refined_field_assignment.sakura",
"content_hash": "sha256:72c19717d1973e6fd701c2fb3bdd48334ff9476cb8e0e79dffd11ed9bfa3317c",
"start_byte": 349,
"end_byte": 357,
"role": "primary"
},
{
"uri": "file://$DIR/V2034_refined_field_assignment.sakura",
"content_hash": "sha256:72c19717d1973e6fd701c2fb3bdd48334ff9476cb8e0e79dffd11ed9bfa3317c",
"start_byte": 283,
"end_byte": 286,
"role": "definition",
"label": "`Pos` is a refined type"
}
],
"related_ids": [],
"expected": {
"render": "Pos"
},
"actual": null,
"obligation": null,
"counterexample": null,
"explanation_uri": "sakura://explain/V2034",
"fixes": [
{
"id": "f:18fe4b10b6b70ff1.0",
"title": "Rebuild `x` through `Pos.from`",
"applicability": "suggested",
"impact": "local",
"edits": [
{
"span": {
"uri": "file://$DIR/V2034_refined_field_assignment.sakura",
"content_hash": "sha256:72c19717d1973e6fd701c2fb3bdd48334ff9476cb8e0e79dffd11ed9bfa3317c",
"start_byte": 349,
"end_byte": 355,
"role": "primary"
},
"replacement": "x = Pos.from(PosRepr { ..x, n: "
},
{
"span": {
"uri": "file://$DIR/V2034_refined_field_assignment.sakura",
"content_hash": "sha256:72c19717d1973e6fd701c2fb3bdd48334ff9476cb8e0e79dffd11ed9bfa3317c",
"start_byte": 357,
"end_byte": 357,
"role": "primary"
},
"replacement": " })"
}
]
}
]
}More fixtures for this code: compiler/tests/ui/V2034_refined_prefix_assignment.sakura.
Fixed program
compiler/tests/ui/V2034_refined_field_assignment.fixed.sakura, which sakura check accepts with no diagnostics:
type PosRepr = { n: Int }
type Pos = PosRepr where it.n > 0
fn f(p: Pos) -> Pos {
var x = p
x = Pos.from(PosRepr { ..x, n: 1 })
x
}V2033 opaque_not_schema
A Schema bound (Json.decode, Sql.query rows, Llm.generate, Cli.parse, Tool.of arguments, a property parameter) reaches an opaque type outside its declaring module, directly or through a field (design §17.130).
V2040 unused_value
A non-final expression statement produces a non-Unit value that is dropped.