Issue #925: lowering: a String parameter returned unchanged on one path lowers the result to EitherText, != against String fails in rustc

Open and answer this issue on GitHub.

ready · opened 2026-10-28 · updated 2026-10-28 · 0 comments

Found by the converter repair after P4a (2026-10-28). The checker accepts the program; the generated Rust does not compile.

Probe

nikaia run main.nika (target/fast at 745584c):

fn swapped(line: String, from: String, to: String) -> String {
    if !from.contains("{") {
        let r: String = line.replace(from, to)
        if r != line {
            println("changed")
        }
        return r
    }
    if !from.ends_with("x") {
        return line
    }
    let mut s: String = line.clone()
    s.push_str(to)
    return s
}

fn all(lines: ref Vec[String], froms: ref Vec[String]) -> Vec[String] {
    let mut out: Vec[String] = []
    for l in lines {
        let mut line: String = l.clone()
        let mut k = 0
        while k < froms.len() {
            let from: String = froms[k].clone()
            k = k + 1
            line = swapped(line, from, "b")
        }
        out.push(line)
    }
    return out
}

fn count(lines: ref Vec[String]) -> i64 {
    return lines.len()
}

fn main() {
    let given: Vec[String] = ["abc", "xyz"]
    let froms: Vec[String] = ["a", "{x"]
    println(f"{count(all(given, froms))}")
}

rustc:

error: Can't compare `String` with `EitherText<'_>`.  (main.nika:4:9, `if r != line`)
error: Mismatched types.                              (main.nika:39:5)

Because return line hands the parameter back unchanged on one path, swapped’s result lowers as EitherText (ADR-282 D2), and so does the parameter where it is compared. That breaks r != line (no PartialEq<EitherText> for String), and the callers’ Vec[String] becomes Vec<EitherText>.

The workaround used in tools until now is return line.clone(). Per the owner’s direction (no workarounds), this is to be fixed in the lowering.

To build

  1. A program test tests/compiler/tests/either_text_compare.nika (the probe above, .stdout 2).
  2. In the lowering: comparing an EitherText with a String (either side) emits a comparison rustc accepts (.as_ref()/&* on both sides, or a PartialEq<EitherText> impl for String in the runtime), and a Vec[String] built from an EitherText result stays Vec<String> (into_owned at the push).
  3. Remove the .clone() workaround from tools, if a builder finds one (grep -n "return .*\.clone()" crates/nikaia-std/src/tools/*.nika around an unchanged parameter).

Back to Open Issues