# Adding / extending stdlib modules

Guidance for the Python stdlib modules implemented in this directory
(`re`, `json`, `math`, `datetime`, `unicodedata`, …). The overriding goal is
**CPython parity**: signatures, error messages, and error *ordering* must
match CPython 3.14 byte-for-byte. Verify every message against CPython with
the `/python-playground` skill before pinning it in a test.

## Always use `#[derive(FromArgs)]` for function parameters

Any function beyond the trivial 0/1/2-positional shapes (covered by
`ArgValues::check_zero_args` / `get_one_arg` / `get_two_args` /
`get_zero_one_arg` / `into_pos_only`) MUST define its parameters with a
`#[derive(FromArgs)]` struct. Never hand-roll `args.into_parts()` loops —
they leak refcounts and diverge from CPython's wording. The derive emits a
static param spec driven by the runtime binder
(`crate::args::bind_native`), which handles dispatch, arity errors, kwarg
matching, duplicate/conflict detection, and refcount cleanup mechanically.

Full attribute reference: `crates/monty-macros/README.md` (including the
CPython-family table and the `at_most_total` litmus test).

### 1. Pick the `style` by how CPython implements the function

| CPython implementation | `style` |
|---|---|
| pure-Python `def` (the `re` functions, `json.dumps`) | `style = def` |
| Argument Clinic (most modern C builtins/methods) | default — omit `style` |
| `PyArg_ParseTupleAndKeywords`, anonymous `function` errors | `style = c` |
| same, with the name embedded (`timezone() missing …`) | `style = c_named` |
| `PyArg_UnpackTuple` (positional-only, `min..max` arity, kwargs rejected wholesale with `takes no keyword arguments`) | `style = unpack` |
| `tp_vectorcall` fast path in front of a clinic parser (`int`, `str`: kwarg-free overflow says `int expected at most 2 arguments, got 3`, with kwargs `int() takes at most 2 arguments (3 given)`) | default style + `at_most_total, vectorcall` |

The style controls wording *and* ordering (e.g. C families report leftover
kwargs last; clinic/def bind fully before any conversion). When unsure, probe
CPython: call the function with a bad type + a bogus kwarg, too many args,
etc., and match the observed messages.

### 2. Typical shapes (real examples from this directory)

A pure-Python `def` — fields stay raw `Value` because CPython `def` binding
never type-checks; coerce in the body (`re.rs`):

```rust
#[derive(FromArgs)]
#[from_args(name = "search", style = def)]
struct ReSearchArgs {
    #[from_args(static_string = "PatternAttr")]
    pattern: Value,
    #[from_args(static_string = "StringAttr")]
    string: Value,
    #[from_args(default = Value::Int(0))]
    flags: Value,
}
```

A clinic-style function with keyword-only params (`math.rs`):

```rust
#[derive(FromArgs)]
#[from_args(name = "isclose")]
struct IscloseArgs {
    a: Value,
    b: Value,
    #[from_args(kw_only, default = Value::Float(1e-9))]
    rel_tol: Value,
    #[from_args(kw_only, default = Value::Float(0.0))]
    abs_tol: Value,
}
```

A positional-only C function with typed extraction (`unicodedata.rs`) —
`StrArg` borrows the text zero-copy. Note the form *name* is validated in the
body (`NormForm::parse`), not during extraction: CPython type-checks every
argument before the body rejects a bad value, so value checks must not run
inside `FromValue`:

```rust
#[derive(FromArgs)]
#[from_args(name = "normalize", style = unpack, bad_arg)]
struct NormalizeArgs {
    #[from_args(pos_only)]
    form: StrArg,
    #[from_args(pos_only)]
    unistr: StrArg,
}
```

Calling and consuming — sketch; see `unicodedata.rs` for the real body (note
`defer_drop!` for fields that hold heap refs):

```rust
fn call_normalize(vm: &mut VM<'_>, args: ArgValues) -> RunResult<Value> {
    let NormalizeArgs { form, unistr } = NormalizeArgs::from_args(args, vm)?;
    defer_drop!(unistr, vm);
    let normalized = form.apply(unistr.as_str(vm)); // sketch — do the work here
    // ... allocate and return the result; the guard releases `unistr`
}
```

### 3. Field rules

- Declaration order = Python signature order:
  `[pos_only…] [pos_or_keyword…] [varargs] [kw_only…] [varkwargs]`, required
  fields before defaulted ones in the positional region.
- **Typed fields** (`i64`, `i32`, `bool`, `StrArg`, `Option<T>`, custom
  `FromValue` impls) are only for functions implemented in C in CPython —
  pair with `bad_arg` / `bad_arg_named` when CPython uses
  `_PyArg_BadArgument` wording (`f() argument 1 must be str, not int`).
- **`style = def` fields must be raw `Value`** (coerce in the body so the
  error message matches what CPython's function body raises).
- Prefer `StrArg` over `String` for str params the function only reads — it
  validates without copying and lends `&str` via `.as_str(vm)`.
- Field names that aren't single ASCII chars need a `StaticStrings` variant
  (in `crate::intern`) for kwarg matching — add one, or point
  `static_string = "ExistingVariant"` at an existing one.
- `Value` / `StrArg` / `Option<Value>` fields hold heap references: bind
  them with `defer_drop!` in the body, or otherwise guarantee
  `drop_with` on every path.
- `varargs` fields must be `Vec<Value>`; convert elements in the body.

### 4. Tests and docs

- Add behaviour tests to `crates/monty/test_cases/` (dual-run against
  CPython — asserts must pass on both engines; exact `==` messages, never
  `in`). Signature-error orderings belong in `args__macro_errors.py`.
- Document every remaining CPython divergence in `./limitations/<module>.md`
  — including "obvious" ones.
- Run `make test-cases`, `make format-rs`, `make lint-rs` (and
  `make lint-py` for test changes) before finishing.
