Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[ty] Solve simple constraint conjunctions directly#25879

Merged
charliermarsh merged 3 commits into
mainfrom
charlie/benchmark-simple-specialization
Jun 22, 2026
Merged

[ty] Solve simple constraint conjunctions directly#25879
charliermarsh merged 3 commits into
mainfrom
charlie/benchmark-simple-specialization

Conversation

@charliermarsh

@charliermarsh charliermarsh commented Jun 11, 2026

Copy link
Copy Markdown
Member

Summary

Constraint-set solution extraction currently traverses every BDD path and builds a sequent map even when the BDD is only a positive conjunction of concrete lower bounds. This recognizes that shape and accumulates its path bounds directly, avoiding PathAssignments and pairwise sequent analysis.

The fast path remains inside the constraint solver and still uses normal solution selection, including validation against declared type-variable bounds and constraints. Specialization-specific inference and map reuse are left to a separate stacked change.

Performance

On the existing accumulation microbenchmark, simulation time drops from 974.7 ms to 374.9 ms and peak memory drops from 91.8 MB to 29.6 MB. The small-accumulation benchmark improves from 195.8 ms to 185.1 ms.

@astral-sh-bot astral-sh-bot Bot added the ty Multi-file analysis & type inference label Jun 11, 2026
@astral-sh-bot

astral-sh-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 94.37%. The percentage of expected errors that received a diagnostic held steady at 89.00%. The number of fully passing files held steady at 94/134.

@astral-sh-bot

astral-sh-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

Memory usage report

Summary

Project Old New Diff Outcome
trio 77.56MB 77.51MB -0.06% (51.37kB) ⬇️
flake8 30.82MB 30.77MB -0.18% (56.41kB) ⬇️
sphinx 193.95MB 193.45MB -0.26% (509.34kB) ⬇️
prefect 518.50MB 516.13MB -0.46% (2.37MB) ⬇️

Significant changes

Click to expand detailed breakdown

trio

Name Old New Diff Outcome
UnionType<'db>::from_two_elements_ 43.01kB 30.39kB -29.33% (12.62kB) ⬇️
UnionType<'db>::from_two_elements_::interned_arguments 42.45kB 31.54kB -25.71% (10.91kB) ⬇️
infer_expression_types_impl 6.57MB 6.56MB -0.13% (8.85kB) ⬇️
UnionType 146.91kB 142.09kB -3.28% (4.81kB) ⬇️
infer_definition_types 5.60MB 5.59MB -0.08% (4.50kB) ⬇️
parsed_module 15.04MB 15.04MB -0.02% (2.91kB) ⬇️
infer_scope_types_impl 3.20MB 3.20MB -0.04% (1.17kB) ⬇️
StaticClassLiteral<'db>::variance_of_::interned_arguments 14.41kB 13.57kB -5.85% (864.00B) ⬇️
infer_deferred_types 1.80MB 1.80MB -0.04% (696.00B) ⬇️
IntersectionType 134.56kB 133.91kB -0.49% (672.00B) ⬇️
StaticClassLiteral<'db>::variance_of_ 10.83kB 10.22kB -5.63% (624.00B) ⬇️
function_known_decorators 285.91kB 285.37kB -0.19% (552.00B) ⬇️
GenericAlias<'db>::variance_of_::interned_arguments 6.96kB 6.61kB -5.05% (360.00B) ⬇️
GenericAlias<'db>::variance_of_ 6.14kB 5.79kB -5.73% (360.00B) ⬇️
loop_header_reachability 124.80kB 124.49kB -0.24% (312.00B) ⬇️
... 6 more

flake8

Name Old New Diff Outcome
UnionType<'db>::from_two_elements_ 29.93kB 10.75kB -64.06% (19.17kB) ⬇️
UnionType<'db>::from_two_elements_::interned_arguments 26.90kB 11.17kB -58.47% (15.73kB) ⬇️
UnionType 68.14kB 56.66kB -16.85% (11.48kB) ⬇️
function_known_decorators 143.00kB 138.91kB -2.86% (4.09kB) ⬇️
is_redundant_with_impl::interned_arguments 89.12kB 87.14kB -2.22% (1.98kB) ⬇️
is_redundant_with_impl 75.36kB 73.69kB -2.22% (1.68kB) ⬇️
infer_expression_types_impl 989.18kB 987.59kB -0.16% (1.59kB) ⬇️
parsed_module 9.77MB 9.77MB +0.01% (1.44kB) ⬇️
infer_definition_types 1.35MB 1.35MB -0.07% (984.00B) ⬇️
infer_scope_types_impl 675.22kB 674.68kB -0.08% (552.00B) ⬇️
IntersectionType 51.08kB 50.59kB -0.96% (504.00B) ⬇️
infer_statement_types_impl 54.62kB 54.57kB -0.09% (48.00B) ⬇️
StaticClassLiteral<'db>::implicit_attribute_inner_ 50.65kB 50.63kB -0.05% (24.00B) ⬇️
member_lookup_with_policy_inner 430.45kB 430.43kB -0.01% (24.00B) ⬇️
try_call_bin_op_return_type_impl 6.02kB 6.00kB -0.39% (24.00B) ⬇️
... 1 more

sphinx

Name Old New Diff Outcome
UnionType<'db>::from_two_elements_ 313.18kB 181.98kB -41.89% (131.20kB) ⬇️
infer_expression_types_impl 23.33MB 23.22MB -0.49% (117.68kB) ⬇️
UnionType<'db>::from_two_elements_::interned_arguments 292.19kB 190.95kB -34.65% (101.23kB) ⬇️
UnionType 655.61kB 567.67kB -13.41% (87.94kB) ⬇️
infer_definition_types 19.56MB 19.54MB -0.09% (17.26kB) ⬇️
function_known_decorators 930.44kB 913.87kB -1.78% (16.57kB) ⬇️
infer_scope_types_impl 10.54MB 10.53MB -0.12% (13.05kB) ⬇️
infer_deferred_types 4.03MB 4.02MB -0.31% (12.87kB) ⬇️
parsed_module 18.36MB 18.36MB -0.01% (2.23kB) ⬇️
IntersectionType 548.82kB 546.69kB -0.39% (2.13kB) ⬇️
is_redundant_with_impl::interned_arguments 1.13MB 1.13MB -0.17% (1.98kB) ⬇️
is_redundant_with_impl 902.48kB 900.75kB -0.19% (1.73kB) ⬇️
try_call_bin_op_return_type_impl 194.78kB 194.01kB -0.40% (792.00B) ⬇️
infer_statement_types_impl 965.28kB 964.62kB -0.07% (672.00B) ⬇️
CallableType 1.47MB 1.47MB -0.03% (528.00B) ⬇️
... 13 more

prefect

Name Old New Diff Outcome
infer_expression_types_impl 56.91MB 56.40MB -0.90% (522.98kB) ⬇️
UnionType<'db>::from_two_elements_ 773.07kB 301.65kB -60.98% (471.42kB) ⬇️
UnionType<'db>::from_two_elements_::interned_arguments 653.04kB 287.46kB -55.98% (365.58kB) ⬇️
infer_definition_types 69.50MB 69.14MB -0.51% (365.01kB) ⬇️
UnionType 1.37MB 1.06MB -22.45% (314.55kB) ⬇️
infer_scope_types_impl 39.03MB 38.88MB -0.39% (156.86kB) ⬇️
function_known_decorators 3.88MB 3.78MB -2.60% (103.25kB) ⬇️
infer_deferred_types 8.89MB 8.86MB -0.32% (29.57kB) ⬇️
infer_unpack_types 997.83kB 976.62kB -2.13% (21.21kB) ⬇️
is_redundant_with_impl::interned_arguments 2.35MB 2.33MB -0.74% (17.70kB) ⬇️
is_redundant_with_impl 1.94MB 1.92MB -0.85% (16.98kB) ⬇️
IntersectionType 983.41kB 969.80kB -1.38% (13.62kB) ⬇️
infer_statement_types_impl 1.54MB 1.53MB -0.81% (12.71kB) ⬇️
all_narrowing_constraints_for_expression 6.58MB 6.58MB -0.08% (5.40kB) ⬇️
parsed_module 19.36MB 19.36MB -0.01% (2.53kB) ⬇️
... 22 more

@astral-sh-bot

astral-sh-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

No diagnostic changes detected ✅

Large timing changes:

Project Old Time New Time Change
isort 0.30s 0.73s +143%

Flaky changes detected. This PR summary excludes flaky changes; see the HTML report for details.

Full report with detailed diff (timing results)

@charliermarsh charliermarsh force-pushed the charlie/benchmark-simple-specialization branch from 9f671c8 to d790d35 Compare June 11, 2026 18:51
@codspeed-hq

codspeed-hq Bot commented Jun 11, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 40.99%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 7 improved benchmarks
✅ 64 untouched benchmarks
⏩ 64 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory ty_micro[typevar_mapping_accumulation] 90.8 MB 28.5 MB ×3.2
Simulation ty_micro[typevar_mapping_accumulation] 883 ms 326.2 ms ×2.7
Simulation ty_micro[typevar_mapping_small_accumulations] 185.6 ms 172.7 ms +7.48%
WallTime pydantic 10 s 9.5 s +5.37%
WallTime pandas 83 s 79.3 s +4.62%
Memory ty_micro[typevar_mapping_small_accumulations] 12.7 MB 12.2 MB +4.21%
WallTime static_frame 27.4 s 26.3 s +4.18%

Tip

Curious why this is faster? Use the CodSpeed MCP and ask your agent.


Comparing charlie/benchmark-simple-specialization (0c94738) with main (1b011de)

Open in CodSpeed

Footnotes

  1. 64 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@charliermarsh charliermarsh changed the base branch from main to charlie/home-assistant-codspeed-baseline June 11, 2026 18:52
@astral-sh-bot

astral-sh-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

@charliermarsh charliermarsh changed the title [ty] Run benchmarks for simple generic specialization [ty] Solve simple constraint conjunctions directly Jun 11, 2026
@charliermarsh charliermarsh force-pushed the charlie/benchmark-simple-specialization branch from 4d61703 to 4b39907 Compare June 11, 2026 21:20
@charliermarsh charliermarsh force-pushed the charlie/benchmark-simple-specialization branch from 4b39907 to c44782a Compare June 11, 2026 21:52
@charliermarsh charliermarsh changed the base branch from charlie/home-assistant-codspeed-baseline to main June 11, 2026 21:53
@charliermarsh charliermarsh force-pushed the charlie/benchmark-simple-specialization branch from c44782a to 8883f41 Compare June 11, 2026 22:15
@charliermarsh charliermarsh marked this pull request as ready for review June 11, 2026 22:24
@charliermarsh charliermarsh force-pushed the charlie/benchmark-simple-specialization branch from 8883f41 to 598ab50 Compare June 12, 2026 19:37
@charliermarsh charliermarsh force-pushed the charlie/benchmark-simple-specialization branch from 598ab50 to f03b69c Compare June 16, 2026 23:57
@charliermarsh charliermarsh requested a review from a team as a code owner June 16, 2026 23:57
@charliermarsh charliermarsh added the performance Potential performance improvement label Jun 16, 2026

@dcreager dcreager left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A minor concern about some repeated work, but otherwise this looks good

Comment on lines +6542 to +6551
assert!(matches!(&solutions, Solutions::Constrained(_)));
if let Solutions::Constrained(solutions) = solutions {
assert_eq!(solutions.len(), 1);
assert_eq!(solutions[0].len(), 1);
assert_eq!(solutions[0][0].bound_typevar, t);
assert_eq!(
solutions[0][0].solution,
UnionType::from_elements(&db, [int, str])
);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: Can this be re-expressed as a single assert_eq call?

Suggested change
assert!(matches!(&solutions, Solutions::Constrained(_)));
if let Solutions::Constrained(solutions) = solutions {
assert_eq!(solutions.len(), 1);
assert_eq!(solutions[0].len(), 1);
assert_eq!(solutions[0][0].bound_typevar, t);
assert_eq!(
solutions[0][0].solution,
UnionType::from_elements(&db, [int, str])
);
}
assert_eq!(solutions, Solutions::Constrained(vec![
vec![TypeVarSolution {
bound_typevar: t,
solution: UnionType::from_elements(&db, [int, str]),
}],
]));

Comment on lines +583 to +585
if self
.node
.simple_lower_bound_conjunction(db, builder)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have a mild concern that this is calling a method that builds up a Vec to determine if we can engage the fast path. And if so, we end up calling simple_lower_bound_conjunction (and building up that Vec) again when we find the solutions. It might be worth making two copies: one for use here, which just returns a bool if the BDD is a simple conjunction (and also checks is_inferable); and a second which builds up the Vec for use down in PathBounds::compute

@charliermarsh charliermarsh force-pushed the charlie/benchmark-simple-specialization branch from f03b69c to 6e58ca3 Compare June 22, 2026 20:21
@charliermarsh charliermarsh merged commit d5163c9 into main Jun 22, 2026
61 checks passed
@charliermarsh charliermarsh deleted the charlie/benchmark-simple-specialization branch June 22, 2026 20:42
charliermarsh added a commit that referenced this pull request Jun 22, 2026
## Summary

The simple-lower-bound fast path added in #25879 currently recognizes
the same BDD shape in two separate traversals: an allocation-free
inferability predicate and a collector used for path-bound construction.

This replaces both implementations with one lazy iterator built with
`std::iter::from_fn`. `remove_noninferable` consumes it with
`Iterator::all`, so eligibility remains allocation-free, while solution
construction collects the same iterator into the `Vec` it already needs.
Invalid BDD shapes yield an error item and take the existing general
fallback.

This is an alternative to #26247. It avoids a callback-based visitor at
the cost of encoding shape failure in the iterator item type.

The full `ty_python_semantic` suite, crate-level Clippy, and file-scoped
repository hooks pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Potential performance improvement ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants