Skip to content

Autoharness: support generic functions - #4679

Merged
feliperodri merged 4 commits into
model-checking:mainfrom
tautschnig:autoharness-generics
Aug 21, 2026
Merged

Autoharness: support generic functions#4679
feliperodri merged 4 commits into
model-checking:mainfrom
tautschnig:autoharness-generics

Conversation

@tautschnig

Copy link
Copy Markdown
Member

Description

Autoharness now generates harnesses for generic functions. For each generic function, it verifies a single monomorphic instantiation: every type parameter is substituted with the first candidate from a fixed list of primitive types (i32, u32, usize, bool, char) such that all of the function's trait bounds are satisfied — checked properly with the trait solver (rustc_trait_selection::ObligationCtxt) — and lifetime parameters are erased. Functions whose bounds no candidate satisfies (e.g. bounds on user traits with no primitive impls) and functions with const generic parameters are still skipped with the existing "Generic Function" reason.

This handles arbitrary numbers of generic parameters, methods of generic impl blocks (whose impl-level parameters are instantiated too), impl Trait argument position (anonymous type parameters), and generic functions with contracts. For contract harnesses, the harness metadata now stores the definition-level name of the target function rather than the instantiated one, since gen_contracts_metadata matches target_fn against definition-level ContractedFunction names (this previously had no observable effect because generic functions never reached that code path).

Design note on soundness of the reported result: verifying foo::<i32> is an underapproximation of foo's behaviors for all instantiations. The summary table makes this explicit by displaying the instantiated name (foo::<i32>), and the documentation spells out that a successful result covers only the chosen instantiation. Verifying one instantiation per function (rather than a set) keeps verification cost proportional to crate size; extending this to user-configurable instantiation sets is possible follow-up work.

Testing

New script-based test cargo_autoharness_generics covering: unbounded and bounded type parameters, bound-driven candidate selection (impl Into<u64> + Copy correctly skips i32 and picks u32), multiple type parameters, lifetimes, methods of generic impl blocks, a generic function with a contract (verified as proof_for_contract), a genuine bug found at the chosen instantiation (arithmetic overflow), and graceful skipping of unsatisfiable bounds and const generics.

Updated cargo_autoharness_filter/include/exclude tests, whose generic example functions are now instantiated and verified instead of skipped.

Manually verified no regressions in all autoharness and autoderive script-based tests and kani-compiler unit tests.

Note: the two functions previously used to exercise multi-parameter generics in a prototype branch ICE'd the compiler ("type parameter U/#1 out of range"); both are covered by the new test and verify correctly.

Towards #3832 (ticks the "Generics" box, the last unchecked entry together with #4677 and #4678).

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

@tautschnig
tautschnig requested a review from a team as a code owner July 28, 2026 01:06
Copilot AI review requested due to automatic review settings July 28, 2026 01:06
@github-actions github-actions Bot added Z-EndToEndBenchCI Tag a PR to run benchmark CI Z-CompilerBenchCI Tag a PR to run benchmark CI labels Jul 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for generating automatic harnesses for generic functions by selecting a single monomorphic instantiation per function (based on a fixed primitive candidate list and trait-bound satisfaction via the trait solver), and updates related metadata, docs, and script-based tests to reflect the new behavior.

Changes:

  • Instantiate generic functions for autoharness using primitive candidates and trait-solver checking; erase lifetimes and keep skipping const generics/unsatisfiable bounds.
  • Fix contract-harness metadata to store the definition-level target function name (to match contract metadata lookup behavior).
  • Add/refresh script-based tests and documentation for generic-function autoharness behavior and output.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/script-based-pre/cargo_autoharness_include/src/lib.rs Updates include test source comment to reflect generic instantiation behavior.
tests/script-based-pre/cargo_autoharness_include/include.expected Updates expected output to include instantiated generic function verification.
tests/script-based-pre/cargo_autoharness_generics/src/lib.rs Adds new script-based test crate covering generic instantiation cases and edge cases.
tests/script-based-pre/cargo_autoharness_generics/generics.sh Adds script to run cargo kani autoharness with contracts enabled for the new test.
tests/script-based-pre/cargo_autoharness_generics/generics.expected Adds expected summary lines for generic instantiation successes/failure and skips.
tests/script-based-pre/cargo_autoharness_generics/config.yml Registers the new script-based test (expects failure exit code due to intentional bug).
tests/script-based-pre/cargo_autoharness_generics/Cargo.toml Adds manifest for the new test crate.
tests/script-based-pre/cargo_autoharness_filter/src/lib.rs Adjusts filter test inputs to include a generic function now expected to be harnessed.
tests/script-based-pre/cargo_autoharness_filter/filter.expected Updates expected output counts and entries to include instantiated generic function.
tests/script-based-pre/cargo_autoharness_exclude/src/lib.rs Updates exclude test source comment to reflect generic instantiation behavior.
tests/script-based-pre/cargo_autoharness_exclude/exclude.expected Updates expected output to include instantiated generic function verification.
kani-compiler/src/main.rs Adds rustc_infer / rustc_trait_selection extern crates required for trait-solver usage.
kani-compiler/src/kani_middle/metadata.rs Switches contract harness metadata to use definition-level target function names.
kani-compiler/src/kani_middle/codegen_units.rs Implements generic instantiation candidate selection and predicate checking for autoharness eligibility.
docs/src/reference/experimental/autoharness.md Documents the new “single-instantiation” behavior and its underapproximation implications.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread kani-compiler/src/kani_middle/codegen_units.rs Outdated
tautschnig and others added 4 commits August 21, 2026 20:33
Previously, autoharness skipped all generic functions. Now, it generates a
harness for a single monomorphic instantiation: each type parameter is
substituted with the first candidate from a fixed list of primitive types
(i32, u32, usize, bool, char) such that all of the function's trait bounds
are satisfied, checked with the trait solver
(rustc_trait_selection::ObligationCtxt). Lifetime parameters are erased.
Functions whose bounds no candidate satisfies, or with const generic
parameters, are still skipped as 'Generic Function'.

The generated harness's name reflects the chosen instantiation (e.g.
foo::<i32>), making explicit that verification covers only that
instantiation; the documentation spells out this underapproximation.
Functions with any number of type, lifetime, and (unsupported) const
parameters are handled, including methods of generic impl blocks, impl-Trait
arguments, and functions with contracts. For contract harnesses, harness
metadata now stores the definition-level name of the target function rather
than the instantiated one, since gen_contracts_metadata matches it against
definition-level ContractedFunction names.

This addresses the 'Generics' item of the automatic harness generation
tracking issue, the last unchecked entry together with the invariants and
pointers work.

Towards model-checking#3832

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Signed-off-by: Felipe Monteiro <felisous@amazon.com>
Rather than the single 'Generic Function' skip reason, attach a detail
explaining what prevented instantiation: const generic parameters, or that
no candidate type satisfies the function's trait bounds. This makes the
skipped-functions table actionable and allows corpus evaluations to
classify the generic-function gap precisely.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Signed-off-by: Felipe Monteiro <felisous@amazon.com>
Instantiate usize const generic parameters (by far the most common case,
e.g. array lengths) with the value 2, alongside the existing type-parameter
instantiation; the summary table shows the chosen value as part of the
instantiated name (e.g. with_const::<2>). Non-usize const parameters are
still skipped, now with a precise reason; the check consults the internal
generics since the public identity arguments do not carry the parameter's
type.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Signed-off-by: Felipe Monteiro <felisous@amazon.com>
choose_generic_instantiation previously only returned an instance when it
had a body, so a generic function without a body (e.g. a generic trait
method without a default) fell through and was reported with a generic
skip reason. Return the resolved instance regardless of has_body() and let
skip_reason report NoBody, matching the behavior for non-generic body-less
functions.

Addresses a review comment on model-checking#4679.

Signed-off-by: Felipe Monteiro <felisous@amazon.com>
@feliperodri
feliperodri force-pushed the autoharness-generics branch from c792ae4 to 4ea9fac Compare August 21, 2026 20:40
@feliperodri
feliperodri enabled auto-merge August 21, 2026 20:43
@feliperodri
feliperodri added this pull request to the merge queue Aug 21, 2026
Merged via the queue into model-checking:main with commit 101f814 Aug 21, 2026
34 checks passed
feliperodri pushed a commit to tautschnig/kani that referenced this pull request Aug 25, 2026
…on (model-checking#4706)

### Description

The top-100 crates.io evaluation (model-checking#3832) showed ~8,700 functions skipped
as "Generic Function: no candidate type satisfies the function's trait
bounds". This PR extends the instantiation search in three ways:

1. **Wider primitive candidates**: add `u8`, `i64`, `u64`, `f64`, `f32`.
The float candidates alone unlock the numerous
`Float`/`FloatCore`-bounded functions in num-traits and its dependents.
The existing candidates keep their positions (`i32` first), so
instantiations that already worked are unchanged.
2. **Per-parameter search**: after the cheap uniform pass (same
candidate for every parameter), search per-parameter combinations, so
functions whose parameters need *different* types (e.g. `fn cast<T:
Float, U: PrimInt>`) are instantiated. Capped at 256 trait-solver
queries per function.
3. **Trait-impl-derived candidates**: for each type parameter, derive
additional candidates from the concrete implementations of the traits it
is bound by (capped at 16 per parameter). A parameter bound by a
crate-local trait can thus be instantiated with a crate-local type
implementing it.

Candidate collection walks the parent chain of the predicate list, so
bounds on an *impl's* type parameters count too: for `impl<W:
Frobnicate> Container<W> { fn mix<T: FloatLike>(..) }`, `W: Frobnicate`
is a predicate of the parent rather than of `mix`, and without walking
the chain `W` would be left with primitive candidates only and the
method skipped. (`args_satisfy_predicates` already sees parent
predicates, via `GenericPredicates::instantiate`, so this was a missed
opportunity rather than a source of bogus instantiations.)

Measured impact (with `--bounded-arguments`, `-j 16`): num-traits 75
generic skips → **0** (2,107 → 2,154 harnesses; every public function
now harnessed); serde_json 594 → 276; syn 167 → 41.

Note that this only widens the *search*; each candidate is still
accepted only if the trait solver confirms it satisfies every predicate,
and verifying one monomorphic instantiation remains an
underapproximation, as documented.

### Testing

`cargo_autoharness_generics` gains a case for each capability:
- a float-only trait bound (`halve::<f64>`),
- a crate-local trait satisfied only by a struct (`frob_it::<Widget>`),
- parameters needing different types (`mixed::<f64, Widget>`),
- an impl method whose impl-level parameter needs an impl-derived
candidate, i.e. the parent-predicate case
(`Container::<Widget>::mix::<f64>`).

`needs_exotic` (a trait with no implementations at all) remains skipped,
with the candidate list in the message updated.

All 21 script-based autoharness tests pass, as do `cargo test -p
kani-compiler`/`-p kani-driver`, `clippy --all-targets` and `kani-fmt
--check`.

Rebased onto main now that model-checking#4679 has landed, so this is a single commit.

Towards model-checking#3832.

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 and MIT licenses.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Z-Autoharness Issue related to autoharness subcommand Z-CompilerBenchCI Tag a PR to run benchmark CI Z-EndToEndBenchCI Tag a PR to run benchmark CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants