From f6bef97f06f92354cdde0a382a52184cfae3aa2e Mon Sep 17 00:00:00 2001 From: Rodolfo Hansen Date: Fri, 18 Sep 2026 14:48:39 +0200 Subject: [PATCH] fix(kyo): brace the valuePrism extension so stryker4s can mutate the module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `sbt mutationAll` aborted on `project kyoIntegration; stryker` with UnableToFixCompilerErrorsException before scoring a single mutant — on an unmodified tree. The module has therefore never appeared in the QA page. stryker4s 0.20.3 re-prints every file it mutates through scalameta. A single-method significant-indentation `extension` clause is not a `Term.Block`, so the printer emits the one-line form `extension (…) def f = …`; the method's leading Scaladoc is replayed verbatim between the two, forcing a newline that lands `def valuePrism` in column 0. The clause is left with no extension method ('Extension without extension methods') and `A` / `self` fall out of scope ('Not found: type A', 'Not found: self'), so the file stops compiling. Because the breakage is structural rather than a bad mutant, stryker's mutant-removal recovery has nothing to remove and the whole module run dies. Bracing the extension body makes the body a `Term.Block`, the printer emits `extension (…) { … }`, and the newline before `def` is harmless. Hoisting the Scaladoc above `extension` also works — it is why the avro / circe / jsoniter clauses have never tripped — but it detaches a substantial doc comment from the method it documents, so braces win. Semantically identical: `javap -p -c` over the extension's owner classes is byte-for-byte unchanged, and a full `javap -v` diff shows only the +13 LineNumberTable shift from the added comment plus the TASTY checksum. The module's suites pass unchanged. kyoIntegration now scores: 20 Killed / 4 Survived / 28 NoCoverage / 15 Ignored — 38.46% total, 83.33% of covered code. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_0194EHFR4NamCpTHiqy7B74V --- .../eo/kyo/schema/StructureOptics.scala | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/kyo/src/main/scala/dev/constructive/eo/kyo/schema/StructureOptics.scala b/kyo/src/main/scala/dev/constructive/eo/kyo/schema/StructureOptics.scala index 06394cdd..eeb0f90d 100644 --- a/kyo/src/main/scala/dev/constructive/eo/kyo/schema/StructureOptics.scala +++ b/kyo/src/main/scala/dev/constructive/eo/kyo/schema/StructureOptics.scala @@ -197,7 +197,24 @@ object StructureValues: end StructureValues -extension [A](self: Schema[A]) +// Braced body, against the significant-indentation house style, on purpose. +// stryker4s 0.20.3 re-prints every mutated file through scalameta, and a +// SINGLE-METHOD significant-indentation `extension` clause comes back as the +// one-line form `extension [A](self: Schema[A]) def valuePrism …`. The method's +// leading Scaladoc is replayed verbatim between the two, which forces a newline +// and lands `def` in column 0: the clause is left with no extension method +// ('Extension without extension methods') and `A` / `self` fall out of scope, so +// the file stops compiling and `project kyoIntegration; stryker` aborts with +// UnableToFixCompilerErrorsException before it can score anything. With braces +// the printer emits `extension (…) { … }` and the newline is harmless. +// +// Hoisting the Scaladoc above `extension` also dodges it (that is why the avro / +// circe / jsoniter clauses have never tripped), but it detaches the doc from the +// method it documents. Braces keep the doc where it belongs and are robust to +// anything else that might force a newline. Verified bytecode-identical: the +// only class-file deltas are the LineNumberTable shift and the TASTY checksum. +// Revert when stryker4s fixes the re-print. +extension [A](self: Schema[A]) { /** Prism between the untyped `Structure.Value` tree and `A` — the typed ↔ untyped face beside * [[prism(Schema)]] (bytes) and [[stringPrism(Schema)]] (String), built on the public @@ -215,3 +232,5 @@ extension [A](self: Schema[A]) v => Structure.decode[A](v).foldError(Right(_), _ => Left(v)), a => Structure.encode(a), ) + +}