Skip to content

[Bug]: "Create X tokens that are copies of …" — copy-token creation can't parse a variable (X) count #2536

Description

@philluiz2323

Summary

The copy-token parser's count helper, parse_copy_token_entry_modifiers (crates/engine/src/parser/oracle_effect/token.rs), accepts only "a" / "one" / a literal digit:

let (rest, count) = opt(alt((
    value(QuantityExpr::Fixed { value: 1 }, alt((tag("a "), tag("one ")))),
    map(nom_primitives::parse_number, |value| QuantityExpr::Fixed { value: value as i32 }),
)))
.parse(rest)?;

So "Create X tokens that are copies of …" fails to parse and the whole effect is dropped — the spell does nothing. The regular (non-copy) token path does not have this limitation: parse_token_count_prefix delegates to the shared parse_count_expr building block, which already handles "X", "twice X", "that many", "half X rounded up", etc. The copy-token path simply doesn't reuse it.

Steps to reproduce

use engine::parser::oracle_effect::token::try_parse_token;

// Variable count — BROKEN:
try_parse_token(
    "create x tokens that are copies of target creature you control",
    "Create X tokens that are copies of target creature you control",
    &mut ParseContext::default(),
);
// => None  (whole effect dropped)

// Literal count — works:
try_parse_token(
    "create two tokens that are copies of target creature you control",
    "Create two tokens that are copies of target creature you control",
    &mut ParseContext::default(),
);
// => Some(CopyTokenOf { target: Typed(Creature, You), count: Fixed(2), … })

The only difference is the count token (X vs two); the target creature you control parsing is already correct.

Real card examples (all currently dropped)

Card Oracle text
Rionya, Fire Dancer "{1}{R}, {T}: Create X tokens that are copies of another target creature you control, where X is …"
Devastating Onslaught "… then create X tokens that are copies of target artifact or creature you control, where X is the number of artifacts destroyed this way."
Nacatl War-Pride "… create X tokens that are copies of it and that are tapped and attacking, where X is the number of creatures defending player controls."
For the Common Good "… create X tokens that are copies of target token you control, where X is the number of Clues you control."
Aggressive Biomancy "Create X tokens that are copies of target creature you control, except …"
The Good Gamers "… create X tokens that are copies of it …"

(Cards using the "that many" phrasing — e.g. The Scarab God — go through parse_token_count_prefix and are unaffected; this gap is specific to the literal X/variable count in the copy-token path.)

Expected behavior

"Create X tokens that are copies of <target>" should parse to
Effect::CopyTokenOf { target, count: QuantityExpr::Ref { qty: QuantityRef::Variable { name: "X" } }, … },
with the X bound to the trailing "where X is <quantity>" clause (as the regular token path already does), so the spell creates the correct number of copies.

Actual behavior

parse_copy_token_entry_modifiers returns no count for X, the subsequent "tokens that are copies of" tag does not align, and try_parse_token returns None → the effect is Unimplemented and the spell resolves with no effect.

Comprehensive Rules

  • CR 707.2 / 707.9 — copying a permanent / token copies.
  • CR 107.3 — the value of X in a spell/ability (here defined by the "where X is …" clause).

Suggested fix (compose from building blocks)

In token.rs, make the copy-token count reuse the shared count grammar instead of the bespoke a/one/digit parser:

  1. Replace the inline count in parse_copy_token_entry_modifiers with parse_token_count_prefix / parse_count_expr (the same building block the non-copy path uses), so "X", "twice X", "that many", and multiplied/fractional counts all parse uniformly.
  2. In try_parse_token's copy branch, parse a trailing ", where X is <expr>" clause and bind the Variable("X") count to it via the existing bind_where_x_in_quantity_expr helper (mirroring how the regular Effect::Token path binds X for token P/T and counts).
  3. Preserve the existing tapped/attacking flag parsing and the except <body> clause handling.

Tests

  • try_parse_token("create x tokens that are copies of target creature you control", …)CopyTokenOf with a Variable("X") count.
  • A "where X is the number of …" card binds X to the correct QuantityRef.
  • Regression: literal-count ("two") and "a" copy-token forms unchanged.

Non-duplicate note

Rionya, Devastating Onslaught, Nacatl War-Pride, and Aggressive Biomancy return 0 open or closed issues/PRs. The existing "create X tokens that are copies" issues (#2169, #1735, #1424) concern runtime token-count doubling and X-resolution, not the parser's inability to recognize the X count — a distinct, parser-only gap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions