# V2034 refined_field_assignment

URL: /docs/errors/V2034
Markdown: /docs/errors/V2034.md

> 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:

```sakura
type Pos = PosRepr where it.n > 0
var x = p
x.n = -1
```

Fixed:

```sakura
x = Pos.from(PosRepr { ..x, n: -1 })
x = Pos.validate(PosRepr { ..x, n: input })?
```

## Wrong program [#wrong-program]

[`compiler/tests/ui/V2034_refined_field_assignment.sakura`](https://github.com/icantcodefyi/sakuralang/blob/master/compiler/tests/ui/V2034_refined_field_assignment.sakura), which the compiler's ui suite runs through `sakura check`:

```sakura
// `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):

```json
{
  "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`](https://github.com/icantcodefyi/sakuralang/blob/master/compiler/tests/ui/V2034_refined_prefix_assignment.sakura).

## Fixed program [#fixed-program]

[`compiler/tests/ui/V2034_refined_field_assignment.fixed.sakura`](https://github.com/icantcodefyi/sakuralang/blob/master/compiler/tests/ui/V2034_refined_field_assignment.fixed.sakura), which `sakura check` accepts with no diagnostics:

```sakura
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
}
```
