Sakura Lang

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 = -1

Fixed:

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
}
Edit on GitHub

On this page