Skip to content

Adapt the type-assertion recipes to Go.TypeAssertion - #64

Merged
knutwannheden merged 4 commits into
mainfrom
lucky-ferret
Aug 18, 2026
Merged

Adapt the type-assertion recipes to Go.TypeAssertion#64
knutwannheden merged 4 commits into
mainfrom
lucky-ferret

Conversation

@knutwannheden

@knutwannheden knutwannheden commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Motivation

  • openrewrite/rewrite#8517 hardens the Go parser and printer, and two of its changes are visible to recipes. x.(T) is modeled as Go.TypeAssertion rather than J.TypeCast, because a prefix cast has nowhere to hold what stands between the expression and the dot. And an if/switch init clause's ; is emitted from a Semicolon marker, since a line break can stand in for the written ;.

  • Every recipe that reached for a type assertion silently stopped matching, and the one recipe that synthesizes an if-init printed invalid Go. Against rewrite-go at that commit, five tests fail; against the published v0.0.29 and against the commit right before #8517 they pass, so #8517 is the whole of it.

Examples

The two shapes a recipe now works with:

// x.(T) — Go.TypeAssertion, whose Left is right-padded and whose
// AssertedType holds the (T)
ta, ok := expr.(*golang.TypeAssertion)
inner := ta.AssertedType.Tree.Element
asserted := ta.Left.Element

// if <init>; <cond> — the `;` rides the init clause's markers
Init: java.RightPadded[java.Statement]{
    Element: initAssign,
    Markers: java.Markers{ID: uuid.New(), Entries: []java.Marker{golang.NewSemicolon()}},
}

Summary

  • RemoveRedundantInterfaceAssertion, UseCommaOkTypeAssertion and UseErrorsAs match *golang.TypeAssertion, reading the asserted expression from Left.Element and the type from AssertedType.
  • RemoveRedundantSprintf carries the prefix over to a *golang.TypeAssertion argument, the kind fmt.Sprintf("%s", x.(string)) presents.
  • CheckTemplateExecuteError attaches a Semicolon marker to the init clause of the if it builds.
  • Requires rewrite-go/v0.0.30.

Test plan

  • go test ./... -count=1 green across all ten packages against rewrite-go at openrewrite/rewrite@f97e158cfc (via a local replace).
  • Confirmed the five failures are regressions from #8517: the same packages pass against v0.0.29 and against openrewrite/rewrite@e74960016b, the commit before it.
  • Re-run against the published rewrite-go/v0.0.30: green across all eleven packages.

One more break, from a later commit in the same release

v0.0.30 was tagged at cad175a62, five rewrite-go commits past the one this branch was verified against, and one of them broke two encoding/json/v2 tests. openrewrite/rewrite#8521 makes RemoveImport keep an import the file still references.

The check was right and the tree was wrong. RelocateRawMessage rewrites the only json.RawMessage in a file to jsontext.Value, but the nodes kept the attribution they were given at parse:

identifier "Raw"  type=encoding/json.RawMessage     // source now reads: Raw jsontext.Value

So the reference check saw a live use of encoding/json and correctly declined to remove the import, and the file came out importing both packages. Setting Force would have silenced that by switching off exactly the safety the flag exists to preserve.

Instead the rewrite now says what it means: the type expression and the field declaration carry encoding/json/jsontext.Value, and the conversion call drops a method type that no longer describes it. The import is then removed by the default, unforced path, because nothing claims to use it.

Worth carrying forward: a recipe that rewrites away the last use of a package has to re-attribute the nodes it touched, or the import survives. Nil-ing the type on the node being replaced is not enough — the enclosing declaration holds one too.

Test plan

  • go test ./... -count=1 green across all ten packages against rewrite-go at openrewrite/rewrite@f97e158cfc (via a local replace).
  • Confirmed the five failures are regressions from #8517: the same packages pass against v0.0.29 and against openrewrite/rewrite@e74960016b, the commit before it.
  • Re-run against the published rewrite-go/v0.0.30: green across all eleven packages.

One more break, from a later commit in the same release

v0.0.30 was tagged at cad175a62, five rewrite-go commits past the one this branch was verified against, and one of them broke two encoding/json/v2 tests. openrewrite/rewrite#8521 made RemoveImport conditional: an import the file still references survives unless Force is set.

RelocateRawMessage rewrites the only json.RawMessage in a file to jsontext.Value and then drops the now-dead import. It nils the type on the nodes it rewrites, but the struct field it rewrote keeps its attributed encoding/json.RawMessage type, and the reference check reads that as a live use — so the import stayed and the file came out with both encoding/json and encoding/json/jsontext.

The recipe already runs a scan that establishes RawMessage is the only usage; that scan is what decides the import is dead, so the removal now says so with Force: true. Worth noting the general shape: after any rewrite the LST's type attribution is stale, so a reference check driven by types will over-report a package as used. Recipes that rewrite away the last use of an import and know it will need Force.

openrewrite/rewrite#8517 models `x.(T)` as `Go.TypeAssertion` instead of
`J.TypeCast`, so the four recipes that reached for a type assertion stopped
matching and made no change. The same PR emits an `if` init clause's `;`
from a `Semicolon` marker, which a recipe that synthesizes such an `if` has
to attach itself; without it `CheckTemplateExecuteError` printed
`if err := f() err != nil`.

This requires `rewrite-go/v0.0.30`, which is not yet published.
@knutwannheden
knutwannheden marked this pull request as ready for review August 18, 2026 10:16
…ewrites

`go.sum` gains the entry for the published `rewrite-go/v0.0.30`.

That release also carries openrewrite/rewrite#8521, which makes `RemoveImport`
keep an import the file still references. `RelocateRawMessage` rewrites the
only `json.RawMessage` in a file to `jsontext.Value` and then drops the import,
but the field, the type expression and the conversion call all still carried
the `encoding/json` attribution they were given at parse. The reference check
read that as a live use and kept the import, so the file came out importing
both packages.

The rewritten nodes now carry `encoding/json/jsontext.Value`, which is what the
source says once the rewrite has run, and the conversion drops a method type
that no longer describes the call it is attached to. The import goes because
nothing claims to use it.
@knutwannheden
knutwannheden force-pushed the lucky-ferret branch 2 times, most recently from a408833 to 1520507 Compare August 18, 2026 10:36
@knutwannheden
knutwannheden merged commit 08faa88 into main Aug 18, 2026
2 of 4 checks passed
@knutwannheden
knutwannheden deleted the lucky-ferret branch August 18, 2026 10:42
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