code-towel 1.732__tar.gz → 1.772__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_towel-1.772/CHANGELOG.md +2078 -0
- code_towel-1.772/CONTRIBUTING.md +222 -0
- code_towel-1.772/MANIFEST.in +16 -0
- code_towel-1.772/PKG-INFO +432 -0
- code_towel-1.772/README.md +387 -0
- code_towel-1.772/SECURITY.md +19 -0
- code_towel-1.772/docs/ADVERSARIAL_REVIEW.md +173 -0
- code_towel-1.772/docs/ARCHITECTURE.md +1589 -0
- code_towel-1.772/docs/DECISIONS.md +1027 -0
- code_towel-1.772/docs/KNOWN_LIMITATIONS.md +2200 -0
- code_towel-1.772/docs/OPEN_SOURCE_AUDIT.md +81 -0
- code_towel-1.772/docs/PRODUCTION_READINESS.md +948 -0
- code_towel-1.772/docs/QUICKSTART.md +102 -0
- code_towel-1.772/docs/RELEASE_LOG.md +454 -0
- code_towel-1.772/docs/RELEASING.md +319 -0
- code_towel-1.772/docs/USAGE_GUIDE.md +915 -0
- code_towel-1.772/docs/proposals/decline-capabilities.md +106 -0
- code_towel-1.772/docs/proposals/performance-roadmap.md +148 -0
- code_towel-1.772/docs/proposals/preview-what-dry-would-do.md +94 -0
- code_towel-1.772/docs/proposals/project-own-check.md +122 -0
- code_towel-1.772/docs/proposals/reuse-existing-function.md +175 -0
- code_towel-1.772/docs/proposals/shared-helper-module.md +174 -0
- code_towel-1.772/docs/proposals/template-method.md +162 -0
- code_towel-1.772/docs/proposals/trusted-decorators.md +44 -0
- code_towel-1.772/docs/proposals/type-parameters.md +382 -0
- code_towel-1.772/justfile +198 -0
- code_towel-1.772/pyproject.toml +134 -0
- code_towel-1.772/scripts/add_copyright_headers.py +160 -0
- code_towel-1.772/scripts/audit_dependencies.py +70 -0
- code_towel-1.772/scripts/bench_refactor.py +132 -0
- code_towel-1.772/scripts/check_mermaid.mjs +62 -0
- code_towel-1.772/scripts/ecosystem/Dockerfile +42 -0
- code_towel-1.772/scripts/ecosystem/manifest.toml +1136 -0
- code_towel-1.772/scripts/ecosystem_check.py +3423 -0
- code_towel-1.772/scripts/report_net_benefit_diff.py +130 -0
- code_towel-1.772/scripts/run_crossfile_coverage.py +41 -0
- code_towel-1.772/scripts/set_version.py +47 -0
- code_towel-1.772/scripts/trace_proposals.py +113 -0
- code_towel-1.772/scripts/verify-examples +125 -0
- code_towel-1.772/src/code_towel.egg-info/PKG-INFO +432 -0
- code_towel-1.772/src/code_towel.egg-info/SOURCES.txt +1275 -0
- code_towel-1.772/src/towel/_mypy_worker.py +1321 -0
- code_towel-1.772/src/towel/canonical_ast.py +115 -0
- code_towel-1.772/src/towel/changes.py +531 -0
- code_towel-1.772/src/towel/checker_project.py +1119 -0
- code_towel-1.772/src/towel/cli.py +2096 -0
- code_towel-1.772/src/towel/consumers.py +405 -0
- code_towel-1.772/src/towel/coverage_config.py +339 -0
- code_towel-1.772/src/towel/declared_python.py +176 -0
- code_towel-1.772/src/towel/declared_requirements.py +404 -0
- code_towel-1.772/src/towel/filesystem.py +421 -0
- code_towel-1.772/src/towel/formatting.py +770 -0
- code_towel-1.772/src/towel/import_model.py +2586 -0
- code_towel-1.772/src/towel/program_files.py +424 -0
- code_towel-1.772/src/towel/project_layout.py +104 -0
- code_towel-1.772/src/towel/pyright_session.py +909 -0
- code_towel-1.772/src/towel/reachability.py +214 -0
- code_towel-1.772/src/towel/renaming.py +1697 -0
- code_towel-1.772/src/towel/shipped_files.py +890 -0
- code_towel-1.772/src/towel/source_files.py +108 -0
- code_towel-1.772/src/towel/source_text.py +207 -0
- code_towel-1.772/src/towel/type_baseline.py +967 -0
- code_towel-1.772/src/towel/type_inference.py +2237 -0
- code_towel-1.772/src/towel/unification/annotation_imports.py +388 -0
- code_towel-1.772/src/towel/unification/annotation_ladder.py +1469 -0
- code_towel-1.772/src/towel/unification/annotation_wiring.py +1955 -0
- code_towel-1.772/src/towel/unification/annotations.py +1632 -0
- code_towel-1.772/src/towel/unification/assert_rewriting.py +439 -0
- code_towel-1.772/src/towel/unification/assignment_analyzer.py +346 -0
- code_towel-1.772/src/towel/unification/block_analysis.py +1128 -0
- code_towel-1.772/src/towel/unification/block_comments.py +2938 -0
- code_towel-1.772/src/towel/unification/bounded_cache.py +137 -0
- code_towel-1.772/src/towel/unification/builtins.py +261 -0
- code_towel-1.772/src/towel/unification/class_private.py +196 -0
- code_towel-1.772/src/towel/unification/clustering.py +592 -0
- code_towel-1.772/src/towel/unification/constant_consistency.py +174 -0
- code_towel-1.772/src/towel/unification/decorator_reach.py +1869 -0
- code_towel-1.772/src/towel/unification/definite_assignment.py +390 -0
- code_towel-1.772/src/towel/unification/engine_state.py +501 -0
- code_towel-1.772/src/towel/unification/exceptions.py +161 -0
- code_towel-1.772/src/towel/unification/extractor.py +1040 -0
- code_towel-1.772/src/towel/unification/fixed_point.py +1267 -0
- code_towel-1.772/src/towel/unification/function_scope.py +456 -0
- code_towel-1.772/src/towel/unification/generic_annotations.py +725 -0
- code_towel-1.772/src/towel/unification/hof_promotion.py +223 -0
- code_towel-1.772/src/towel/unification/import_graph.py +2117 -0
- code_towel-1.772/src/towel/unification/insertion.py +338 -0
- code_towel-1.772/src/towel/unification/instantiation.py +1183 -0
- code_towel-1.772/src/towel/unification/known_bases.py +220 -0
- code_towel-1.772/src/towel/unification/known_decorators.py +380 -0
- code_towel-1.772/src/towel/unification/known_platforms.py +1166 -0
- code_towel-1.772/src/towel/unification/known_source_readers.py +140 -0
- code_towel-1.772/src/towel/unification/lexical_scopes.py +195 -0
- code_towel-1.772/src/towel/unification/materialize.py +1099 -0
- code_towel-1.772/src/towel/unification/models.py +480 -0
- code_towel-1.772/src/towel/unification/module_bindings.py +458 -0
- code_towel-1.772/src/towel/unification/namespace_writes.py +934 -0
- code_towel-1.772/src/towel/unification/narrowing.py +277 -0
- code_towel-1.772/src/towel/unification/pair_evaluation.py +1822 -0
- code_towel-1.772/src/towel/unification/parallel.py +436 -0
- code_towel-1.772/src/towel/unification/parameterization.py +347 -0
- code_towel-1.772/src/towel/unification/pipeline.py +467 -0
- code_towel-1.772/src/towel/unification/placement.py +823 -0
- code_towel-1.772/src/towel/unification/program_imports.py +208 -0
- code_towel-1.772/src/towel/unification/progress.py +270 -0
- code_towel-1.772/src/towel/unification/project_layout.py +23 -0
- code_towel-1.772/src/towel/unification/refactor_engine.py +1090 -0
- code_towel-1.772/src/towel/unification/reuse.py +200 -0
- code_towel-1.772/src/towel/unification/revealed_types.py +487 -0
- code_towel-1.772/src/towel/unification/scope_analyzer.py +793 -0
- code_towel-1.772/src/towel/unification/semantic_safety.py +1962 -0
- code_towel-1.772/src/towel/unification/source_readers.py +409 -0
- code_towel-1.772/src/towel/unification/splicing.py +150 -0
- code_towel-1.772/src/towel/unification/statement_facts.py +294 -0
- code_towel-1.772/src/towel/unification/static_positions.py +508 -0
- code_towel-1.772/src/towel/unification/structural_memo.py +161 -0
- code_towel-1.772/src/towel/unification/substitution.py +134 -0
- code_towel-1.772/src/towel/unification/thunk_inlining.py +567 -0
- code_towel-1.772/src/towel/unification/type_bindings.py +1091 -0
- code_towel-1.772/src/towel/unification/type_generalization.py +279 -0
- code_towel-1.772/src/towel/unification/typing_forms.py +325 -0
- code_towel-1.772/src/towel/unification/unifier.py +1180 -0
- code_towel-1.772/src/towel/unification/unifier_state.py +83 -0
- code_towel-1.772/src/towel/unification/visitors.py +710 -0
- code_towel-1.772/test_examples_crossfile/README.md +244 -0
- code_towel-1.772/test_examples_crossfile/multi_level/core/services/payment.py +29 -0
- code_towel-1.772/test_examples_crossfile/multi_level/utils/validators.py +28 -0
- code_towel-1.772/test_examples_crossfile/nested_structure/src/data_processor.py +25 -0
- code_towel-1.772/test_examples_crossfile/simple_crossfile/user_service.py +27 -0
- code_towel-1.772/test_examples_crossfile_expected_output/multi_level/api/checkout.py +35 -0
- code_towel-1.772/test_examples_crossfile_expected_output/multi_level/core/services/payment.py +20 -0
- code_towel-1.772/test_examples_crossfile_expected_output/multi_level/utils/validators.py +19 -0
- code_towel-1.772/test_examples_crossfile_expected_output/nested_structure/lib/report_generator.py +28 -0
- code_towel-1.772/test_examples_crossfile_expected_output/nested_structure/src/data_processor.py +19 -0
- code_towel-1.772/test_examples_crossfile_expected_output/simple_crossfile/admin_service.py +29 -0
- code_towel-1.772/test_examples_crossfile_expected_output/simple_crossfile/user_service.py +19 -0
- code_towel-1.772/test_examples_expected_output/annotated_module.py +108 -0
- code_towel-1.772/test_examples_expected_output/binding_constructs_comprehensive.py +133 -0
- code_towel-1.772/test_examples_expected_output/bindings_for_loops.py +60 -0
- code_towel-1.772/test_examples_expected_output/closure_adversarial.py +119 -0
- code_towel-1.772/test_examples_expected_output/control_flow_adversarial.py +145 -0
- code_towel-1.772/test_examples_expected_output/edge_cases_stress_test.py +211 -0
- code_towel-1.772/test_examples_expected_output/example4_complex.py +37 -0
- code_towel-1.772/test_examples_expected_output/exception_adversarial.py +156 -0
- code_towel-1.772/test_examples_expected_output/fstrings_constants.py +88 -0
- code_towel-1.772/test_examples_expected_output/functional_patterns.py +239 -0
- code_towel-1.772/test_examples_expected_output/method_chains.py +204 -0
- code_towel-1.772/test_examples_expected_output/return_values.py +96 -0
- code_towel-1.772/test_examples_expected_output/scoping_edge_cases.py +113 -0
- code_towel-1.772/test_examples_expected_output/side_effects_adversarial.py +122 -0
- code_towel-1.772/test_examples_expected_output/syntactic_coverage_comprehensive.py +735 -0
- code_towel-1.772/test_examples_expected_output/tricky_edge_cases_adversarial.py +221 -0
- code_towel-1.772/tests/README.md +324 -0
- code_towel-1.772/tests/__init__.py +17 -0
- code_towel-1.772/tests/audit_defects.py +27 -0
- code_towel-1.772/tests/automatic_equivalence_tester.py +652 -0
- code_towel-1.772/tests/conftest.py +188 -0
- code_towel-1.772/tests/crossfile_equivalence_tester.py +385 -0
- code_towel-1.772/tests/differential/__init__.py +37 -0
- code_towel-1.772/tests/differential/cases.py +113 -0
- code_towel-1.772/tests/differential/comparison.py +148 -0
- code_towel-1.772/tests/differential/export.py +264 -0
- code_towel-1.772/tests/differential/fuzz.py +259 -0
- code_towel-1.772/tests/differential/grammar.py +838 -0
- code_towel-1.772/tests/differential/observer.py +416 -0
- code_towel-1.772/tests/differential/runner.py +320 -0
- code_towel-1.772/tests/differential/scope_grammar.py +298 -0
- code_towel-1.772/tests/ecosystem_fixtures.py +214 -0
- code_towel-1.772/tests/edge_case_values.py +375 -0
- code_towel-1.772/tests/equivalence_targets.py +161 -0
- code_towel-1.772/tests/generate_baseline.py +198 -0
- code_towel-1.772/tests/hostile_cases/h01_side_effect_order.py +37 -0
- code_towel-1.772/tests/hostile_cases/h02_conditional_eval.py +30 -0
- code_towel-1.772/tests/hostile_cases/h03_loop_reeval.py +33 -0
- code_towel-1.772/tests/hostile_cases/h04_closure_freevar.py +30 -0
- code_towel-1.772/tests/hostile_cases/h05_cond_return_plus_retvar.py +34 -0
- code_towel-1.772/tests/hostile_cases/h06_del_in_block.py +34 -0
- code_towel-1.772/tests/hostile_cases/h07_except_unbind.py +38 -0
- code_towel-1.772/tests/hostile_cases/h08_late_binding_closure.py +30 -0
- code_towel-1.772/tests/hostile_cases/h10_short_circuit.py +28 -0
- code_towel-1.772/tests/hostile_cases/h13_dunder_file.py +27 -0
- code_towel-1.772/tests/hostile_cases/h26_match_capture.py +32 -0
- code_towel-1.772/tests/hostile_cases/h38_closure_after_block.py +32 -0
- code_towel-1.772/tests/hostile_cases/h39_augassign_after_block.py +30 -0
- code_towel-1.772/tests/hostile_cases/h51_semicolons.py +28 -0
- code_towel-1.772/tests/hostile_cases/h52_del_after_block.py +30 -0
- code_towel-1.772/tests/hostile_cases/h53_nested_func_reads_retvar.py +28 -0
- code_towel-1.772/tests/hostile_cases/h54_attr_param_property.py +35 -0
- code_towel-1.772/tests/hostile_cases/h55_walrus_used_after.py +31 -0
- code_towel-1.772/tests/hostile_cases/h56_global_write.py +29 -0
- code_towel-1.772/tests/hostile_cases/h57_exception_in_param.py +32 -0
- code_towel-1.772/tests/hostile_cases/h58_nested_def_freevar_reassigned.py +28 -0
- code_towel-1.772/tests/hostile_cases/h59_retvar_conditional_unbound.py +34 -0
- code_towel-1.772/tests/hostile_cases/h60_finally_return.py +32 -0
- code_towel-1.772/tests/hostile_cases/h61_str_method_param.py +30 -0
- code_towel-1.772/tests/hostile_cases/p01_static_helper_class_name_shadowed_by_parameter.py +39 -0
- code_towel-1.772/tests/hostile_cases/p02_static_helper_class_deleted.py +39 -0
- code_towel-1.772/tests/hostile_cases/p03_static_helper_mangled_class_name.py +38 -0
- code_towel-1.772/tests/hostile_cases/p04_static_helper_class_decorator_returns_factory.py +42 -0
- code_towel-1.772/tests/hostile_cases/p05_static_helper_class_global_rebound.py +41 -0
- code_towel-1.772/tests/hostile_cases/p06_static_helper_called_while_class_body_runs.py +38 -0
- code_towel-1.772/tests/hostile_cases/p07_static_methods_called_while_class_body_runs.py +39 -0
- code_towel-1.772/tests/hostile_cases/p08_classmethod_never_using_cls.py +39 -0
- code_towel-1.772/tests/hostile_cases/p09_static_helper_metaclass_hides_attribute.py +42 -0
- code_towel-1.772/tests/hostile_cases/p10_one_line_exception_base.py +39 -0
- code_towel-1.772/tests/hostile_cases/p11_one_line_base_docstring_and_assignment.py +37 -0
- code_towel-1.772/tests/hostile_cases/p12_one_line_body_after_a_split_header.py +40 -0
- code_towel-1.772/tests/hostile_cases/p13_protocol_default_methods.py +50 -0
- code_towel-1.772/tests/hostile_cases/p14_protocol_common_ancestor.py +46 -0
- code_towel-1.772/tests/hostile_cases/p15_class_decorator_rebuilds_namespace.py +44 -0
- code_towel-1.772/tests/hostile_cases/p16_class_decorator_wraps_every_function.py +52 -0
- code_towel-1.772/tests/hostile_cases/p17_decorated_base_rebuilds_namespace.py +46 -0
- code_towel-1.772/tests/hostile_cases/p18_known_class_decorators_keep_the_helper.py +99 -0
- code_towel-1.772/tests/hostile_cases/p19_thunk_of_possibly_unbound_local.py +44 -0
- code_towel-1.772/tests/hostile_cases/p20_thunk_of_loop_variable.py +42 -0
- code_towel-1.772/tests/hostile_cases/p21_helper_placed_after_an_assignment_that_calls_it.py +51 -0
- code_towel-1.772/tests/hostile_cases/r01_property_thunk_order.py +33 -0
- code_towel-1.772/tests/hostile_cases/r02_cluster_attrs.py +39 -0
- code_towel-1.772/tests/hostile_cases/r07_attr_two_positions.py +30 -0
- code_towel-1.772/tests/hostile_cases/r100_clustered_site_with_live_binding.py +36 -0
- code_towel-1.772/tests/hostile_cases/r101_same_named_method_forces_module_helper.py +57 -0
- code_towel-1.772/tests/hostile_cases/r102_whole_body_reuse.py +40 -0
- code_towel-1.772/tests/hostile_cases/r103_lambda_parameter_spelling.py +39 -0
- code_towel-1.772/tests/hostile_cases/r104_walrus_target_spelling.py +28 -0
- code_towel-1.772/tests/hostile_cases/r105_closure_over_block_bound_name_rebound_after.py +32 -0
- code_towel-1.772/tests/hostile_cases/r106_dir_reads_the_frame.py +27 -0
- code_towel-1.772/tests/hostile_cases/r107_unbound_global_argument.py +28 -0
- code_towel-1.772/tests/hostile_cases/r108_clustered_sites_assign_the_returned_name.py +37 -0
- code_towel-1.772/tests/hostile_cases/r109_eager_optional_import_failed.py +33 -0
- code_towel-1.772/tests/hostile_cases/r110_eager_type_checking_import.py +32 -0
- code_towel-1.772/tests/hostile_cases/r111_eager_module_except_as_name.py +36 -0
- code_towel-1.772/tests/hostile_cases/r112_eager_module_match_capture_unmatched.py +33 -0
- code_towel-1.772/tests/hostile_cases/r113_eager_class_attr_in_method.py +31 -0
- code_towel-1.772/tests/hostile_cases/r114_eager_nested_class_attr.py +33 -0
- code_towel-1.772/tests/hostile_cases/r115_eager_class_in_function_attr.py +34 -0
- code_towel-1.772/tests/hostile_cases/r116_eager_enclosing_bound_after_inner_call.py +33 -0
- code_towel-1.772/tests/hostile_cases/r117_eager_enclosing_deleted_before_inner_call.py +33 -0
- code_towel-1.772/tests/hostile_cases/r118_eager_enclosing_conditionally_bound.py +33 -0
- code_towel-1.772/tests/hostile_cases/r119_eager_module_def_and_class_after_call.py +33 -0
- code_towel-1.772/tests/hostile_cases/r11_same_function_pair.py +28 -0
- code_towel-1.772/tests/hostile_cases/r120_eager_module_import_after_call.py +31 -0
- code_towel-1.772/tests/hostile_cases/r121_eager_comprehension_variable.py +30 -0
- code_towel-1.772/tests/hostile_cases/r122_eager_lambda_parameter.py +30 -0
- code_towel-1.772/tests/hostile_cases/r123_direct_eval_after_block.py +30 -0
- code_towel-1.772/tests/hostile_cases/r124_eval_alias_reads_preblock_name.py +60 -0
- code_towel-1.772/tests/hostile_cases/r125_locals_dir_after_block.py +54 -0
- code_towel-1.772/tests/hostile_cases/r126_locals_in_nested_scope.py +44 -0
- code_towel-1.772/tests/hostile_cases/r127_eval_alias_partial_block.py +67 -0
- code_towel-1.772/tests/hostile_cases/r128_warn_stacklevel_via_alias.py +45 -0
- code_towel-1.772/tests/hostile_cases/r129_warning_registry_dedup.py +32 -0
- code_towel-1.772/tests/hostile_cases/r130_resource_observed_after_block_differs.py +34 -0
- code_towel-1.772/tests/hostile_cases/r131_weakref_and_id_identity.py +41 -0
- code_towel-1.772/tests/hostile_cases/r132_async_comprehension_in_block.py +30 -0
- code_towel-1.772/tests/hostile_cases/r134_u2028_in_comment_shifts_splice.py +31 -0
- code_towel-1.772/tests/hostile_cases/r136_u2028_in_string_literal.py +31 -0
- code_towel-1.772/tests/hostile_cases/r137_x1c_x85_in_comment.py +31 -0
- code_towel-1.772/tests/hostile_cases/r138_u2028_comment_same_indent_neighbours.py +38 -0
- code_towel-1.772/tests/hostile_cases/r139_literal_roundtrip.py +32 -0
- code_towel-1.772/tests/hostile_cases/r140_unbound_name_on_untaken_branch.py +35 -0
- code_towel-1.772/tests/hostile_cases/r141_module_name_bound_after_an_early_call.py +38 -0
- code_towel-1.772/tests/hostile_cases/r142_module_data_rebound_by_callback.py +36 -0
- code_towel-1.772/tests/hostile_cases/r143_module_name_shadowed_by_a_local_elsewhere.py +36 -0
- code_towel-1.772/tests/hostile_cases/r144_class_cell_in_a_method_helper.py +46 -0
- code_towel-1.772/tests/hostile_cases/r145_getframe_assigned_alias.py +61 -0
- code_towel-1.772/tests/hostile_cases/r146_module_function_reads_caller_frame.py +51 -0
- code_towel-1.772/tests/hostile_cases/r147_wide_int_literal.py +44 -0
- code_towel-1.772/tests/hostile_cases/r148_equal_constants_of_different_types.py +52 -0
- code_towel-1.772/tests/hostile_cases/r149_match_pattern_reads_caller_names.py +86 -0
- code_towel-1.772/tests/hostile_cases/r14_starred_param.py +29 -0
- code_towel-1.772/tests/hostile_cases/r150_pattern_parts_that_differ.py +95 -0
- code_towel-1.772/tests/hostile_cases/r151_pattern_names_that_may_differ.py +61 -0
- code_towel-1.772/tests/hostile_cases/r152_definition_time_reads.py +68 -0
- code_towel-1.772/tests/hostile_cases/r153_class_definition_reads.py +78 -0
- code_towel-1.772/tests/hostile_cases/r154_bare_annotation_reads_the_caller.py +32 -0
- code_towel-1.772/tests/hostile_cases/r155_lazy_type_values_close_over_caller_names.pynew +53 -0
- code_towel-1.772/tests/hostile_cases/r156_created_objects_that_escape.py +105 -0
- code_towel-1.772/tests/hostile_cases/r157_created_objects_only_called.py +69 -0
- code_towel-1.772/tests/hostile_cases/r158_getattr_serves_unknown_names_from_data.py +56 -0
- code_towel-1.772/tests/hostile_cases/r159_frame_handle_before_a_nested_block.py +46 -0
- code_towel-1.772/tests/hostile_cases/r160_host_binds_any_before_its_last_import.py +55 -0
- code_towel-1.772/tests/hostile_cases/r161_module_with_all_gains_no_public_name.py +50 -0
- code_towel-1.772/tests/hostile_cases/r162_host_star_imports_callable.py +46 -0
- code_towel-1.772/tests/hostile_cases/r22_fstring_attr.py +33 -0
- code_towel-1.772/tests/hostile_cases/r26_attr_store_param.py +28 -0
- code_towel-1.772/tests/hostile_cases/r27_del_subscript.py +26 -0
- code_towel-1.772/tests/hostile_cases/r32_method_callee.py +32 -0
- code_towel-1.772/tests/hostile_cases/r38_name_two_positions.py +28 -0
- code_towel-1.772/tests/hostile_cases/r39_cluster_alpha.py +31 -0
- code_towel-1.772/tests/hostile_cases/r40_class_hierarchy.py +35 -0
- code_towel-1.772/tests/hostile_cases/r41_conditional_call_param.py +31 -0
- code_towel-1.772/tests/hostile_cases/r42_loop_call_param.py +30 -0
- code_towel-1.772/tests/hostile_cases/r43_opaque_decorator_receiver.py +31 -0
- code_towel-1.772/tests/hostile_cases/r44_cluster_nested_overlap.py +37 -0
- code_towel-1.772/tests/hostile_cases/r45_async_no_await.py +31 -0
- code_towel-1.772/tests/hostile_cases/r46_nonlocal_counter.py +36 -0
- code_towel-1.772/tests/hostile_cases/r47_lru_cache_method.py +33 -0
- code_towel-1.772/tests/hostile_cases/r50_helper_name_collision.py +32 -0
- code_towel-1.772/tests/hostile_cases/r53_try_else_binding.py +42 -0
- code_towel-1.772/tests/hostile_cases/r54_with_binding_used_after.py +29 -0
- code_towel-1.772/tests/hostile_cases/r55_chained_assign.py +28 -0
- code_towel-1.772/tests/hostile_cases/r57_star_unpack.py +26 -0
- code_towel-1.772/tests/hostile_cases/r58_escaping_closure.py +30 -0
- code_towel-1.772/tests/hostile_cases/r60_local_import.py +28 -0
- code_towel-1.772/tests/hostile_cases/r61_same_function_clean.py +27 -0
- code_towel-1.772/tests/hostile_cases/r62_recursion.py +28 -0
- code_towel-1.772/tests/hostile_cases/r63_except_var_used_in_handler.py +36 -0
- code_towel-1.772/tests/hostile_cases/r66_conditional_callee.py +31 -0
- code_towel-1.772/tests/hostile_cases/r69_annassign.py +28 -0
- code_towel-1.772/tests/hostile_cases/r72_global_promoted.py +31 -0
- code_towel-1.772/tests/hostile_cases/r73_slice_vs_index.py +26 -0
- code_towel-1.772/tests/hostile_cases/r74_starred_vs_plain.py +29 -0
- code_towel-1.772/tests/hostile_cases/r75_prior_helper_param_names.py +28 -0
- code_towel-1.772/tests/hostile_cases/r76_return_order.py +30 -0
- code_towel-1.772/tests/hostile_cases/r77_mixed_return_and_variables.py +31 -0
- code_towel-1.772/tests/hostile_cases/r78_walrus_in_parameter.py +31 -0
- code_towel-1.772/tests/hostile_cases/r79_list_display_in_loop.py +37 -0
- code_towel-1.772/tests/hostile_cases/r7bi_augassign_and_del_after_block.py +61 -0
- code_towel-1.772/tests/hostile_cases/r7bi_except_name_in_nested_block.py +46 -0
- code_towel-1.772/tests/hostile_cases/r7bi_loop_capture_def_rebind_prebound.py +86 -0
- code_towel-1.772/tests/hostile_cases/r7bi_loop_del_before_eager_read.py +38 -0
- code_towel-1.772/tests/hostile_cases/r7bi_read_before_own_binding.py +46 -0
- code_towel-1.772/tests/hostile_cases/r7bi_renamed_binder_named_by_unbound_error.py +62 -0
- code_towel-1.772/tests/hostile_cases/r7c_loop_copy_beside_a_top_level_twin.py +38 -0
- code_towel-1.772/tests/hostile_cases/r7c_mangled_import_names.py +81 -0
- code_towel-1.772/tests/hostile_cases/r7c_mangled_parameter_passed_by_keyword.py +39 -0
- code_towel-1.772/tests/hostile_cases/r7c_mangled_parameter_passed_by_keyword_in_a_method_helper.py +42 -0
- code_towel-1.772/tests/hostile_cases/r7c_rebinding_enclosing_function_beside_a_benign_twin.py +64 -0
- code_towel-1.772/tests/hostile_cases/r7c_top_level_copy_beside_a_loop_copy.py +39 -0
- code_towel-1.772/tests/hostile_cases/r7c_unmangled_parameter_passed_by_keyword.py +51 -0
- code_towel-1.772/tests/hostile_cases/r7d_instrumenting_call_applied_by_hand.py +63 -0
- code_towel-1.772/tests/hostile_cases/r7d_instrumenting_class_decorator.py +63 -0
- code_towel-1.772/tests/hostile_cases/r7d_instrumenting_enclosing_function.py +64 -0
- code_towel-1.772/tests/hostile_cases/r7d_instrumenting_function_decorator.py +57 -0
- code_towel-1.772/tests/hostile_cases/r7d_metaclass_recompiles_methods.py +63 -0
- code_towel-1.772/tests/hostile_cases/r7d_method_helper_in_a_testcase.py +70 -0
- code_towel-1.772/tests/hostile_cases/r7d_plain_wrapper_applied_by_hand.py +74 -0
- code_towel-1.772/tests/hostile_cases/r7d_plain_wrapper_decorator.py +57 -0
- code_towel-1.772/tests/hostile_cases/r7fz_binding_builtin_rebound_mid_module.py +63 -0
- code_towel-1.772/tests/hostile_cases/r7fz_binding_cell_filled_late.py +71 -0
- code_towel-1.772/tests/hostile_cases/r7fz_binding_cell_read_before_fill.py +75 -0
- code_towel-1.772/tests/hostile_cases/r7fz_binding_class_attr_vs_local.py +62 -0
- code_towel-1.772/tests/hostile_cases/r7fz_binding_dunder_name_file_samemodule.py +60 -0
- code_towel-1.772/tests/hostile_cases/r7fz_binding_global_rebound_by_callee.py +67 -0
- code_towel-1.772/tests/hostile_cases/r7fz_binding_global_vs_local.py +68 -0
- code_towel-1.772/tests/hostile_cases/r7fz_binding_loop_var_leak.py +62 -0
- code_towel-1.772/tests/hostile_cases/r7fz_binding_module_getattr.py +69 -0
- code_towel-1.772/tests/hostile_cases/r7fz_binding_unbound_local_handler.py +71 -0
- code_towel-1.772/tests/hostile_cases/r7fz_builtins_differ_in_builtin.py +61 -0
- code_towel-1.772/tests/hostile_cases/r7fz_builtins_mock_patch_module.py +80 -0
- code_towel-1.772/tests/hostile_cases/r7fz_builtins_module_shadows_len.py +68 -0
- code_towel-1.772/tests/hostile_cases/r7fz_builtins_param_named_len.py +63 -0
- code_towel-1.772/tests/hostile_cases/r7fz_builtins_print_shadowed_local.py +64 -0
- code_towel-1.772/tests/hostile_cases/r7fz_classhost_dataclass_slots_super.py +81 -0
- code_towel-1.772/tests/hostile_cases/r7fz_classhost_dunder_class_same_class.py +65 -0
- code_towel-1.772/tests/hostile_cases/r7fz_classhost_enum_methods.py +64 -0
- code_towel-1.772/tests/hostile_cases/r7fz_classhost_getattr_fallback.py +68 -0
- code_towel-1.772/tests/hostile_cases/r7fz_classhost_init_subclass_wraps.py +79 -0
- code_towel-1.772/tests/hostile_cases/r7fz_classhost_metaclass_registry.py +74 -0
- code_towel-1.772/tests/hostile_cases/r7fz_classhost_private_same_class.py +81 -0
- code_towel-1.772/tests/hostile_cases/r7fz_classhost_siblings_plain.py +69 -0
- code_towel-1.772/tests/hostile_cases/r7fz_classhost_underscore_class_names.py +82 -0
- code_towel-1.772/tests/hostile_cases/r7fz_classhost_zero_arg_super.py +71 -0
- code_towel-1.772/tests/hostile_cases/r7fz_cluster_K_cell.py +73 -0
- code_towel-1.772/tests/hostile_cases/r7fz_cluster_generator_site.py +72 -0
- code_towel-1.772/tests/hostile_cases/r7fz_cluster_local_K.py +77 -0
- code_towel-1.772/tests/hostile_cases/r7fz_cluster_method_site.py +74 -0
- code_towel-1.772/tests/hostile_cases/r7fz_ctrl_break_in_try_finally.py +72 -0
- code_towel-1.772/tests/hostile_cases/r7fz_ctrl_match_class_pattern.py +78 -0
- code_towel-1.772/tests/hostile_cases/r7fz_ctrl_nested_try.py +74 -0
- code_towel-1.772/tests/hostile_cases/r7fz_ctrl_suppress_context.py +64 -0
- code_towel-1.772/tests/hostile_cases/r7fz_ctrl_walrus_while.py +64 -0
- code_towel-1.772/tests/hostile_cases/r7fz_directives_fmt_off_region.py +69 -0
- code_towel-1.772/tests/hostile_cases/r7fz_directives_noqa_on_argument.py +57 -0
- code_towel-1.772/tests/hostile_cases/r7fz_directives_pragma_branch.py +65 -0
- code_towel-1.772/tests/hostile_cases/r7fz_directives_type_ignore_line.py +68 -0
- code_towel-1.772/tests/hostile_cases/r7fz_extra_pragma_with_header.py +70 -0
- code_towel-1.772/tests/hostile_cases/r7fz_grammar_t11254_read_before_bind.py +119 -0
- code_towel-1.772/tests/hostile_cases/r7fz_grammar_u0217_read_before_bind.py +127 -0
- code_towel-1.772/tests/hostile_cases/r7fz_grammar_u0417_augassign_after_block.py +127 -0
- code_towel-1.772/tests/hostile_cases/r7fz_grammar_u0898_for_target_prebound.py +121 -0
- code_towel-1.772/tests/hostile_cases/r7fz_late_after_block_augassign.py +64 -0
- code_towel-1.772/tests/hostile_cases/r7fz_late_after_block_del.py +64 -0
- code_towel-1.772/tests/hostile_cases/r7fz_literals_cast_literal.py +61 -0
- code_towel-1.772/tests/hostile_cases/r7fz_literals_enum_functional.py +61 -0
- code_towel-1.772/tests/hostile_cases/r7fz_literals_gettext_differ.py +60 -0
- code_towel-1.772/tests/hostile_cases/r7fz_literals_typevar_name.py +60 -0
- code_towel-1.772/tests/hostile_cases/r7fz_misc_async_no_await.py +62 -0
- code_towel-1.772/tests/hostile_cases/r7fz_misc_binder_renamed_del_message.py +62 -0
- code_towel-1.772/tests/hostile_cases/r7fz_misc_binder_renamed_unbound_message_inner.py +59 -0
- code_towel-1.772/tests/hostile_cases/r7fz_misc_block_raises_in_handler_context.py +73 -0
- code_towel-1.772/tests/hostile_cases/r7fz_misc_decorated_lru_cache.py +63 -0
- code_towel-1.772/tests/hostile_cases/r7fz_misc_except_star_reraise.py +81 -0
- code_towel-1.772/tests/hostile_cases/r7fz_misc_generator_return_block.py +71 -0
- code_towel-1.772/tests/hostile_cases/r7fz_misc_mutable_default_host.py +56 -0
- code_towel-1.772/tests/hostile_cases/r7fz_misc_nested_host_called_early.py +62 -0
- code_towel-1.772/tests/hostile_cases/r7fz_misc_super_inside_block.py +80 -0
- code_towel-1.772/tests/hostile_cases/r7fz_prebound_class_conditional_rebind.py +67 -0
- code_towel-1.772/tests/hostile_cases/r7fz_prebound_def_conditional_rebind.py +67 -0
- code_towel-1.772/tests/hostile_cases/r7fz_prebound_for_else_prebound.py +67 -0
- code_towel-1.772/tests/hostile_cases/r7fz_prebound_for_read_in_block.py +62 -0
- code_towel-1.772/tests/hostile_cases/r7fz_prebound_for_target_attr_prebound.py +63 -0
- code_towel-1.772/tests/hostile_cases/r7fz_prebound_for_tuple_target.py +61 -0
- code_towel-1.772/tests/hostile_cases/r7fz_prebound_match_capture_prebound.py +67 -0
- code_towel-1.772/tests/hostile_cases/r7fz_prebound_try_except_else_bind.py +67 -0
- code_towel-1.772/tests/hostile_cases/r7fz_prebound_while_walrus_prebound.py +61 -0
- code_towel-1.772/tests/hostile_cases/r7fz_prebound_with_enter_raises_caught.py +93 -0
- code_towel-1.772/tests/hostile_cases/r7fz_srctext_bom.py +42 -0
- code_towel-1.772/tests/hostile_cases/r7fz_srctext_cp1252_quotes.py +44 -0
- code_towel-1.772/tests/hostile_cases/r7fz_srctext_crlf.py +42 -0
- code_towel-1.772/tests/hostile_cases/r7fz_srctext_form_feed.py +44 -0
- code_towel-1.772/tests/hostile_cases/r7fz_srctext_fstring_nested_quotes.py +59 -0
- code_towel-1.772/tests/hostile_cases/r7fz_srctext_latin1_literals.py +44 -0
- code_towel-1.772/tests/hostile_cases/r7fz_srctext_named_escape.py +56 -0
- code_towel-1.772/tests/hostile_cases/r7fz_srctext_semicolons_continuations.py +60 -0
- code_towel-1.772/tests/hostile_cases/r7fz_srctext_tabs_indent.py +57 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks2_dict_unhashable.py +115 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks2_dict_unpack.py +114 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks2_lambda_default.py +115 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks2_lambda_kwdefault.py +114 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks2_list_starred.py +114 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks2_plain_first.py +112 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks2_property_first.py +114 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks2_set_hash_effect.py +114 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks2_set_unhashable.py +115 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks2_tuple_starred.py +114 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks2_two_thunks_order.py +114 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks2_undefined_global.py +120 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks_call_arg_order.py +95 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks_conditional.py +95 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks_dict_order.py +91 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks_lambda_default_first.py +93 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks_match_guard.py +99 -0
- code_towel-1.772/tests/hostile_cases/r7fz_thunks_short_circuit_and.py +91 -0
- code_towel-1.772/tests/hostile_cases/r7sp_block_ends_before_a_semicolon.py +29 -0
- code_towel-1.772/tests/hostile_cases/r7sp_block_starts_after_a_semicolon.py +33 -0
- code_towel-1.772/tests/hostile_cases/r7sp_directive_on_a_shared_line.py +38 -0
- code_towel-1.772/tests/hostile_cases/r7sp_thunk_after_a_lambda_default.py +37 -0
- code_towel-1.772/tests/hostile_cases/r7sp_thunk_after_a_raise.py +38 -0
- code_towel-1.772/tests/hostile_cases/r7sp_thunk_after_a_set_display.py +44 -0
- code_towel-1.772/tests/hostile_cases/r7sp_thunk_after_a_starred_display.py +37 -0
- code_towel-1.772/tests/hostile_cases/r7sp_thunk_after_an_unbound_global_read.py +41 -0
- code_towel-1.772/tests/hostile_cases/r80_inlined_leading_thunk.py +33 -0
- code_towel-1.772/tests/hostile_cases/r81_conditionally_bound_free_variable.py +35 -0
- code_towel-1.772/tests/hostile_cases/r82_local_classes_same_name.py +34 -0
- code_towel-1.772/tests/hostile_cases/r83_tab_indented_class.py +34 -0
- code_towel-1.772/tests/hostile_cases/r84_warn_stacklevel.py +32 -0
- code_towel-1.772/tests/hostile_cases/r85_conditionally_bound_parameter.py +39 -0
- code_towel-1.772/tests/hostile_cases/r86_annotated_assignment_live.py +40 -0
- code_towel-1.772/tests/hostile_cases/r87_nested_function_in_method.py +36 -0
- code_towel-1.772/tests/hostile_cases/r88_elif_branch.py +39 -0
- code_towel-1.772/tests/hostile_cases/r89_conditionally_bound_return.py +42 -0
- code_towel-1.772/tests/hostile_cases/r90_cluster_across_classes.py +42 -0
- code_towel-1.772/tests/hostile_cases/r91_class_body_helper.py +37 -0
- code_towel-1.772/tests/hostile_cases/r92_parameterless_class_body_helper.py +39 -0
- code_towel-1.772/tests/hostile_cases/r93_import_name_differs.py +53 -0
- code_towel-1.772/tests/hostile_cases/r94_import_binds_live_name.py +26 -0
- code_towel-1.772/tests/hostile_cases/r95_type_checking_annotation.py +32 -0
- code_towel-1.772/tests/hostile_cases/r96_unpacked_targets_live.py +33 -0
- code_towel-1.772/tests/hostile_cases/r97_helper_in_function_with_outside_site.py +35 -0
- code_towel-1.772/tests/hostile_cases/r98_same_named_methods_nested_helpers.py +51 -0
- code_towel-1.772/tests/hostile_cases/r99_partial_return_branches.py +39 -0
- code_towel-1.772/tests/hostile_cases/r9bd_augmented_builtin_read_in_block.py +53 -0
- code_towel-1.772/tests/hostile_cases/r9bd_global_bound_by_every_binder.py +135 -0
- code_towel-1.772/tests/hostile_cases/r9bd_global_declared_at_one_site.py +40 -0
- code_towel-1.772/tests/hostile_cases/r9bd_only_binding_deleted_read_after.py +61 -0
- code_towel-1.772/tests/hostile_cases/r9bd_only_binding_every_binder.py +329 -0
- code_towel-1.772/tests/hostile_cases/r9bd_only_binding_kept_local.py +72 -0
- code_towel-1.772/tests/hostile_cases/r9bd_only_binding_read_before_block.py +76 -0
- code_towel-1.772/tests/hostile_cases/r9bd_only_binding_read_by_nested_scopes.py +109 -0
- code_towel-1.772/tests/hostile_cases/r9bd_type_alias_only_binding.pynew +48 -0
- code_towel-1.772/tests/hostile_cases/r9dc_stacked_decoration_by_hand.py +90 -0
- code_towel-1.772/tests/hostile_cases/r9dr_fmt_off_layout_moves_verbatim.py +90 -0
- code_towel-1.772/tests/hostile_cases/r9dr_region_directive_around_block.py +93 -0
- code_towel-1.772/tests/hostile_cases/r9sb_binding_target_loads.py +71 -0
- code_towel-1.772/tests/hostile_cases/r9sb_fstring_format_spec.py +44 -0
- code_towel-1.772/tests/hostile_cases/r9sb_lambda_parameter_capture.py +45 -0
- code_towel-1.772/tests/hostile_cases/r9sb_thunk_meets_inlined_comprehension.py +45 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_decorator_rebound_by_another_module/enable_checks.py +40 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_decorator_rebound_by_another_module/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_decorator_rebound_by_another_module/pkg/checks.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_decorator_rebound_by_another_module/pkg/core.py +36 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_decorator_rebound_by_another_module/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_star_import_binds_an_instrumenting_decorator/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_star_import_binds_an_instrumenting_decorator/pkg/common.py +40 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_star_import_binds_an_instrumenting_decorator/pkg/shapes.py +33 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_star_import_binds_an_instrumenting_decorator/run.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_star_import_of_a_module_part_way_through_a_cycle/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_star_import_of_a_module_part_way_through_a_cycle/pkg/common.py +46 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_star_import_of_a_module_part_way_through_a_cycle/pkg/shapes.py +33 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_star_import_of_a_module_part_way_through_a_cycle/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_star_import_that_cannot_bind_a_decorator/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_star_import_that_cannot_bind_a_decorator/pkg/common.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_star_import_that_cannot_bind_a_decorator/pkg/shapes.py +41 -0
- code_towel-1.772/tests/hostile_crossfile/r9dc_star_import_that_cannot_bind_a_decorator/run.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/r9sb_lambda_capture_across_modules/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/r9sb_lambda_capture_across_modules/pkg/orders.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/r9sb_lambda_capture_across_modules/pkg/quotes.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/r9sb_lambda_capture_across_modules/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/r9sb_lambda_capture_across_modules/run.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/r9sr_inline_snapshot/inline_snapshot.py +35 -0
- code_towel-1.772/tests/hostile_crossfile/r9sr_inline_snapshot/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/r9sr_inline_snapshot/pkg/check_render.py +35 -0
- code_towel-1.772/tests/hostile_crossfile/r9sr_inline_snapshot/pytest.ini +3 -0
- code_towel-1.772/tests/hostile_crossfile/r9sr_inline_snapshot/run.py +36 -0
- code_towel-1.772/tests/hostile_crossfile/xf10_reuse_existing_function/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf10_reuse_existing_function/pkg/a.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf10_reuse_existing_function/pkg/b.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf10_reuse_existing_function/run.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf11_package_init_reaches_back/pkg/__init__.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf11_package_init_reaches_back/pkg/detail.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/xf11_package_init_reaches_back/run.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf12_cycle_through_package_init/pkg/__init__.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf12_cycle_through_package_init/pkg/core.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf12_cycle_through_package_init/pkg/sub/__init__.py +16 -0
- code_towel-1.772/tests/hostile_crossfile/xf12_cycle_through_package_init/pkg/sub/leaf.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf12_cycle_through_package_init/pkg/vendor/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf12_cycle_through_package_init/pkg/vendor/tool.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf12_cycle_through_package_init/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/xf13_import_time_effects/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf13_import_time_effects/pkg/a.py +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf13_import_time_effects/pkg/b.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf13_import_time_effects/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/xf14_script_with_leading_statement/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf14_script_with_leading_statement/pkg/app.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf14_script_with_leading_statement/pkg/util.py +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf14_script_with_leading_statement/run.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf15_ancestor_in_another_module/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf15_ancestor_in_another_module/pkg/base.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/xf15_ancestor_in_another_module/pkg/servers.py +36 -0
- code_towel-1.772/tests/hostile_crossfile/xf15_ancestor_in_another_module/pkg/tokens.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/xf15_ancestor_in_another_module/run.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/xf16_consumer_outside_target_owns_helper_name/app/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf16_consumer_outside_target_owns_helper_name/app/consumer.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf16_consumer_outside_target_owns_helper_name/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf16_consumer_outside_target_owns_helper_name/pkg/lib.py +40 -0
- code_towel-1.772/tests/hostile_crossfile/xf16_consumer_outside_target_owns_helper_name/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/xf17_builtin_shadowed_in_borrower/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf17_builtin_shadowed_in_borrower/pkg/a.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/xf17_builtin_shadowed_in_borrower/pkg/b.py +29 -0
- code_towel-1.772/tests/hostile_crossfile/xf17_builtin_shadowed_in_borrower/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/xf18_builtin_shadowed_by_star_import/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf18_builtin_shadowed_by_star_import/pkg/a.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf18_builtin_shadowed_by_star_import/pkg/b.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf18_builtin_shadowed_by_star_import/pkg/util.py +16 -0
- code_towel-1.772/tests/hostile_crossfile/xf18_builtin_shadowed_by_star_import/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/xf19_builtin_shadowed_by_borrower_local/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf19_builtin_shadowed_by_borrower_local/pkg/a.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf19_builtin_shadowed_by_borrower_local/pkg/b.py +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf19_builtin_shadowed_by_borrower_local/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/xf20_builtin_shadowed_in_ancestor_module/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf20_builtin_shadowed_in_ancestor_module/pkg/base.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf20_builtin_shadowed_in_ancestor_module/pkg/servers.py +31 -0
- code_towel-1.772/tests/hostile_crossfile/xf20_builtin_shadowed_in_ancestor_module/run.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/xf21_builtin_shadowed_in_third_module/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf21_builtin_shadowed_in_third_module/pkg/a.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf21_builtin_shadowed_in_third_module/pkg/b.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf21_builtin_shadowed_in_third_module/pkg/c.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf21_builtin_shadowed_in_third_module/pkg/util.py +16 -0
- code_towel-1.772/tests/hostile_crossfile/xf21_builtin_shadowed_in_third_module/run.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/xf22_borrower_rebinds_builtins_namespace/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf22_borrower_rebinds_builtins_namespace/pkg/a.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf22_borrower_rebinds_builtins_namespace/pkg/b.py +28 -0
- code_towel-1.772/tests/hostile_crossfile/xf22_borrower_rebinds_builtins_namespace/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/xf23_relative_import_in_another_package/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf23_relative_import_in_another_package/pkg/x/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf23_relative_import_in_another_package/pkg/x/a.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf23_relative_import_in_another_package/pkg/x/sub.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf23_relative_import_in_another_package/pkg/y/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf23_relative_import_in_another_package/pkg/y/b.py +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf23_relative_import_in_another_package/pkg/y/sub.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf23_relative_import_in_another_package/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/xf24_relative_import_climbs_elsewhere/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf24_relative_import_climbs_elsewhere/pkg/b.py +25 -0
- code_towel-1.772/tests/hostile_crossfile/xf24_relative_import_climbs_elsewhere/pkg/sub.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf24_relative_import_climbs_elsewhere/pkg/x/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf24_relative_import_climbs_elsewhere/pkg/x/a.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf24_relative_import_climbs_elsewhere/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/xf25_relative_import_in_the_same_package/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf25_relative_import_in_the_same_package/pkg/x/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf25_relative_import_in_the_same_package/pkg/x/a.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf25_relative_import_in_the_same_package/pkg/x/b.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/xf25_relative_import_in_the_same_package/pkg/x/sub.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf25_relative_import_in_the_same_package/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/xf26_relative_import_ancestor_in_another_package/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf26_relative_import_ancestor_in_another_package/pkg/x/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf26_relative_import_ancestor_in_another_package/pkg/x/base.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf26_relative_import_ancestor_in_another_package/pkg/x/settings.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf26_relative_import_ancestor_in_another_package/pkg/y/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf26_relative_import_ancestor_in_another_package/pkg/y/servers.py +33 -0
- code_towel-1.772/tests/hostile_crossfile/xf26_relative_import_ancestor_in_another_package/pkg/y/settings.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf26_relative_import_ancestor_in_another_package/run.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/xf27_registration_decorator_in_host/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf27_registration_decorator_in_host/pkg/alpha.py +30 -0
- code_towel-1.772/tests/hostile_crossfile/xf27_registration_decorator_in_host/pkg/beta.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf27_registration_decorator_in_host/pkg/registry.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf27_registration_decorator_in_host/run.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf28_registration_decorator_in_reused_module/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf28_registration_decorator_in_reused_module/pkg/alpha.py +29 -0
- code_towel-1.772/tests/hostile_crossfile/xf28_registration_decorator_in_reused_module/pkg/beta.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/xf28_registration_decorator_in_reused_module/pkg/registry.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf28_registration_decorator_in_reused_module/run.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/xf29_type_checking_block_with_branches/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf29_type_checking_block_with_branches/pkg/alpha.py +33 -0
- code_towel-1.772/tests/hostile_crossfile/xf29_type_checking_block_with_branches/pkg/beta.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf29_type_checking_block_with_branches/run.py +18 -0
- code_towel-1.772/tests/hostile_crossfile/xf30_star_importer_takes_a_typing_name/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf30_star_importer_takes_a_typing_name/pkg/a.py +39 -0
- code_towel-1.772/tests/hostile_crossfile/xf30_star_importer_takes_a_typing_name/pkg/b.py +35 -0
- code_towel-1.772/tests/hostile_crossfile/xf30_star_importer_takes_a_typing_name/run.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/xf31_package_init_star_imports_a_matcher_named_any/pkg/__init__.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf31_package_init_star_imports_a_matcher_named_any/pkg/matchers.py +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf31_package_init_star_imports_a_matcher_named_any/pkg/report.py +36 -0
- code_towel-1.772/tests/hostile_crossfile/xf31_package_init_star_imports_a_matcher_named_any/run.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/xf32_star_importer_outside_the_target/consumer.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf32_star_importer_outside_the_target/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf32_star_importer_outside_the_target/pkg/report.py +36 -0
- code_towel-1.772/tests/hostile_crossfile/xf32_star_importer_outside_the_target/run.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf3_same_named_local_function/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf3_same_named_local_function/pkg/a.py +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf3_same_named_local_function/pkg/b.py +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf3_same_named_local_function/run.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf4_same_named_class/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf4_same_named_class/pkg/a.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf4_same_named_class/pkg/b.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf4_same_named_class/run.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf5_dunder_file/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf5_dunder_file/pkg/a.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/xf5_dunder_file/pkg/b.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/xf5_dunder_file/run.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf6_same_alias_different_import/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf6_same_alias_different_import/pkg/a.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/xf6_same_alias_different_import/pkg/b.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/xf6_same_alias_different_import/pkg/util1.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf6_same_alias_different_import/pkg/util2.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf6_same_alias_different_import/run.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf7_docstring_import/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7_docstring_import/pkg/a.py +28 -0
- code_towel-1.772/tests/hostile_crossfile/xf7_docstring_import/pkg/b.py +27 -0
- code_towel-1.772/tests/hostile_crossfile/xf7_docstring_import/run.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf7c_rebinding_enclosing_function_beside_a_twin_in_another_module/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7c_rebinding_enclosing_function_beside_a_twin_in_another_module/pkg/a.py +32 -0
- code_towel-1.772/tests/hostile_crossfile/xf7c_rebinding_enclosing_function_beside_a_twin_in_another_module/pkg/b.py +37 -0
- code_towel-1.772/tests/hostile_crossfile/xf7c_rebinding_enclosing_function_beside_a_twin_in_another_module/run.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/xf7d_assert_moves_to_a_module_pytest_does_not_rewrite/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7d_assert_moves_to_a_module_pytest_does_not_rewrite/pkg/check_values.py +27 -0
- code_towel-1.772/tests/hostile_crossfile/xf7d_assert_moves_to_a_module_pytest_does_not_rewrite/pkg/checks.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/xf7d_assert_moves_to_a_module_pytest_does_not_rewrite/pytest.ini +3 -0
- code_towel-1.772/tests/hostile_crossfile/xf7d_assert_moves_to_a_module_pytest_does_not_rewrite/run.py +43 -0
- code_towel-1.772/tests/hostile_crossfile/xf7d_asserts_shared_by_modules_rewritten_alike/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7d_asserts_shared_by_modules_rewritten_alike/pkg/alpha.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf7d_asserts_shared_by_modules_rewritten_alike/pkg/beta.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7d_asserts_shared_by_modules_rewritten_alike/pkg/check_suite.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf7d_asserts_shared_by_modules_rewritten_alike/pytest.ini +3 -0
- code_towel-1.772/tests/hostile_crossfile/xf7d_asserts_shared_by_modules_rewritten_alike/run.py +42 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_binding_x_class_level_name/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_binding_x_class_level_name/pkg/a.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_binding_x_class_level_name/pkg/b.py +30 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_binding_x_class_level_name/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_binding_x_class_level_name/run.py +44 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_binding_x_dunder_file/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_binding_x_dunder_file/pkg/a.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_binding_x_dunder_file/pkg/b.py +30 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_binding_x_dunder_file/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_binding_x_dunder_file/run.py +44 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_builtins_x_shadow_in_b/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_builtins_x_shadow_in_b/pkg/a.py +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_builtins_x_shadow_in_b/pkg/b.py +29 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_builtins_x_shadow_in_b/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_builtins_x_shadow_in_b/run.py +44 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_binder_message_mypy/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_binder_message_mypy/pkg/m.py +30 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_binder_message_mypy/pyproject.toml +7 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_binder_message_mypy/run.py +43 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_binder_message_pyright/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_binder_message_pyright/pkg/m.py +30 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_binder_message_pyright/pyproject.toml +7 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_binder_message_pyright/run.py +43 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_read_before_bind_mypy/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_read_before_bind_mypy/pkg/m.py +30 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_read_before_bind_mypy/pyproject.toml +7 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_read_before_bind_mypy/run.py +45 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_read_before_bind_pyright/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_read_before_bind_pyright/pkg/m.py +30 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_read_before_bind_pyright/pyproject.toml +7 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_read_before_bind_pyright/run.py +45 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_thunk_lambda_default_mypy/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_thunk_lambda_default_mypy/pkg/m.py +49 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_thunk_lambda_default_mypy/pyproject.toml +7 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_thunk_lambda_default_mypy/run.py +44 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_thunk_lambda_default_pyright/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_thunk_lambda_default_pyright/pkg/m.py +49 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_thunk_lambda_default_pyright/pyproject.toml +7 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_extra_typed_thunk_lambda_default_pyright/run.py +44 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_t11094_semicolon/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_t11094_semicolon/pkg/m.py +102 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_t11094_semicolon/pyproject.toml +7 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_t11094_semicolon/run.py +47 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_t11271_semicolon/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_t11271_semicolon/pkg/m.py +110 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_t11271_semicolon/pyproject.toml +10 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_t11271_semicolon/run.py +47 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u0474_for_target_prebound/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u0474_for_target_prebound/pkg/a.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u0474_for_target_prebound/pkg/b.py +27 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u0474_for_target_prebound/pkg/common.py +52 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u0474_for_target_prebound/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u0474_for_target_prebound/run.py +53 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u0624_for_target_prebound/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u0624_for_target_prebound/pkg/a.py +33 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u0624_for_target_prebound/pkg/b.py +34 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u0624_for_target_prebound/pkg/common.py +52 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u0624_for_target_prebound/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u0624_for_target_prebound/run.py +53 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u1209_for_target_prebound/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u1209_for_target_prebound/pkg/a.py +33 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u1209_for_target_prebound/pkg/b.py +34 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u1209_for_target_prebound/pkg/common.py +52 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u1209_for_target_prebound/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_grammar_u1209_for_target_prebound/run.py +53 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_late_toplevel_module_in_package/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_late_toplevel_module_in_package/pkg/a.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_late_toplevel_module_in_package/pkg/c.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_late_toplevel_module_in_package/pkg/helpers_top.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_late_toplevel_module_in_package/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_late_toplevel_module_in_package/run.py +50 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_late_toplevel_module_in_package/tests/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_late_toplevel_module_in_package/tests/test_x.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_misc_typed_for_prebound_mypy/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_misc_typed_for_prebound_mypy/pkg/m.py +30 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_misc_typed_for_prebound_mypy/pyproject.toml +7 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_misc_typed_for_prebound_mypy/run.py +43 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_b_imports_a/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_b_imports_a/pkg/a.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_b_imports_a/pkg/b.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_b_imports_a/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_b_imports_a/run.py +56 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_init_hosts/pkg/__init__.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_init_hosts/pkg/a.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_init_hosts/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_init_hosts/run.py +56 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_rel_import_in_block/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_rel_import_in_block/pkg/a.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_rel_import_in_block/pkg/b.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_rel_import_in_block/pkg/conf.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_rel_import_in_block/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_rel_import_in_block/run.py +57 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_script_main_guard/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_script_main_guard/pkg/a.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_script_main_guard/pkg/b.py +27 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_script_main_guard/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_script_main_guard/run.py +68 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_three_modules_cluster/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_three_modules_cluster/pkg/a.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_three_modules_cluster/pkg/b.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_three_modules_cluster/pkg/c.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_three_modules_cluster/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_modules_three_modules_cluster/run.py +64 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_both/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_both/pkg/compat.py +16 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_both/pkg/compat_false.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_both/pkg/f.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_both/pkg/m.py +42 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_both/pkg/w.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_both/pyproject.toml +10 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_both/run.py +47 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_both/tests/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_both/tests/test_w.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_both/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_both/pkg/compat.py +16 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_both/pkg/compat_false.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_both/pkg/f.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_both/pkg/m.py +41 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_both/pkg/w.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_both/pyproject.toml +10 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_both/run.py +47 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_both/tests/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_both/tests/test_w.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_mypy/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_mypy/pkg/compat.py +16 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_mypy/pkg/compat_false.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_mypy/pkg/f.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_mypy/pkg/m.py +41 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_mypy/pkg/w.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_mypy/pyproject.toml +7 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_mypy/run.py +47 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_mypy/tests/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_late_use_mypy/tests/test_w.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_mypy/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_mypy/pkg/compat.py +16 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_mypy/pkg/compat_false.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_mypy/pkg/f.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_mypy/pkg/m.py +42 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_mypy/pkg/w.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_mypy/pyproject.toml +7 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_mypy/run.py +47 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_mypy/tests/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_from_compat_true_mypy/tests/test_w.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_both/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_both/pkg/compat.py +16 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_both/pkg/compat_false.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_both/pkg/f.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_both/pkg/m.py +41 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_both/pkg/w.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_both/pyproject.toml +10 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_both/run.py +47 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_both/tests/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_both/tests/test_w.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_mypy/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_mypy/pkg/compat.py +16 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_mypy/pkg/compat_false.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_mypy/pkg/f.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_mypy/pkg/m.py +41 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_mypy/pkg/w.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_mypy/pyproject.toml +7 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_mypy/run.py +47 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_mypy/tests/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7fz_typeguard2_import_alias_flag_mypy/tests/test_w.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_host_the_wheel_leaves_out/pkg/pyproject.toml +11 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_host_the_wheel_leaves_out/pkg/src/shop/__init__.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_host_the_wheel_leaves_out/pkg/src/shop/_devtools.py +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_host_the_wheel_leaves_out/pkg/src/shop/stats.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_host_the_wheel_leaves_out/run.py +38 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_namesake_of_a_required_library/pkg/pyproject.toml +7 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_namesake_of_a_required_library/pkg/zzapp/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_namesake_of_a_required_library/pkg/zzapp/core.py +25 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_namesake_of_a_required_library/pkg/zzlib/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_namesake_of_a_required_library/pkg/zzlib/utils.py +25 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_namesake_of_a_required_library/run.py +27 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_namesake_of_a_required_library/site-packages/zzlib/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_namesake_of_a_required_library/site-packages/zzlib/utils.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_subpackage_the_wheel_leaves_out/pkg/pyproject.toml +11 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_subpackage_the_wheel_leaves_out/pkg/src/shop/__init__.py +15 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_subpackage_the_wheel_leaves_out/pkg/src/shop/cli.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_subpackage_the_wheel_leaves_out/pkg/src/shop/devtools/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_subpackage_the_wheel_leaves_out/pkg/src/shop/devtools/dump.py +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_subpackage_the_wheel_leaves_out/pkg/src/shop/stats.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/xf7n_subpackage_the_wheel_leaves_out/run.py +39 -0
- code_towel-1.772/tests/hostile_crossfile/xf7t_import_order_is_registration_order/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7t_import_order_is_registration_order/pkg/alpha.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf7t_import_order_is_registration_order/pkg/app.py +36 -0
- code_towel-1.772/tests/hostile_crossfile/xf7t_import_order_is_registration_order/pkg/beta.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf7t_import_order_is_registration_order/pkg/gamma.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf7t_import_order_is_registration_order/pkg/loader.py +36 -0
- code_towel-1.772/tests/hostile_crossfile/xf7t_import_order_is_registration_order/pkg/registry.py +19 -0
- code_towel-1.772/tests/hostile_crossfile/xf7t_import_order_is_registration_order/pkg/zeta.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf7t_import_order_is_registration_order/pyproject.toml +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf7t_import_order_is_registration_order/run.py +21 -0
- code_towel-1.772/tests/hostile_crossfile/xf8_helper_name_collision/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf8_helper_name_collision/pkg/a.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf8_helper_name_collision/pkg/b.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf8_helper_name_collision/pkg/c.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf8_helper_name_collision/pkg/d.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf8_helper_name_collision/pkg/e.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf8_helper_name_collision/run.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf9_same_named_base_class/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf9_same_named_base_class/pkg/one/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf9_same_named_base_class/pkg/one/base.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf9_same_named_base_class/pkg/one/introspect.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf9_same_named_base_class/pkg/one/revoke.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf9_same_named_base_class/pkg/two/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf9_same_named_base_class/pkg/two/access.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf9_same_named_base_class/pkg/two/base.py +17 -0
- code_towel-1.772/tests/hostile_crossfile/xf9_same_named_base_class/pkg/two/request.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf9_same_named_base_class/run.py +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf9bd_only_binding_across_modules/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf9bd_only_binding_across_modules/pkg/a.py +28 -0
- code_towel-1.772/tests/hostile_crossfile/xf9bd_only_binding_across_modules/pkg/b.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf9bd_only_binding_across_modules/run.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_hides_hand_decoration/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_hides_hand_decoration/pkg/fast.pynew +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_hides_hand_decoration/pkg/instrument.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_hides_hand_decoration/pkg/kernels.py +34 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_hides_hand_decoration/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_hides_hand_decoration/run.py +23 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_test_hides_builtin_patch/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_test_hides_builtin_patch/pkg/a.py +26 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_test_hides_builtin_patch/pkg/b.py +20 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_test_hides_builtin_patch/pyproject.toml +4 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_test_hides_builtin_patch/run.py +25 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_test_hides_builtin_patch/tests/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf9up_newer_syntax_test_hides_builtin_patch/tests/test_b.pynew +32 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_namesake_lacking_a_module/pkg/pyproject.toml +8 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_namesake_lacking_a_module/pkg/third_party/zzlib/utils.py +25 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_namesake_lacking_a_module/pkg/zzapp/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_namesake_lacking_a_module/pkg/zzapp/cli.py +22 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_namesake_lacking_a_module/pkg/zzapp/core.py +25 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_namesake_lacking_a_module/run.py +27 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_namesake_lacking_a_module/site-packages/zzlib/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_namesake_lacking_a_module/site-packages/zzlib/core.py +16 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_namesake_lacking_a_module/site-packages/zzlib/utils.py +16 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_type_only_import_of_a_missing_module/pkg/__init__.py +13 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_type_only_import_of_a_missing_module/pkg/a.py +28 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_type_only_import_of_a_missing_module/pkg/b.py +24 -0
- code_towel-1.772/tests/hostile_crossfile/xf9xi_type_only_import_of_a_missing_module/run.py +18 -0
- code_towel-1.772/tests/hostile_execution.py +343 -0
- code_towel-1.772/tests/hostile_refactoring.py +131 -0
- code_towel-1.772/tests/probe_answers.py +54 -0
- code_towel-1.772/tests/run_tests.py +45 -0
- code_towel-1.772/tests/test_adversarial_renaming.py +342 -0
- code_towel-1.772/tests/test_adversarial_semantics.py +387 -0
- code_towel-1.772/tests/test_analysis_edge_paths.py +177 -0
- code_towel-1.772/tests/test_analysis_sessions.py +278 -0
- code_towel-1.772/tests/test_annotation_imports.py +510 -0
- code_towel-1.772/tests/test_annotation_ladder.py +164 -0
- code_towel-1.772/tests/test_annotation_ladder_rules.py +437 -0
- code_towel-1.772/tests/test_annotation_runtime_effects.py +245 -0
- code_towel-1.772/tests/test_annotations_for_the_oldest_python.py +259 -0
- code_towel-1.772/tests/test_assert_rewriting.py +239 -0
- code_towel-1.772/tests/test_assignment_analyzer_comprehensive.py +693 -0
- code_towel-1.772/tests/test_attribute_declaration_order.py +91 -0
- code_towel-1.772/tests/test_audit_io_safety.py +358 -0
- code_towel-1.772/tests/test_baseline_shapes.py +438 -0
- code_towel-1.772/tests/test_binding_and_read_collectors.py +474 -0
- code_towel-1.772/tests/test_binding_collectors_match_cpython.py +955 -0
- code_towel-1.772/tests/test_binding_context_memo.py +72 -0
- code_towel-1.772/tests/test_binding_detector.py +302 -0
- code_towel-1.772/tests/test_binding_detector_edge_cases.py +392 -0
- code_towel-1.772/tests/test_bindings.py +271 -0
- code_towel-1.772/tests/test_bindings_additional.py +110 -0
- code_towel-1.772/tests/test_block_comments.py +652 -0
- code_towel-1.772/tests/test_block_site_caches.py +213 -0
- code_towel-1.772/tests/test_breakers.py +133 -0
- code_towel-1.772/tests/test_builtins_across_modules.py +588 -0
- code_towel-1.772/tests/test_builtins_comprehensive.py +412 -0
- code_towel-1.772/tests/test_cache_lifetimes.py +86 -0
- code_towel-1.772/tests/test_candidate_index.py +223 -0
- code_towel-1.772/tests/test_canonical_ast.py +353 -0
- code_towel-1.772/tests/test_change_transactions.py +321 -0
- code_towel-1.772/tests/test_checker_failure_reporting.py +182 -0
- code_towel-1.772/tests/test_checker_reports.py +142 -0
- code_towel-1.772/tests/test_checker_snapshot_follows_project.py +160 -0
- code_towel-1.772/tests/test_class_object_annotations.py +146 -0
- code_towel-1.772/tests/test_class_private_helpers.py +579 -0
- code_towel-1.772/tests/test_class_private_positions.py +300 -0
- code_towel-1.772/tests/test_cli_dispatch_paths.py +105 -0
- code_towel-1.772/tests/test_cli_input_errors.py +100 -0
- code_towel-1.772/tests/test_cli_integration.py +316 -0
- code_towel-1.772/tests/test_cli_oracle_lifecycle.py +117 -0
- code_towel-1.772/tests/test_cli_output_safety.py +65 -0
- code_towel-1.772/tests/test_cold_confirmation.py +321 -0
- code_towel-1.772/tests/test_complete_check_scope.py +468 -0
- code_towel-1.772/tests/test_comprehensive_coverage.py +307 -0
- code_towel-1.772/tests/test_configured_checker_must_run.py +148 -0
- code_towel-1.772/tests/test_consumer_reach.py +250 -0
- code_towel-1.772/tests/test_copy_preservation.py +52 -0
- code_towel-1.772/tests/test_coverage_config.py +254 -0
- code_towel-1.772/tests/test_created_object_escapes.py +172 -0
- code_towel-1.772/tests/test_cross_distribution.py +134 -0
- code_towel-1.772/tests/test_cross_module_opt_in.py +200 -0
- code_towel-1.772/tests/test_crossfile_integration.py +470 -0
- code_towel-1.772/tests/test_crossfile_observational_equivalence.py +42 -0
- code_towel-1.772/tests/test_decline_traces.py +260 -0
- code_towel-1.772/tests/test_declined_run_reports.py +269 -0
- code_towel-1.772/tests/test_decorator_reach.py +1515 -0
- code_towel-1.772/tests/test_deeply_nested_source.py +58 -0
- code_towel-1.772/tests/test_definite_assignment.py +236 -0
- code_towel-1.772/tests/test_definite_assignment_runtime.py +110 -0
- code_towel-1.772/tests/test_dependency_audit_export.py +95 -0
- code_towel-1.772/tests/test_differential_baseline.py +922 -0
- code_towel-1.772/tests/test_differential_grammar.py +207 -0
- code_towel-1.772/tests/test_differential_scope_family.py +55 -0
- code_towel-1.772/tests/test_distinct_proposals.py +102 -0
- code_towel-1.772/tests/test_ecosystem_environment.py +764 -0
- code_towel-1.772/tests/test_ecosystem_manifest_regressions.py +103 -0
- code_towel-1.772/tests/test_ecosystem_tool_pins.py +280 -0
- code_towel-1.772/tests/test_ecosystem_typing_deps.py +402 -0
- code_towel-1.772/tests/test_ecosystem_verdicts.py +1517 -0
- code_towel-1.772/tests/test_ecosystem_work_isolation.py +140 -0
- code_towel-1.772/tests/test_effect_free.py +360 -0
- code_towel-1.772/tests/test_engine_adversarial.py +1358 -0
- code_towel-1.772/tests/test_engine_failure_visibility.py +48 -0
- code_towel-1.772/tests/test_environment_independence.py +377 -0
- code_towel-1.772/tests/test_environment_inside_the_root.py +283 -0
- code_towel-1.772/tests/test_equivalence_harness_regressions.py +175 -0
- code_towel-1.772/tests/test_equivalence_target_selection.py +172 -0
- code_towel-1.772/tests/test_error_paths.py +292 -0
- code_towel-1.772/tests/test_exceptions.py +30 -0
- code_towel-1.772/tests/test_extractor_additional_branches.py +130 -0
- code_towel-1.772/tests/test_extractor_augassign_and_fstrings.py +126 -0
- code_towel-1.772/tests/test_extractor_callee_and_multi_return.py +137 -0
- code_towel-1.772/tests/test_extractor_comprehensive.py +569 -0
- code_towel-1.772/tests/test_extractor_edge_cases.py +369 -0
- code_towel-1.772/tests/test_extractor_final_lines.py +165 -0
- code_towel-1.772/tests/test_extractor_generate_call_variants.py +96 -0
- code_towel-1.772/tests/test_extractor_more_paths.py +137 -0
- code_towel-1.772/tests/test_extractor_preamble_and_call_mapping.py +151 -0
- code_towel-1.772/tests/test_extractor_remaining_branches.py +273 -0
- code_towel-1.772/tests/test_extractor_return_statements.py +341 -0
- code_towel-1.772/tests/test_extractor_substituter_and_helpers.py +425 -0
- code_towel-1.772/tests/test_file_under_two_names.py +308 -0
- code_towel-1.772/tests/test_filesystem_guards.py +87 -0
- code_towel-1.772/tests/test_final_coverage_push.py +262 -0
- code_towel-1.772/tests/test_fixed_point_refusals.py +161 -0
- code_towel-1.772/tests/test_forwarder_chains.py +635 -0
- code_towel-1.772/tests/test_frame_sensitivity_scan.py +83 -0
- code_towel-1.772/tests/test_free_variable_correspondence.py +285 -0
- code_towel-1.772/tests/test_fstrings.py +188 -0
- code_towel-1.772/tests/test_fully_annotated_modules.py +80 -0
- code_towel-1.772/tests/test_function_facts_equivalence.py +93 -0
- code_towel-1.772/tests/test_function_index.py +90 -0
- code_towel-1.772/tests/test_functions_keep_their_own_bodies.py +193 -0
- code_towel-1.772/tests/test_generated_code_formatting.py +433 -0
- code_towel-1.772/tests/test_generic_body_annotations.py +308 -0
- code_towel-1.772/tests/test_generic_expression_types.py +313 -0
- code_towel-1.772/tests/test_generic_extraction.py +408 -0
- code_towel-1.772/tests/test_generic_method_boundaries.py +305 -0
- code_towel-1.772/tests/test_generic_methods.py +285 -0
- code_towel-1.772/tests/test_generic_probe_positions.py +110 -0
- code_towel-1.772/tests/test_generic_verification.py +365 -0
- code_towel-1.772/tests/test_helper_annotations.py +458 -0
- code_towel-1.772/tests/test_helper_hosts.py +147 -0
- code_towel-1.772/tests/test_helper_inventory.py +230 -0
- code_towel-1.772/tests/test_helpers.py +282 -0
- code_towel-1.772/tests/test_helpers_that_compute_nothing.py +151 -0
- code_towel-1.772/tests/test_host_left_out_of_the_build.py +399 -0
- code_towel-1.772/tests/test_host_ships_with_borrower.py +236 -0
- code_towel-1.772/tests/test_hostile_battery.py +433 -0
- code_towel-1.772/tests/test_hostile_crossfile_battery.py +342 -0
- code_towel-1.772/tests/test_hosting_rationale.py +399 -0
- code_towel-1.772/tests/test_import_bindings.py +112 -0
- code_towel-1.772/tests/test_import_cycle_package_init.py +71 -0
- code_towel-1.772/tests/test_import_cycles_by_the_program.py +141 -0
- code_towel-1.772/tests/test_import_model.py +1187 -0
- code_towel-1.772/tests/test_import_name_corroboration.py +466 -0
- code_towel-1.772/tests/test_import_position.py +214 -0
- code_towel-1.772/tests/test_import_problem_refusal.py +673 -0
- code_towel-1.772/tests/test_import_resolution.py +80 -0
- code_towel-1.772/tests/test_import_sorting_scope.py +310 -0
- code_towel-1.772/tests/test_import_time_code.py +417 -0
- code_towel-1.772/tests/test_import_verdicts_hold_at_run_time.py +388 -0
- code_towel-1.772/tests/test_in_place_stage.py +261 -0
- code_towel-1.772/tests/test_incremental_global_passes.py +155 -0
- code_towel-1.772/tests/test_instantiation_by_binding.py +236 -0
- code_towel-1.772/tests/test_instantiation_check.py +380 -0
- code_towel-1.772/tests/test_known_bases.py +122 -0
- code_towel-1.772/tests/test_known_platforms.py +125 -0
- code_towel-1.772/tests/test_ladder_hygiene.py +310 -0
- code_towel-1.772/tests/test_lazy_engine_import.py +49 -0
- code_towel-1.772/tests/test_lexical_binding_regressions.py +217 -0
- code_towel-1.772/tests/test_licence_headers.py +58 -0
- code_towel-1.772/tests/test_memoization_is_invisible.py +274 -0
- code_towel-1.772/tests/test_method_helper_hosting.py +352 -0
- code_towel-1.772/tests/test_method_host_machinery.py +534 -0
- code_towel-1.772/tests/test_method_level_and_attributes.py +83 -0
- code_towel-1.772/tests/test_method_receiver_arguments.py +54 -0
- code_towel-1.772/tests/test_missing_submodules.py +213 -0
- code_towel-1.772/tests/test_module_names_stay_free.py +133 -0
- code_towel-1.772/tests/test_moved_comments.py +1312 -0
- code_towel-1.772/tests/test_mypy_judged_as_configured.py +752 -0
- code_towel-1.772/tests/test_mypy_matches_its_own_run.py +326 -0
- code_towel-1.772/tests/test_mypy_plugins.py +278 -0
- code_towel-1.772/tests/test_mypy_worker_isolation.py +265 -0
- code_towel-1.772/tests/test_names_typed_any.py +362 -0
- code_towel-1.772/tests/test_narrowing_refusal.py +263 -0
- code_towel-1.772/tests/test_no_builtin_arguments.py +454 -0
- code_towel-1.772/tests/test_no_quoted_none.py +56 -0
- code_towel-1.772/tests/test_noop_application.py +86 -0
- code_towel-1.772/tests/test_observational_equivalence.py +922 -0
- code_towel-1.772/tests/test_only_binding_moves.py +245 -0
- code_towel-1.772/tests/test_orphan_detection.py +268 -0
- code_towel-1.772/tests/test_orphan_detector_comprehensive.py +604 -0
- code_towel-1.772/tests/test_out_of_place_cycle_regression.py +181 -0
- code_towel-1.772/tests/test_out_of_place_stage.py +141 -0
- code_towel-1.772/tests/test_out_of_place_whole_project.py +279 -0
- code_towel-1.772/tests/test_output_import_names.py +124 -0
- code_towel-1.772/tests/test_overlap_filtering.py +389 -0
- code_towel-1.772/tests/test_pair_budget.py +125 -0
- code_towel-1.772/tests/test_parallel_evaluation.py +230 -0
- code_towel-1.772/tests/test_parameterize_builtins.py +572 -0
- code_towel-1.772/tests/test_parameters.py +45 -0
- code_towel-1.772/tests/test_parameters_stand_alone.py +170 -0
- code_towel-1.772/tests/test_parent_watchdog.py +124 -0
- code_towel-1.772/tests/test_pending_journal_rule.py +353 -0
- code_towel-1.772/tests/test_perf_memos.py +252 -0
- code_towel-1.772/tests/test_pipeline_api.py +71 -0
- code_towel-1.772/tests/test_pipeline_phases.py +363 -0
- code_towel-1.772/tests/test_platform_only_hosts.py +255 -0
- code_towel-1.772/tests/test_program_imports.py +150 -0
- code_towel-1.772/tests/test_program_type_names.py +901 -0
- code_towel-1.772/tests/test_progress_heartbeat.py +194 -0
- code_towel-1.772/tests/test_progress_modes.py +177 -0
- code_towel-1.772/tests/test_project_helper_names.py +145 -0
- code_towel-1.772/tests/test_project_layout_and_imports.py +225 -0
- code_towel-1.772/tests/test_project_layout_behavior.py +109 -0
- code_towel-1.772/tests/test_project_layout_shim.py +27 -0
- code_towel-1.772/tests/test_promotion_and_mangling.py +207 -0
- code_towel-1.772/tests/test_properties.py +1202 -0
- code_towel-1.772/tests/test_pyright_configuration_grammar.py +238 -0
- code_towel-1.772/tests/test_pyright_editable_installs.py +155 -0
- code_towel-1.772/tests/test_pyright_probes_stay_private.py +122 -0
- code_towel-1.772/tests/test_pyright_scope.py +216 -0
- code_towel-1.772/tests/test_pyright_server_configuration.py +349 -0
- code_towel-1.772/tests/test_pyright_session.py +321 -0
- code_towel-1.772/tests/test_r9my_check_fails_unchanged.py +116 -0
- code_towel-1.772/tests/test_r9my_check_scope.py +203 -0
- code_towel-1.772/tests/test_r9my_environment_by_content.py +162 -0
- code_towel-1.772/tests/test_r9my_mypy_config_groups.py +209 -0
- code_towel-1.772/tests/test_r9my_mypy_jurisdiction.py +238 -0
- code_towel-1.772/tests/test_r9py_pyright_config_groups.py +149 -0
- code_towel-1.772/tests/test_r9py_pyright_config_venv.py +155 -0
- code_towel-1.772/tests/test_r9py_pyright_copy_inputs.py +167 -0
- code_towel-1.772/tests/test_r9py_pyright_fork_guard.py +157 -0
- code_towel-1.772/tests/test_r9py_pyright_marker_outside_include.py +106 -0
- code_towel-1.772/tests/test_r9py_pyright_message_paths.py +149 -0
- code_towel-1.772/tests/test_readme_links.py +75 -0
- code_towel-1.772/tests/test_receiver_dependency.py +132 -0
- code_towel-1.772/tests/test_recovery_journal.py +516 -0
- code_towel-1.772/tests/test_refactor_engine_comprehensive.py +523 -0
- code_towel-1.772/tests/test_refactor_engine_edge_cases.py +337 -0
- code_towel-1.772/tests/test_refactor_engine_more.py +138 -0
- code_towel-1.772/tests/test_refactor_engine_targeted_branches.py +383 -0
- code_towel-1.772/tests/test_refactoring_engine.py +268 -0
- code_towel-1.772/tests/test_region_directives.py +633 -0
- code_towel-1.772/tests/test_regression.py +495 -0
- code_towel-1.772/tests/test_regressions_broader.py +174 -0
- code_towel-1.772/tests/test_rejected_proposal_memory.py +212 -0
- code_towel-1.772/tests/test_release_regressions.py +157 -0
- code_towel-1.772/tests/test_rename_audit_regressions.py +261 -0
- code_towel-1.772/tests/test_rename_by_the_programs_names.py +117 -0
- code_towel-1.772/tests/test_rename_class_private_helpers.py +368 -0
- code_towel-1.772/tests/test_rename_discovery_regressions.py +189 -0
- code_towel-1.772/tests/test_rename_parameters_and_methods.py +144 -0
- code_towel-1.772/tests/test_renamed_binder_observability_runtime.py +240 -0
- code_towel-1.772/tests/test_required_namesakes.py +619 -0
- code_towel-1.772/tests/test_return_alignment.py +58 -0
- code_towel-1.772/tests/test_return_values.py +108 -0
- code_towel-1.772/tests/test_returned_variables_not_orphaned.py +112 -0
- code_towel-1.772/tests/test_reuse_existing_function.py +332 -0
- code_towel-1.772/tests/test_reuse_type_checks.py +132 -0
- code_towel-1.772/tests/test_revealed_spellings.py +249 -0
- code_towel-1.772/tests/test_robustness_paths.py +212 -0
- code_towel-1.772/tests/test_run_by_path.py +225 -0
- code_towel-1.772/tests/test_run_lookups.py +188 -0
- code_towel-1.772/tests/test_scope_analyzer.py +248 -0
- code_towel-1.772/tests/test_scope_analyzer_comprehensive.py +786 -0
- code_towel-1.772/tests/test_sdist_manifest.py +132 -0
- code_towel-1.772/tests/test_self_outside_its_class.py +70 -0
- code_towel-1.772/tests/test_semantic_guard_traces.py +204 -0
- code_towel-1.772/tests/test_semantic_guards_extended.py +215 -0
- code_towel-1.772/tests/test_semantic_safety_regressions.py +407 -0
- code_towel-1.772/tests/test_shared_line_directives.py +195 -0
- code_towel-1.772/tests/test_shipped_files_recorded.py +346 -0
- code_towel-1.772/tests/test_sidecar_records_applied_helpers.py +96 -0
- code_towel-1.772/tests/test_signature_prefilter.py +79 -0
- code_towel-1.772/tests/test_source_encoding.py +178 -0
- code_towel-1.772/tests/test_source_reading_callees.py +440 -0
- code_towel-1.772/tests/test_speculative_cache_isolation.py +137 -0
- code_towel-1.772/tests/test_splicing.py +420 -0
- code_towel-1.772/tests/test_stale_proposal_recovery.py +223 -0
- code_towel-1.772/tests/test_statement_facts.py +276 -0
- code_towel-1.772/tests/test_statically_read_literals.py +498 -0
- code_towel-1.772/tests/test_structural_memo.py +122 -0
- code_towel-1.772/tests/test_stub_resolution.py +111 -0
- code_towel-1.772/tests/test_stubbed_host.py +125 -0
- code_towel-1.772/tests/test_substitution_follows_bindings.py +298 -0
- code_towel-1.772/tests/test_substitution_keys.py +60 -0
- code_towel-1.772/tests/test_super_in_method_helpers.py +992 -0
- code_towel-1.772/tests/test_symlinked_input_directory.py +132 -0
- code_towel-1.772/tests/test_symlinked_project_paths.py +235 -0
- code_towel-1.772/tests/test_targeted_rung.py +191 -0
- code_towel-1.772/tests/test_thunk_inlining.py +190 -0
- code_towel-1.772/tests/test_thunks_meet_inlined_comprehensions.py +129 -0
- code_towel-1.772/tests/test_top_level_module_inside_package.py +247 -0
- code_towel-1.772/tests/test_trivial_helper_filter.py +130 -0
- code_towel-1.772/tests/test_type_bindings.py +561 -0
- code_towel-1.772/tests/test_type_checking_import_cycles.py +329 -0
- code_towel-1.772/tests/test_type_checking_imports.py +427 -0
- code_towel-1.772/tests/test_type_declaration_materialization.py +389 -0
- code_towel-1.772/tests/test_type_generalization.py +253 -0
- code_towel-1.772/tests/test_type_inference.py +679 -0
- code_towel-1.772/tests/test_type_only_imports.py +221 -0
- code_towel-1.772/tests/test_type_oracle_projects.py +568 -0
- code_towel-1.772/tests/test_type_run_policy.py +429 -0
- code_towel-1.772/tests/test_typing_form_resolution.py +207 -0
- code_towel-1.772/tests/test_typing_forms_cost.py +55 -0
- code_towel-1.772/tests/test_typing_forms_resolved.py +496 -0
- code_towel-1.772/tests/test_unchecked_code.py +372 -0
- code_towel-1.772/tests/test_unconfigured_mypy.py +476 -0
- code_towel-1.772/tests/test_unifier_additional_branches.py +103 -0
- code_towel-1.772/tests/test_unifier_batch2.py +144 -0
- code_towel-1.772/tests/test_unifier_batch3.py +183 -0
- code_towel-1.772/tests/test_unifier_batch4.py +116 -0
- code_towel-1.772/tests/test_unifier_batch5.py +119 -0
- code_towel-1.772/tests/test_unifier_bound_variables.py +360 -0
- code_towel-1.772/tests/test_unifier_comprehensions.py +76 -0
- code_towel-1.772/tests/test_unifier_comprehensive.py +360 -0
- code_towel-1.772/tests/test_unifier_core.py +309 -0
- code_towel-1.772/tests/test_unifier_edge_cases.py +205 -0
- code_towel-1.772/tests/test_unifier_more_branches.py +222 -0
- code_towel-1.772/tests/test_unifier_state_reset.py +107 -0
- code_towel-1.772/tests/test_unparsed_program.py +622 -0
- code_towel-1.772/tests/test_unreadable_and_revealed.py +189 -0
- code_towel-1.772/tests/test_unsupported_layout.py +113 -0
- code_towel-1.772/tests/test_untypeable_extractions.py +229 -0
- code_towel-1.772/tests/test_validation_partial_lifetime.py +169 -0
- code_towel-1.772/tests/test_variable_capture_bug.py +223 -0
- code_towel-1.772/tests/test_visitors.py +419 -0
- code_towel-1.772/tests/test_workers_fork_alone.py +493 -0
- code_towel-1.772/tests/typed_fixtures.py +131 -0
- code_towel-1.772/uv.lock +1554 -0
- code_towel-1.732/CHANGELOG.md +0 -770
- code_towel-1.732/CONTRIBUTING.md +0 -209
- code_towel-1.732/MANIFEST.in +0 -16
- code_towel-1.732/PKG-INFO +0 -378
- code_towel-1.732/README.md +0 -333
- code_towel-1.732/SECURITY.md +0 -19
- code_towel-1.732/docs/ADVERSARIAL_REVIEW.md +0 -155
- code_towel-1.732/docs/ARCHITECTURE.md +0 -898
- code_towel-1.732/docs/KNOWN_LIMITATIONS.md +0 -565
- code_towel-1.732/docs/OPEN_SOURCE_AUDIT.md +0 -68
- code_towel-1.732/docs/PRODUCTION_READINESS.md +0 -697
- code_towel-1.732/docs/QUICKSTART.md +0 -84
- code_towel-1.732/docs/RELEASE_LOG.md +0 -344
- code_towel-1.732/docs/RELEASING.md +0 -126
- code_towel-1.732/docs/USAGE_GUIDE.md +0 -467
- code_towel-1.732/docs/proposals/performance-roadmap.md +0 -144
- code_towel-1.732/docs/proposals/reuse-existing-function.md +0 -171
- code_towel-1.732/docs/proposals/template-method.md +0 -157
- code_towel-1.732/docs/proposals/type-parameters.md +0 -322
- code_towel-1.732/justfile +0 -172
- code_towel-1.732/pyproject.toml +0 -134
- code_towel-1.732/scripts/add_copyright_headers.py +0 -67
- code_towel-1.732/scripts/audit_dependencies.py +0 -56
- code_towel-1.732/scripts/bench_refactor.py +0 -118
- code_towel-1.732/scripts/ecosystem/manifest.toml +0 -1158
- code_towel-1.732/scripts/ecosystem_check.py +0 -1033
- code_towel-1.732/scripts/report_net_benefit_diff.py +0 -116
- code_towel-1.732/scripts/run_crossfile_coverage.py +0 -27
- code_towel-1.732/scripts/set_version.py +0 -33
- code_towel-1.732/scripts/trace_proposals.py +0 -99
- code_towel-1.732/scripts/verify-examples +0 -111
- code_towel-1.732/src/code_towel.egg-info/PKG-INFO +0 -378
- code_towel-1.732/src/code_towel.egg-info/SOURCES.txt +0 -534
- code_towel-1.732/src/towel/_mypy_worker.py +0 -177
- code_towel-1.732/src/towel/changes.py +0 -327
- code_towel-1.732/src/towel/checker_project.py +0 -220
- code_towel-1.732/src/towel/cli.py +0 -1537
- code_towel-1.732/src/towel/filesystem.py +0 -68
- code_towel-1.732/src/towel/formatting.py +0 -437
- code_towel-1.732/src/towel/project_layout.py +0 -633
- code_towel-1.732/src/towel/renaming.py +0 -1326
- code_towel-1.732/src/towel/source_files.py +0 -70
- code_towel-1.732/src/towel/source_text.py +0 -94
- code_towel-1.732/src/towel/type_inference.py +0 -1081
- code_towel-1.732/src/towel/unification/annotation_wiring.py +0 -303
- code_towel-1.732/src/towel/unification/annotations.py +0 -968
- code_towel-1.732/src/towel/unification/assignment_analyzer.py +0 -394
- code_towel-1.732/src/towel/unification/block_analysis.py +0 -846
- code_towel-1.732/src/towel/unification/bounded_cache.py +0 -93
- code_towel-1.732/src/towel/unification/builtins.py +0 -235
- code_towel-1.732/src/towel/unification/clustering.py +0 -468
- code_towel-1.732/src/towel/unification/constant_consistency.py +0 -149
- code_towel-1.732/src/towel/unification/definite_assignment.py +0 -343
- code_towel-1.732/src/towel/unification/engine_state.py +0 -301
- code_towel-1.732/src/towel/unification/exceptions.py +0 -39
- code_towel-1.732/src/towel/unification/extractor.py +0 -813
- code_towel-1.732/src/towel/unification/fixed_point.py +0 -637
- code_towel-1.732/src/towel/unification/hof_promotion.py +0 -219
- code_towel-1.732/src/towel/unification/import_graph.py +0 -542
- code_towel-1.732/src/towel/unification/insertion.py +0 -315
- code_towel-1.732/src/towel/unification/instantiation.py +0 -478
- code_towel-1.732/src/towel/unification/materialize.py +0 -472
- code_towel-1.732/src/towel/unification/models.py +0 -392
- code_towel-1.732/src/towel/unification/pair_evaluation.py +0 -1318
- code_towel-1.732/src/towel/unification/parallel.py +0 -339
- code_towel-1.732/src/towel/unification/parameterization.py +0 -313
- code_towel-1.732/src/towel/unification/pipeline.py +0 -477
- code_towel-1.732/src/towel/unification/placement.py +0 -531
- code_towel-1.732/src/towel/unification/progress.py +0 -133
- code_towel-1.732/src/towel/unification/project_layout.py +0 -10
- code_towel-1.732/src/towel/unification/refactor_engine.py +0 -797
- code_towel-1.732/src/towel/unification/reuse.py +0 -560
- code_towel-1.732/src/towel/unification/scope_analyzer.py +0 -628
- code_towel-1.732/src/towel/unification/semantic_safety.py +0 -1128
- code_towel-1.732/src/towel/unification/statement_facts.py +0 -252
- code_towel-1.732/src/towel/unification/structural_memo.py +0 -146
- code_towel-1.732/src/towel/unification/substitution.py +0 -128
- code_towel-1.732/src/towel/unification/thunk_inlining.py +0 -334
- code_towel-1.732/src/towel/unification/unifier.py +0 -917
- code_towel-1.732/src/towel/unification/unifier_state.py +0 -65
- code_towel-1.732/src/towel/unification/visitors.py +0 -605
- code_towel-1.732/test_examples_crossfile/README.md +0 -242
- code_towel-1.732/test_examples_crossfile/multi_level/core/services/payment.py +0 -27
- code_towel-1.732/test_examples_crossfile/multi_level/utils/validators.py +0 -26
- code_towel-1.732/test_examples_crossfile/nested_structure/src/data_processor.py +0 -23
- code_towel-1.732/test_examples_crossfile/simple_crossfile/user_service.py +0 -25
- code_towel-1.732/test_examples_crossfile_expected_output/multi_level/api/checkout.py +0 -27
- code_towel-1.732/test_examples_crossfile_expected_output/multi_level/core/services/payment.py +0 -18
- code_towel-1.732/test_examples_crossfile_expected_output/multi_level/utils/validators.py +0 -17
- code_towel-1.732/test_examples_crossfile_expected_output/nested_structure/lib/report_generator.py +0 -30
- code_towel-1.732/test_examples_crossfile_expected_output/nested_structure/src/data_processor.py +0 -17
- code_towel-1.732/test_examples_crossfile_expected_output/simple_crossfile/admin_service.py +0 -25
- code_towel-1.732/test_examples_crossfile_expected_output/simple_crossfile/user_service.py +0 -17
- code_towel-1.732/test_examples_expected_output/annotated_module.py +0 -108
- code_towel-1.732/test_examples_expected_output/binding_constructs_comprehensive.py +0 -109
- code_towel-1.732/test_examples_expected_output/bindings_for_loops.py +0 -48
- code_towel-1.732/test_examples_expected_output/closure_adversarial.py +0 -115
- code_towel-1.732/test_examples_expected_output/control_flow_adversarial.py +0 -117
- code_towel-1.732/test_examples_expected_output/edge_cases_stress_test.py +0 -181
- code_towel-1.732/test_examples_expected_output/example4_complex.py +0 -34
- code_towel-1.732/test_examples_expected_output/exception_adversarial.py +0 -132
- code_towel-1.732/test_examples_expected_output/fstrings_constants.py +0 -84
- code_towel-1.732/test_examples_expected_output/functional_patterns.py +0 -224
- code_towel-1.732/test_examples_expected_output/method_chains.py +0 -182
- code_towel-1.732/test_examples_expected_output/return_values.py +0 -76
- code_towel-1.732/test_examples_expected_output/scoping_edge_cases.py +0 -109
- code_towel-1.732/test_examples_expected_output/side_effects_adversarial.py +0 -102
- code_towel-1.732/test_examples_expected_output/syntactic_coverage_comprehensive.py +0 -632
- code_towel-1.732/test_examples_expected_output/tricky_edge_cases_adversarial.py +0 -173
- code_towel-1.732/tests/README.md +0 -254
- code_towel-1.732/tests/__init__.py +0 -3
- code_towel-1.732/tests/automatic_equivalence_tester.py +0 -638
- code_towel-1.732/tests/conftest.py +0 -28
- code_towel-1.732/tests/crossfile_equivalence_tester.py +0 -358
- code_towel-1.732/tests/edge_case_values.py +0 -361
- code_towel-1.732/tests/equivalence_targets.py +0 -147
- code_towel-1.732/tests/generate_baseline.py +0 -176
- code_towel-1.732/tests/hostile_cases/h01_side_effect_order.py +0 -23
- code_towel-1.732/tests/hostile_cases/h02_conditional_eval.py +0 -16
- code_towel-1.732/tests/hostile_cases/h03_loop_reeval.py +0 -19
- code_towel-1.732/tests/hostile_cases/h04_closure_freevar.py +0 -16
- code_towel-1.732/tests/hostile_cases/h05_cond_return_plus_retvar.py +0 -20
- code_towel-1.732/tests/hostile_cases/h06_del_in_block.py +0 -20
- code_towel-1.732/tests/hostile_cases/h07_except_unbind.py +0 -24
- code_towel-1.732/tests/hostile_cases/h08_late_binding_closure.py +0 -16
- code_towel-1.732/tests/hostile_cases/h10_short_circuit.py +0 -14
- code_towel-1.732/tests/hostile_cases/h13_dunder_file.py +0 -13
- code_towel-1.732/tests/hostile_cases/h26_match_capture.py +0 -18
- code_towel-1.732/tests/hostile_cases/h38_closure_after_block.py +0 -18
- code_towel-1.732/tests/hostile_cases/h39_augassign_after_block.py +0 -16
- code_towel-1.732/tests/hostile_cases/h51_semicolons.py +0 -14
- code_towel-1.732/tests/hostile_cases/h52_del_after_block.py +0 -16
- code_towel-1.732/tests/hostile_cases/h53_nested_func_reads_retvar.py +0 -14
- code_towel-1.732/tests/hostile_cases/h54_attr_param_property.py +0 -21
- code_towel-1.732/tests/hostile_cases/h55_walrus_used_after.py +0 -17
- code_towel-1.732/tests/hostile_cases/h56_global_write.py +0 -15
- code_towel-1.732/tests/hostile_cases/h57_exception_in_param.py +0 -18
- code_towel-1.732/tests/hostile_cases/h58_nested_def_freevar_reassigned.py +0 -14
- code_towel-1.732/tests/hostile_cases/h59_retvar_conditional_unbound.py +0 -20
- code_towel-1.732/tests/hostile_cases/h60_finally_return.py +0 -18
- code_towel-1.732/tests/hostile_cases/h61_str_method_param.py +0 -16
- code_towel-1.732/tests/hostile_cases/r01_property_thunk_order.py +0 -19
- code_towel-1.732/tests/hostile_cases/r02_cluster_attrs.py +0 -25
- code_towel-1.732/tests/hostile_cases/r07_attr_two_positions.py +0 -16
- code_towel-1.732/tests/hostile_cases/r100_clustered_site_with_live_binding.py +0 -22
- code_towel-1.732/tests/hostile_cases/r101_same_named_method_forces_module_helper.py +0 -43
- code_towel-1.732/tests/hostile_cases/r102_whole_body_reuse.py +0 -26
- code_towel-1.732/tests/hostile_cases/r103_lambda_parameter_spelling.py +0 -25
- code_towel-1.732/tests/hostile_cases/r104_walrus_target_spelling.py +0 -14
- code_towel-1.732/tests/hostile_cases/r105_closure_over_block_bound_name_rebound_after.py +0 -18
- code_towel-1.732/tests/hostile_cases/r106_dir_reads_the_frame.py +0 -13
- code_towel-1.732/tests/hostile_cases/r107_unbound_global_argument.py +0 -14
- code_towel-1.732/tests/hostile_cases/r108_clustered_sites_assign_the_returned_name.py +0 -23
- code_towel-1.732/tests/hostile_cases/r109_eager_optional_import_failed.py +0 -19
- code_towel-1.732/tests/hostile_cases/r110_eager_type_checking_import.py +0 -18
- code_towel-1.732/tests/hostile_cases/r111_eager_module_except_as_name.py +0 -22
- code_towel-1.732/tests/hostile_cases/r112_eager_module_match_capture_unmatched.py +0 -19
- code_towel-1.732/tests/hostile_cases/r113_eager_class_attr_in_method.py +0 -17
- code_towel-1.732/tests/hostile_cases/r114_eager_nested_class_attr.py +0 -19
- code_towel-1.732/tests/hostile_cases/r115_eager_class_in_function_attr.py +0 -20
- code_towel-1.732/tests/hostile_cases/r116_eager_enclosing_bound_after_inner_call.py +0 -19
- code_towel-1.732/tests/hostile_cases/r117_eager_enclosing_deleted_before_inner_call.py +0 -19
- code_towel-1.732/tests/hostile_cases/r118_eager_enclosing_conditionally_bound.py +0 -19
- code_towel-1.732/tests/hostile_cases/r119_eager_module_def_and_class_after_call.py +0 -19
- code_towel-1.732/tests/hostile_cases/r11_same_function_pair.py +0 -14
- code_towel-1.732/tests/hostile_cases/r120_eager_module_import_after_call.py +0 -17
- code_towel-1.732/tests/hostile_cases/r121_eager_comprehension_variable.py +0 -16
- code_towel-1.732/tests/hostile_cases/r122_eager_lambda_parameter.py +0 -16
- code_towel-1.732/tests/hostile_cases/r123_direct_eval_after_block.py +0 -16
- code_towel-1.732/tests/hostile_cases/r124_eval_alias_reads_preblock_name.py +0 -46
- code_towel-1.732/tests/hostile_cases/r125_locals_dir_after_block.py +0 -40
- code_towel-1.732/tests/hostile_cases/r126_locals_in_nested_scope.py +0 -30
- code_towel-1.732/tests/hostile_cases/r127_eval_alias_partial_block.py +0 -53
- code_towel-1.732/tests/hostile_cases/r128_warn_stacklevel_via_alias.py +0 -31
- code_towel-1.732/tests/hostile_cases/r129_warning_registry_dedup.py +0 -18
- code_towel-1.732/tests/hostile_cases/r130_resource_observed_after_block_differs.py +0 -20
- code_towel-1.732/tests/hostile_cases/r131_weakref_and_id_identity.py +0 -27
- code_towel-1.732/tests/hostile_cases/r132_async_comprehension_in_block.py +0 -16
- code_towel-1.732/tests/hostile_cases/r134_u2028_in_comment_shifts_splice.py +0 -17
- code_towel-1.732/tests/hostile_cases/r136_u2028_in_string_literal.py +0 -17
- code_towel-1.732/tests/hostile_cases/r137_x1c_x85_in_comment.py +0 -17
- code_towel-1.732/tests/hostile_cases/r138_u2028_comment_same_indent_neighbours.py +0 -24
- code_towel-1.732/tests/hostile_cases/r139_literal_roundtrip.py +0 -18
- code_towel-1.732/tests/hostile_cases/r140_unbound_name_on_untaken_branch.py +0 -21
- code_towel-1.732/tests/hostile_cases/r141_module_name_bound_after_an_early_call.py +0 -24
- code_towel-1.732/tests/hostile_cases/r142_module_data_rebound_by_callback.py +0 -22
- code_towel-1.732/tests/hostile_cases/r143_module_name_shadowed_by_a_local_elsewhere.py +0 -22
- code_towel-1.732/tests/hostile_cases/r144_class_cell_in_a_method_helper.py +0 -32
- code_towel-1.732/tests/hostile_cases/r145_getframe_assigned_alias.py +0 -47
- code_towel-1.732/tests/hostile_cases/r146_module_function_reads_caller_frame.py +0 -37
- code_towel-1.732/tests/hostile_cases/r14_starred_param.py +0 -15
- code_towel-1.732/tests/hostile_cases/r22_fstring_attr.py +0 -19
- code_towel-1.732/tests/hostile_cases/r26_attr_store_param.py +0 -14
- code_towel-1.732/tests/hostile_cases/r27_del_subscript.py +0 -12
- code_towel-1.732/tests/hostile_cases/r32_method_callee.py +0 -18
- code_towel-1.732/tests/hostile_cases/r38_name_two_positions.py +0 -14
- code_towel-1.732/tests/hostile_cases/r39_cluster_alpha.py +0 -17
- code_towel-1.732/tests/hostile_cases/r40_class_hierarchy.py +0 -21
- code_towel-1.732/tests/hostile_cases/r41_conditional_call_param.py +0 -17
- code_towel-1.732/tests/hostile_cases/r42_loop_call_param.py +0 -16
- code_towel-1.732/tests/hostile_cases/r43_opaque_decorator_receiver.py +0 -17
- code_towel-1.732/tests/hostile_cases/r44_cluster_nested_overlap.py +0 -23
- code_towel-1.732/tests/hostile_cases/r45_async_no_await.py +0 -17
- code_towel-1.732/tests/hostile_cases/r46_nonlocal_counter.py +0 -22
- code_towel-1.732/tests/hostile_cases/r47_lru_cache_method.py +0 -19
- code_towel-1.732/tests/hostile_cases/r50_helper_name_collision.py +0 -18
- code_towel-1.732/tests/hostile_cases/r53_try_else_binding.py +0 -28
- code_towel-1.732/tests/hostile_cases/r54_with_binding_used_after.py +0 -15
- code_towel-1.732/tests/hostile_cases/r55_chained_assign.py +0 -14
- code_towel-1.732/tests/hostile_cases/r57_star_unpack.py +0 -12
- code_towel-1.732/tests/hostile_cases/r58_escaping_closure.py +0 -16
- code_towel-1.732/tests/hostile_cases/r60_local_import.py +0 -14
- code_towel-1.732/tests/hostile_cases/r61_same_function_clean.py +0 -13
- code_towel-1.732/tests/hostile_cases/r62_recursion.py +0 -14
- code_towel-1.732/tests/hostile_cases/r63_except_var_used_in_handler.py +0 -22
- code_towel-1.732/tests/hostile_cases/r66_conditional_callee.py +0 -17
- code_towel-1.732/tests/hostile_cases/r69_annassign.py +0 -14
- code_towel-1.732/tests/hostile_cases/r72_global_promoted.py +0 -17
- code_towel-1.732/tests/hostile_cases/r73_slice_vs_index.py +0 -12
- code_towel-1.732/tests/hostile_cases/r74_starred_vs_plain.py +0 -15
- code_towel-1.732/tests/hostile_cases/r75_prior_helper_param_names.py +0 -14
- code_towel-1.732/tests/hostile_cases/r76_return_order.py +0 -16
- code_towel-1.732/tests/hostile_cases/r77_mixed_return_and_variables.py +0 -17
- code_towel-1.732/tests/hostile_cases/r78_walrus_in_parameter.py +0 -17
- code_towel-1.732/tests/hostile_cases/r79_list_display_in_loop.py +0 -23
- code_towel-1.732/tests/hostile_cases/r80_inlined_leading_thunk.py +0 -19
- code_towel-1.732/tests/hostile_cases/r81_conditionally_bound_free_variable.py +0 -21
- code_towel-1.732/tests/hostile_cases/r82_local_classes_same_name.py +0 -20
- code_towel-1.732/tests/hostile_cases/r83_tab_indented_class.py +0 -20
- code_towel-1.732/tests/hostile_cases/r84_warn_stacklevel.py +0 -18
- code_towel-1.732/tests/hostile_cases/r85_conditionally_bound_parameter.py +0 -25
- code_towel-1.732/tests/hostile_cases/r86_annotated_assignment_live.py +0 -26
- code_towel-1.732/tests/hostile_cases/r87_nested_function_in_method.py +0 -22
- code_towel-1.732/tests/hostile_cases/r88_elif_branch.py +0 -25
- code_towel-1.732/tests/hostile_cases/r89_conditionally_bound_return.py +0 -28
- code_towel-1.732/tests/hostile_cases/r90_cluster_across_classes.py +0 -28
- code_towel-1.732/tests/hostile_cases/r91_class_body_helper.py +0 -23
- code_towel-1.732/tests/hostile_cases/r92_parameterless_class_body_helper.py +0 -25
- code_towel-1.732/tests/hostile_cases/r93_import_name_differs.py +0 -39
- code_towel-1.732/tests/hostile_cases/r94_import_binds_live_name.py +0 -12
- code_towel-1.732/tests/hostile_cases/r95_type_checking_annotation.py +0 -18
- code_towel-1.732/tests/hostile_cases/r96_unpacked_targets_live.py +0 -19
- code_towel-1.732/tests/hostile_cases/r97_helper_in_function_with_outside_site.py +0 -21
- code_towel-1.732/tests/hostile_cases/r98_same_named_methods_nested_helpers.py +0 -37
- code_towel-1.732/tests/hostile_cases/r99_partial_return_branches.py +0 -25
- code_towel-1.732/tests/hostile_crossfile/xf10_reuse_existing_function/pkg/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf10_reuse_existing_function/pkg/a.py +0 -6
- code_towel-1.732/tests/hostile_crossfile/xf10_reuse_existing_function/pkg/b.py +0 -12
- code_towel-1.732/tests/hostile_crossfile/xf10_reuse_existing_function/run.py +0 -3
- code_towel-1.732/tests/hostile_crossfile/xf11_package_init_reaches_back/pkg/__init__.py +0 -9
- code_towel-1.732/tests/hostile_crossfile/xf11_package_init_reaches_back/pkg/detail.py +0 -7
- code_towel-1.732/tests/hostile_crossfile/xf11_package_init_reaches_back/run.py +0 -3
- code_towel-1.732/tests/hostile_crossfile/xf12_cycle_through_package_init/pkg/__init__.py +0 -1
- code_towel-1.732/tests/hostile_crossfile/xf12_cycle_through_package_init/pkg/core.py +0 -3
- code_towel-1.732/tests/hostile_crossfile/xf12_cycle_through_package_init/pkg/sub/__init__.py +0 -2
- code_towel-1.732/tests/hostile_crossfile/xf12_cycle_through_package_init/pkg/sub/leaf.py +0 -6
- code_towel-1.732/tests/hostile_crossfile/xf12_cycle_through_package_init/pkg/vendor/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf12_cycle_through_package_init/pkg/vendor/tool.py +0 -6
- code_towel-1.732/tests/hostile_crossfile/xf12_cycle_through_package_init/run.py +0 -4
- code_towel-1.732/tests/hostile_crossfile/xf13_import_time_effects/pkg/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf13_import_time_effects/pkg/a.py +0 -8
- code_towel-1.732/tests/hostile_crossfile/xf13_import_time_effects/pkg/b.py +0 -10
- code_towel-1.732/tests/hostile_crossfile/xf13_import_time_effects/run.py +0 -4
- code_towel-1.732/tests/hostile_crossfile/xf14_script_with_leading_statement/pkg/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf14_script_with_leading_statement/pkg/app.py +0 -9
- code_towel-1.732/tests/hostile_crossfile/xf14_script_with_leading_statement/pkg/util.py +0 -8
- code_towel-1.732/tests/hostile_crossfile/xf14_script_with_leading_statement/run.py +0 -3
- code_towel-1.732/tests/hostile_crossfile/xf15_ancestor_in_another_module/pkg/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf15_ancestor_in_another_module/pkg/base.py +0 -4
- code_towel-1.732/tests/hostile_crossfile/xf15_ancestor_in_another_module/pkg/servers.py +0 -22
- code_towel-1.732/tests/hostile_crossfile/xf15_ancestor_in_another_module/pkg/tokens.py +0 -7
- code_towel-1.732/tests/hostile_crossfile/xf15_ancestor_in_another_module/run.py +0 -5
- code_towel-1.732/tests/hostile_crossfile/xf3_same_named_local_function/pkg/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf3_same_named_local_function/pkg/a.py +0 -8
- code_towel-1.732/tests/hostile_crossfile/xf3_same_named_local_function/pkg/b.py +0 -8
- code_towel-1.732/tests/hostile_crossfile/xf3_same_named_local_function/run.py +0 -3
- code_towel-1.732/tests/hostile_crossfile/xf4_same_named_class/pkg/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf4_same_named_class/pkg/a.py +0 -9
- code_towel-1.732/tests/hostile_crossfile/xf4_same_named_class/pkg/b.py +0 -9
- code_towel-1.732/tests/hostile_crossfile/xf4_same_named_class/run.py +0 -3
- code_towel-1.732/tests/hostile_crossfile/xf5_dunder_file/pkg/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf5_dunder_file/pkg/a.py +0 -7
- code_towel-1.732/tests/hostile_crossfile/xf5_dunder_file/pkg/b.py +0 -7
- code_towel-1.732/tests/hostile_crossfile/xf5_dunder_file/run.py +0 -3
- code_towel-1.732/tests/hostile_crossfile/xf6_same_alias_different_import/pkg/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf6_same_alias_different_import/pkg/a.py +0 -7
- code_towel-1.732/tests/hostile_crossfile/xf6_same_alias_different_import/pkg/b.py +0 -7
- code_towel-1.732/tests/hostile_crossfile/xf6_same_alias_different_import/pkg/util1.py +0 -1
- code_towel-1.732/tests/hostile_crossfile/xf6_same_alias_different_import/pkg/util2.py +0 -1
- code_towel-1.732/tests/hostile_crossfile/xf6_same_alias_different_import/run.py +0 -3
- code_towel-1.732/tests/hostile_crossfile/xf7_docstring_import/pkg/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf7_docstring_import/pkg/a.py +0 -14
- code_towel-1.732/tests/hostile_crossfile/xf7_docstring_import/pkg/b.py +0 -13
- code_towel-1.732/tests/hostile_crossfile/xf7_docstring_import/run.py +0 -3
- code_towel-1.732/tests/hostile_crossfile/xf8_helper_name_collision/pkg/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf8_helper_name_collision/pkg/a.py +0 -6
- code_towel-1.732/tests/hostile_crossfile/xf8_helper_name_collision/pkg/b.py +0 -6
- code_towel-1.732/tests/hostile_crossfile/xf8_helper_name_collision/pkg/c.py +0 -6
- code_towel-1.732/tests/hostile_crossfile/xf8_helper_name_collision/pkg/d.py +0 -6
- code_towel-1.732/tests/hostile_crossfile/xf8_helper_name_collision/pkg/e.py +0 -12
- code_towel-1.732/tests/hostile_crossfile/xf8_helper_name_collision/run.py +0 -6
- code_towel-1.732/tests/hostile_crossfile/xf9_same_named_base_class/pkg/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf9_same_named_base_class/pkg/one/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf9_same_named_base_class/pkg/one/base.py +0 -3
- code_towel-1.732/tests/hostile_crossfile/xf9_same_named_base_class/pkg/one/introspect.py +0 -10
- code_towel-1.732/tests/hostile_crossfile/xf9_same_named_base_class/pkg/one/revoke.py +0 -10
- code_towel-1.732/tests/hostile_crossfile/xf9_same_named_base_class/pkg/two/__init__.py +0 -0
- code_towel-1.732/tests/hostile_crossfile/xf9_same_named_base_class/pkg/two/access.py +0 -10
- code_towel-1.732/tests/hostile_crossfile/xf9_same_named_base_class/pkg/two/base.py +0 -3
- code_towel-1.732/tests/hostile_crossfile/xf9_same_named_base_class/pkg/two/request.py +0 -10
- code_towel-1.732/tests/hostile_crossfile/xf9_same_named_base_class/run.py +0 -8
- code_towel-1.732/tests/hostile_execution.py +0 -31
- code_towel-1.732/tests/run_tests.py +0 -31
- code_towel-1.732/tests/test_adversarial_renaming.py +0 -328
- code_towel-1.732/tests/test_adversarial_semantics.py +0 -305
- code_towel-1.732/tests/test_analysis_edge_paths.py +0 -165
- code_towel-1.732/tests/test_analysis_sessions.py +0 -268
- code_towel-1.732/tests/test_ancestor_insertion.py +0 -76
- code_towel-1.732/tests/test_assignment_analyzer_comprehensive.py +0 -725
- code_towel-1.732/tests/test_audit_io_safety.py +0 -306
- code_towel-1.732/tests/test_backend_layouts.py +0 -379
- code_towel-1.732/tests/test_binding_context_memo.py +0 -58
- code_towel-1.732/tests/test_binding_detector.py +0 -288
- code_towel-1.732/tests/test_binding_detector_edge_cases.py +0 -378
- code_towel-1.732/tests/test_bindings.py +0 -259
- code_towel-1.732/tests/test_bindings_additional.py +0 -96
- code_towel-1.732/tests/test_breakers.py +0 -119
- code_towel-1.732/tests/test_builtins_comprehensive.py +0 -398
- code_towel-1.732/tests/test_cache_lifetimes.py +0 -71
- code_towel-1.732/tests/test_candidate_index.py +0 -190
- code_towel-1.732/tests/test_change_transactions.py +0 -254
- code_towel-1.732/tests/test_cli_dispatch_paths.py +0 -83
- code_towel-1.732/tests/test_cli_input_errors.py +0 -86
- code_towel-1.732/tests/test_cli_integration.py +0 -292
- code_towel-1.732/tests/test_cli_oracle_lifecycle.py +0 -88
- code_towel-1.732/tests/test_cli_output_safety.py +0 -51
- code_towel-1.732/tests/test_comprehensive_coverage.py +0 -293
- code_towel-1.732/tests/test_copy_preservation.py +0 -38
- code_towel-1.732/tests/test_crossfile_integration.py +0 -419
- code_towel-1.732/tests/test_crossfile_observational_equivalence.py +0 -28
- code_towel-1.732/tests/test_definite_assignment.py +0 -169
- code_towel-1.732/tests/test_dependency_audit_export.py +0 -81
- code_towel-1.732/tests/test_distinct_proposals.py +0 -88
- code_towel-1.732/tests/test_ecosystem_manifest_regressions.py +0 -89
- code_towel-1.732/tests/test_ecosystem_verdicts.py +0 -1233
- code_towel-1.732/tests/test_engine_adversarial.py +0 -1453
- code_towel-1.732/tests/test_engine_failure_visibility.py +0 -34
- code_towel-1.732/tests/test_environment_independence.py +0 -223
- code_towel-1.732/tests/test_equivalence_harness_regressions.py +0 -161
- code_towel-1.732/tests/test_equivalence_target_selection.py +0 -158
- code_towel-1.732/tests/test_error_paths.py +0 -275
- code_towel-1.732/tests/test_exceptions.py +0 -16
- code_towel-1.732/tests/test_extractor_additional_branches.py +0 -116
- code_towel-1.732/tests/test_extractor_augassign_and_fstrings.py +0 -112
- code_towel-1.732/tests/test_extractor_callee_and_multi_return.py +0 -123
- code_towel-1.732/tests/test_extractor_comprehensive.py +0 -555
- code_towel-1.732/tests/test_extractor_edge_cases.py +0 -355
- code_towel-1.732/tests/test_extractor_final_lines.py +0 -151
- code_towel-1.732/tests/test_extractor_generate_call_variants.py +0 -82
- code_towel-1.732/tests/test_extractor_more_paths.py +0 -123
- code_towel-1.732/tests/test_extractor_preamble_and_call_mapping.py +0 -137
- code_towel-1.732/tests/test_extractor_remaining_branches.py +0 -259
- code_towel-1.732/tests/test_extractor_return_statements.py +0 -327
- code_towel-1.732/tests/test_extractor_substituter_and_helpers.py +0 -411
- code_towel-1.732/tests/test_filesystem_guards.py +0 -73
- code_towel-1.732/tests/test_final_coverage_push.py +0 -251
- code_towel-1.732/tests/test_fixed_point_refusals.py +0 -141
- code_towel-1.732/tests/test_forwarder_chains.py +0 -264
- code_towel-1.732/tests/test_frame_sensitivity_scan.py +0 -69
- code_towel-1.732/tests/test_free_variable_correspondence.py +0 -271
- code_towel-1.732/tests/test_fstrings.py +0 -174
- code_towel-1.732/tests/test_function_facts_equivalence.py +0 -78
- code_towel-1.732/tests/test_function_index.py +0 -76
- code_towel-1.732/tests/test_generated_code_formatting.py +0 -282
- code_towel-1.732/tests/test_helper_annotations.py +0 -436
- code_towel-1.732/tests/test_helper_inventory.py +0 -186
- code_towel-1.732/tests/test_helpers.py +0 -268
- code_towel-1.732/tests/test_hostile_battery.py +0 -150
- code_towel-1.732/tests/test_hostile_crossfile_battery.py +0 -77
- code_towel-1.732/tests/test_import_bindings.py +0 -98
- code_towel-1.732/tests/test_import_cycle_package_init.py +0 -74
- code_towel-1.732/tests/test_import_resolution.py +0 -66
- code_towel-1.732/tests/test_incremental_global_passes.py +0 -122
- code_towel-1.732/tests/test_instantiation_check.py +0 -273
- code_towel-1.732/tests/test_lazy_engine_import.py +0 -35
- code_towel-1.732/tests/test_lexical_binding_regressions.py +0 -203
- code_towel-1.732/tests/test_method_level_and_attributes.py +0 -69
- code_towel-1.732/tests/test_method_receiver_arguments.py +0 -40
- code_towel-1.732/tests/test_module_names_stay_free.py +0 -119
- code_towel-1.732/tests/test_observational_equivalence.py +0 -908
- code_towel-1.732/tests/test_orphan_detection.py +0 -254
- code_towel-1.732/tests/test_orphan_detector_comprehensive.py +0 -589
- code_towel-1.732/tests/test_out_of_place_cycle_regression.py +0 -166
- code_towel-1.732/tests/test_overlap_filtering.py +0 -375
- code_towel-1.732/tests/test_pair_budget.py +0 -111
- code_towel-1.732/tests/test_parallel_evaluation.py +0 -222
- code_towel-1.732/tests/test_parameters.py +0 -31
- code_towel-1.732/tests/test_parent_watchdog.py +0 -110
- code_towel-1.732/tests/test_perf_memos.py +0 -237
- code_towel-1.732/tests/test_pipeline_api.py +0 -57
- code_towel-1.732/tests/test_pipeline_phases.py +0 -367
- code_towel-1.732/tests/test_progress_modes.py +0 -162
- code_towel-1.732/tests/test_project_layout_and_imports.py +0 -266
- code_towel-1.732/tests/test_project_layout_behavior.py +0 -324
- code_towel-1.732/tests/test_project_layout_shim.py +0 -19
- code_towel-1.732/tests/test_promotion_and_mangling.py +0 -193
- code_towel-1.732/tests/test_properties.py +0 -1187
- code_towel-1.732/tests/test_recovery_journal.py +0 -413
- code_towel-1.732/tests/test_refactor_engine_comprehensive.py +0 -509
- code_towel-1.732/tests/test_refactor_engine_edge_cases.py +0 -315
- code_towel-1.732/tests/test_refactor_engine_more.py +0 -119
- code_towel-1.732/tests/test_refactor_engine_targeted_branches.py +0 -363
- code_towel-1.732/tests/test_refactoring_engine.py +0 -249
- code_towel-1.732/tests/test_regression.py +0 -472
- code_towel-1.732/tests/test_regressions_broader.py +0 -155
- code_towel-1.732/tests/test_release_regressions.py +0 -145
- code_towel-1.732/tests/test_rename_audit_regressions.py +0 -225
- code_towel-1.732/tests/test_rename_discovery_regressions.py +0 -175
- code_towel-1.732/tests/test_rename_parameters_and_methods.py +0 -130
- code_towel-1.732/tests/test_return_alignment.py +0 -44
- code_towel-1.732/tests/test_return_values.py +0 -94
- code_towel-1.732/tests/test_returned_variables_not_orphaned.py +0 -98
- code_towel-1.732/tests/test_reuse_existing_function.py +0 -578
- code_towel-1.732/tests/test_reuse_type_checks.py +0 -125
- code_towel-1.732/tests/test_robustness_paths.py +0 -178
- code_towel-1.732/tests/test_scope_analyzer.py +0 -234
- code_towel-1.732/tests/test_scope_analyzer_comprehensive.py +0 -663
- code_towel-1.732/tests/test_semantic_guard_traces.py +0 -186
- code_towel-1.732/tests/test_semantic_guards_extended.py +0 -201
- code_towel-1.732/tests/test_semantic_safety_regressions.py +0 -427
- code_towel-1.732/tests/test_signature_prefilter.py +0 -65
- code_towel-1.732/tests/test_source_encoding.py +0 -86
- code_towel-1.732/tests/test_stale_proposal_recovery.py +0 -209
- code_towel-1.732/tests/test_statement_facts.py +0 -267
- code_towel-1.732/tests/test_structural_memo.py +0 -98
- code_towel-1.732/tests/test_substitution_keys.py +0 -25
- code_towel-1.732/tests/test_symlinked_input_directory.py +0 -118
- code_towel-1.732/tests/test_thunk_inlining.py +0 -173
- code_towel-1.732/tests/test_trivial_helper_filter.py +0 -116
- code_towel-1.732/tests/test_type_inference.py +0 -656
- code_towel-1.732/tests/test_type_oracle_projects.py +0 -555
- code_towel-1.732/tests/test_type_run_policy.py +0 -408
- code_towel-1.732/tests/test_unifier_additional_branches.py +0 -89
- code_towel-1.732/tests/test_unifier_batch2.py +0 -130
- code_towel-1.732/tests/test_unifier_batch3.py +0 -169
- code_towel-1.732/tests/test_unifier_batch4.py +0 -102
- code_towel-1.732/tests/test_unifier_batch5.py +0 -105
- code_towel-1.732/tests/test_unifier_bound_variables.py +0 -346
- code_towel-1.732/tests/test_unifier_comprehensions.py +0 -62
- code_towel-1.732/tests/test_unifier_comprehensive.py +0 -346
- code_towel-1.732/tests/test_unifier_core.py +0 -188
- code_towel-1.732/tests/test_unifier_edge_cases.py +0 -191
- code_towel-1.732/tests/test_unifier_more_branches.py +0 -208
- code_towel-1.732/tests/test_unifier_state_reset.py +0 -93
- code_towel-1.732/tests/test_unsupported_layout.py +0 -84
- code_towel-1.732/tests/test_validation_partial_lifetime.py +0 -155
- code_towel-1.732/tests/test_variable_capture_bug.py +0 -209
- code_towel-1.732/tests/test_visitors.py +0 -405
- code_towel-1.732/uv.lock +0 -1554
- {code_towel-1.732 → code_towel-1.772}/.flake8 +0 -0
- {code_towel-1.732 → code_towel-1.772}/.pre-commit-config.yaml +0 -0
- {code_towel-1.732 → code_towel-1.772}/CODE_OF_CONDUCT.md +0 -0
- {code_towel-1.732 → code_towel-1.772}/LICENSE +0 -0
- {code_towel-1.732 → code_towel-1.772}/docs/proposals/dethunking.md +0 -0
- {code_towel-1.732 → code_towel-1.772}/scripts/bench_similar_blocks.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/scripts/promote_dry_helpers.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/setup.cfg +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/code_towel.egg-info/dependency_links.txt +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/code_towel.egg-info/entry_points.txt +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/code_towel.egg-info/requires.txt +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/code_towel.egg-info/top_level.txt +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/__init__.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/diagnostics.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/project_tools.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/py.typed +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/unification/__init__.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/unification/binding_context.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/unification/binding_detector.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/unification/block_signature.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/unification/defaults.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/unification/function_index.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/unification/orphan_detector.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/unification/overlap.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/src/towel/unification/parameters.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/README.md +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/annotated_module.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/binding_constructs_comprehensive.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/bindings_comprehensions.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/bindings_for_loops.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/closure_adversarial.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/complex_expressions.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/control_flow_adversarial.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/edge_cases_stress_test.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/example1_simple.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/example2_classes.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/example3_file1.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/example3_file2.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/example4_complex.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/exception_adversarial.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/fstrings_constants.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/functional_patterns.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/generators_async.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/global_nonlocal_examples.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/hygienic_naming.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/method_chains.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/nested_structures.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/real_world_patterns.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/referential_transparency.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/return_values.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/scoping_edge_cases.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/side_effects_adversarial.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/syntactic_coverage_comprehensive.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples/tricky_edge_cases_adversarial.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_crossfile/class_hierarchy/base_record.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_crossfile/class_hierarchy/scored_record.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_crossfile/multi_level/api/checkout.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_crossfile/nested_structure/lib/report_generator.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_crossfile/simple_crossfile/admin_service.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_crossfile_expected_output/class_hierarchy/base_record.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_crossfile_expected_output/class_hierarchy/scored_record.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_expected_output/bindings_comprehensions.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_expected_output/complex_expressions.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_expected_output/example1_simple.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_expected_output/example2_classes.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_expected_output/example3_file1.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_expected_output/example3_file2.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_expected_output/generators_async.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_expected_output/global_nonlocal_examples.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_expected_output/hygienic_naming.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_expected_output/nested_structures.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_expected_output/real_world_patterns.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/test_examples_expected_output/referential_transparency.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/tests/OBSERVATIONAL_EQUIVALENCE.md +0 -0
- {code_towel-1.732 → code_towel-1.772}/tests/hostile_cases/r133_formfeed.py +0 -0
- {code_towel-1.732 → code_towel-1.772}/tests/hostile_cases/r135_formfeed_line_shifts_splice.py +0 -0
|
@@ -0,0 +1,2078 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Towel are recorded here. This file summarizes releases;
|
|
4
|
+
[docs/RELEASE_LOG.md](docs/RELEASE_LOG.md) keeps the detailed engineering log,
|
|
5
|
+
and [docs/PRODUCTION_READINESS.md](docs/PRODUCTION_READINESS.md) records the
|
|
6
|
+
ecosystem evidence behind each claim. The format follows
|
|
7
|
+
[Keep a Changelog](https://keepachangelog.com/), and the project aims to follow
|
|
8
|
+
[Semantic Versioning](https://semver.org/).
|
|
9
|
+
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
## [1.772] - 2026-09-26
|
|
13
|
+
|
|
14
|
+
Changes since 1.732.
|
|
15
|
+
|
|
16
|
+
A typed run over a large project did not finish. Sphinx, 243 modules with mypy
|
|
17
|
+
and Pyright both strict, ran 3 h 21 min without reaching a fixed point where
|
|
18
|
+
the same run without types takes 11 minutes. Nearly all of that was the mypy
|
|
19
|
+
worker getting slower with every request it served, and most of the rest was
|
|
20
|
+
asking the checkers questions whose answers were already known. On
|
|
21
|
+
September 21 it reached a fixed point in 46 minutes, applying 380 refactorings
|
|
22
|
+
across 105 files, after which Sphinx's own test suite reported exactly what it
|
|
23
|
+
reported before: 2385 passed, 34 skipped, and the same six failures that
|
|
24
|
+
checkout already had. The audit rounds that followed made Towel decline more;
|
|
25
|
+
the release corpus, typed with `--cross-module` on one worker in the release corpus's Docker container, a Linux VM on the same M5 Max given all 18 cores and 8 GB of memory, running four projects at a time,
|
|
26
|
+
took 94 minutes over Sphinx and changed 55 files, its suite again unchanged.
|
|
27
|
+
|
|
28
|
+
Every figure in this file was measured on an Apple M5 Max with 18 cores and
|
|
29
|
+
128 GiB. This entry's were taken on macOS 26.5.1 with Python 3.12.13, the
|
|
30
|
+
machine otherwise idle, except where a figure says otherwise: the release
|
|
31
|
+
corpus ran in the release corpus's Docker container, a Linux VM on the same M5 Max given all 18 cores and 8 GB of memory, running four projects at a time. The Sphinx figures are Sphinx 9.1.1 at `e44a40e`, 243 modules with
|
|
32
|
+
mypy and Pyright both strict, checked by that project's own venv: **mypy
|
|
33
|
+
1.19.1 and pyright 1.1.407**. The mypy behaviour described below belongs to
|
|
34
|
+
that version; Towel's own checks run against a newer mypy and do not show it.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- Resolving which typing form a name denotes grew exponentially with the
|
|
38
|
+
number of functions that alias a name to itself (`tok = tok.next_token`,
|
|
39
|
+
`node = node.parent`), a common way to walk a linked structure. Twelve such
|
|
40
|
+
functions cost 469 million calls and 371 s, and yapf's `--cross-module`
|
|
41
|
+
fixed point took 2,083 s (macOS, one worker, `--no-types --no-format`, the
|
|
42
|
+
run sharing the machine with one other). The resolution is now computed once
|
|
43
|
+
per name and depth, and the same run takes 5 s with byte-identical output. The release
|
|
44
|
+
corpus's yapf and sphinx runs had timed out on it.
|
|
45
|
+
- inline-snapshot tests that differ only in their literal were extracted
|
|
46
|
+
into one helper, so `snapshot()` read a helper parameter at its call site
|
|
47
|
+
and passing tests failed: 68 of rich-click's 151. inline-snapshot's
|
|
48
|
+
readers (`snapshot`, `external`, `snapshot_arg`) are now recognized by
|
|
49
|
+
binding and their calls stay where they stand. Other callees that read
|
|
50
|
+
their caller's frame are reflection, a documented limitation.
|
|
51
|
+
- Region directives opened before a block and closed after it declined it
|
|
52
|
+
only for pylint and coverage.py. Every tool's region directive now does,
|
|
53
|
+
as that tool reads it (ruff, fmt, yapf, isort), and a block holding
|
|
54
|
+
`fmt: off` or `fmt: skip` carries its protected layout into the helper as
|
|
55
|
+
written.
|
|
56
|
+
- A file of the program that does not parse on the Python Towel runs on now
|
|
57
|
+
refuses the run in every mode, before anything is written. It used to be
|
|
58
|
+
skipped, so on 3.11 a 3.12 project's hand-applied decorator, or a test's
|
|
59
|
+
patch of `len` in 3.12 syntax, went unseen. The refusal names each file,
|
|
60
|
+
the newest Python the project declares, and an `--exclude` that clears
|
|
61
|
+
it. `--exclude` takes a file name as well as a directory name; an excluded
|
|
62
|
+
file that parses is still read as evidence, and one that does not parse
|
|
63
|
+
is taken as no part of the program. Fixtures in syntax newer than a
|
|
64
|
+
supported Python are stored as `.pynew`.
|
|
65
|
+
- mypy was taken to look at every file it was handed, so a file its own run
|
|
66
|
+
never checks, one outside `files` or under `ignore_errors`, got annotations
|
|
67
|
+
nothing verified, and typed runs declined whole projects under
|
|
68
|
+
`ignore_errors`. Such a file is now treated as one no checker covers. Each
|
|
69
|
+
mypy configuration group is checked in its own cache over the whole
|
|
70
|
+
candidate; candidate checks honour `--exclude` and the project's mypy
|
|
71
|
+
`exclude` as the baseline does; and a project package named `env` is no
|
|
72
|
+
longer taken for an environment. A check that fails for the project as it
|
|
73
|
+
stands now stops the run at once, naming the cause: sqlmodel ran 15
|
|
74
|
+
minutes and exited 1 with every proposal "not judged".
|
|
75
|
+
- A project decorator that is a plain wrapper was trusted although another
|
|
76
|
+
module rebinds it, as `app.checks.checked = typeguard.typechecked` before
|
|
77
|
+
the decorated module imports it, and code moved out of the instrumented
|
|
78
|
+
function. A decorator is now known only when nothing in the program may
|
|
79
|
+
rebind it, at every module its name passes through. A stacked decoration
|
|
80
|
+
by hand, `f = typechecked(register(f))`, is judged for every call along
|
|
81
|
+
the chain.
|
|
82
|
+
- A star import made every decorator of its module unknown; it now makes
|
|
83
|
+
unknown only the names it may bind, read as it may run (a literal
|
|
84
|
+
`__all__` with the module's public names, since an import cycle can run
|
|
85
|
+
the star import first).
|
|
86
|
+
- A lambda, comprehension or nested function whose parameter is spelled
|
|
87
|
+
like a name that becomes a helper parameter read the helper's parameter
|
|
88
|
+
instead of its own, and silently computed the wrong result:
|
|
89
|
+
`max(orders, key=lambda order: order["total"])` beside a parameter
|
|
90
|
+
`order`. A name in an f-string's format spec (`f"{v:>{width}}"`), and the
|
|
91
|
+
attribute and subscript loads in a `for`, `with` or comprehension target
|
|
92
|
+
(`for box.v in items`), kept the template's spelling and raised NameError.
|
|
93
|
+
Substitution now follows Python's scopes, and the instantiation check,
|
|
94
|
+
which passed all three because it compared names by spelling, compares by
|
|
95
|
+
binding.
|
|
96
|
+
- A call is declined where its thunk would read a closure variable that an
|
|
97
|
+
inlined comprehension in the same function rebinds; Python 3.12 and later
|
|
98
|
+
raise NameError there.
|
|
99
|
+
- A block holding its function's only binding of a name that the function
|
|
100
|
+
reads elsewhere is declined (`moves_only_binding`), unless the call binds
|
|
101
|
+
the name again. Moving it made the name stop being local, so the other
|
|
102
|
+
reads found a module name or builtin where the original raised
|
|
103
|
+
UnboundLocalError. A helper now declares `global` or `nonlocal` every name
|
|
104
|
+
its blocks bind by any construct, and sites whose functions declare a name
|
|
105
|
+
differently share no helper (`scope_declarations_differ`).
|
|
106
|
+
- A new import was written above a `#!` line or an encoding declaration, so
|
|
107
|
+
a script lost its shebang or a Latin-1 module was read as UTF-8, and it
|
|
108
|
+
could land ahead of a borrower's statement that runs at import. Imports
|
|
109
|
+
now go below the shebang, the encoding line, a file-wide `# type: ignore`
|
|
110
|
+
and the leading comments, and after the borrower's leading statements that
|
|
111
|
+
run code. A write that would change how Python decodes a file is refused.
|
|
112
|
+
- `--cross-module` hosted a helper in a module some platform cannot import,
|
|
113
|
+
such as one that imports `msvcrt`, or in one the program imports only
|
|
114
|
+
under a condition. It now requires what CPython's documentation marks as
|
|
115
|
+
platform-only, what a supported Python removed, and what a marker limits,
|
|
116
|
+
and hosts in a conditionally imported module only for a borrower that
|
|
117
|
+
already loads it.
|
|
118
|
+
- Build selections are read for every setting of each listed backend, in
|
|
119
|
+
each distribution's own configuration: Poetry `packages` and `include`,
|
|
120
|
+
PDM `includes`, `source-includes` and `package-dir`, hatch `only-packages`
|
|
121
|
+
and its default, setuptools `py-modules`, uv `module-name` and
|
|
122
|
+
`module-root`, flit's module and scikit-build-core `wheel.packages`. A
|
|
123
|
+
helper was hosted in a module the wheel left out.
|
|
124
|
+
- `--cross-module` no longer makes a module of one distribution import a
|
|
125
|
+
helper from another (`other_distribution`), which failed when the borrower
|
|
126
|
+
was installed against the other's released version.
|
|
127
|
+
- A typed run checked a change to a member with its own pyright
|
|
128
|
+
configuration only from that member's root, so a consumer in the outer
|
|
129
|
+
project was judged against the original text, and a helper that broke it
|
|
130
|
+
was accepted. A change is now checked from every configured root that
|
|
131
|
+
encloses it, and each copy shows the whole change.
|
|
132
|
+
- pyright's configured `venvPath`/`venv` was dropped from Towel's copy, so
|
|
133
|
+
the check silently used Towel's own interpreter: a helper typed `-> int`
|
|
134
|
+
was accepted where the project's library returns `str`. pyright now
|
|
135
|
+
resolves imports in the configured environment, and an environment it
|
|
136
|
+
cannot use refuses the typed run. Search paths that lead into an
|
|
137
|
+
environment name the original.
|
|
138
|
+
- A pyright-typed run was refused whenever any data file was not UTF-8 or a
|
|
139
|
+
directory was linked from outside. Only the files pyright reads as
|
|
140
|
+
configuration are now held to UTF-8, and links are reproduced as links, so
|
|
141
|
+
an import through an alias inside the project sees the candidate.
|
|
142
|
+
- pyright's messages could carry the private copy's paths, so a
|
|
143
|
+
pre-existing import cycle declined every change. The command line's
|
|
144
|
+
errors without a range are accepted. A forked child can no longer disable
|
|
145
|
+
a `PyrightOracle` or delete its copy. A target outside pyright's `include`
|
|
146
|
+
no longer waits 60 s for the language server.
|
|
147
|
+
- Under `--cross-module`, a directory named like a library, such as
|
|
148
|
+
`third_party/click/` holding `utils.py`, was taken for the library when
|
|
149
|
+
Towel's interpreter lacked it, and Towel wrote `from click.utils import`.
|
|
150
|
+
A directory lacking a module that an import from outside it names is now
|
|
151
|
+
in doubt, unless the project's metadata gives the project that name.
|
|
152
|
+
Requirements declared in `[tool.uv]` and `[tool.pdm]` dev dependencies,
|
|
153
|
+
hatch environments, a Pipfile and `*-requirements.txt` files are read
|
|
154
|
+
too.
|
|
155
|
+
- A file that a symbolic or hard link gives a second module name is now in
|
|
156
|
+
doubt, and the report names the link. Before, its import-time code could
|
|
157
|
+
run twice.
|
|
158
|
+
- An import that attests nothing, because it is guarded, type-only, or in a
|
|
159
|
+
file that changes `sys.path`, no longer locates a name or puts one in
|
|
160
|
+
doubt; graphene's `setup.py` refused every `--cross-module` run. An
|
|
161
|
+
import of a module the tree lacks no longer makes a stray
|
|
162
|
+
`src/__init__.py` a used package, and a type-only one no longer leaves its
|
|
163
|
+
file unchanged.
|
|
164
|
+
- A formatter that changed or failed on the code it was given ended a
|
|
165
|
+
directory run with exit 1 and nothing written: Black normalizing a
|
|
166
|
+
docstring, Black unable to parse a snippet, ruff exiting nonzero or timing
|
|
167
|
+
out. The proposal is now declined as "the formatter changed its code or
|
|
168
|
+
failed", and the run goes on.
|
|
169
|
+
- A differing value holding a slice outside its own subscript, such as
|
|
170
|
+
numbagg's `data[0, :50]`, became the argument `lambda: (0, :50)`, which is
|
|
171
|
+
not Python. Such a value is no longer made a parameter.
|
|
172
|
+
- An out-of-place run copied a pending journal into its stage and refused
|
|
173
|
+
its own write there; a journal inside the target was published into the
|
|
174
|
+
output. Journals are left out of both, and the run warns, naming the
|
|
175
|
+
project's journal and `towel recover`.
|
|
176
|
+
- A refusal over a pending journal named `towel recover` even where recovery
|
|
177
|
+
would then fail. The remedy now runs recovery's own checks and names what
|
|
178
|
+
works.
|
|
179
|
+
- One hard-linked file failed a whole in-place run at publication, losing
|
|
180
|
+
every other file's refactoring. Proposals that would write it are now
|
|
181
|
+
declined up front, and the rest is written.
|
|
182
|
+
- `--no-interactive` still asked "Analyze anyway?" for an input without a
|
|
183
|
+
`.py` suffix. A crash inside mypy or a plugin was reported only as
|
|
184
|
+
"SystemExit: 2"; it now names the exception and mypy's last output.
|
|
185
|
+
- Under forked evaluation the rejection trace counted some pairs twice, and
|
|
186
|
+
which pairs were duplicates depended on the split between workers. The
|
|
187
|
+
trace is now the serial run's, line for line.
|
|
188
|
+
- The sdist left out 33 test fixture configuration files.
|
|
189
|
+
- Code moved out of functions whose decorators rewrite or compile their
|
|
190
|
+
bodies. typeguard's `@typechecked` recompiles a function from its source
|
|
191
|
+
with checks added, and numba's `@njit` compiles it. The moved code went
|
|
192
|
+
unchecked or stopped compiling, and typeguard's own
|
|
193
|
+
`test_unpacking_assign_star_no_annotation_fail[typechecked]` failed after
|
|
194
|
+
a root run.
|
|
195
|
+
|
|
196
|
+
Code now moves out of a function, or a helper into one, only when
|
|
197
|
+
everything that can reach that code is known to leave bodies alone:
|
|
198
|
+
- decorators on the function and on every enclosing function and class;
|
|
199
|
+
- decorators applied by hand (`fast = numba.njit(kernel)`,
|
|
200
|
+
`f = typechecked(f)`, `njit(cache=True)(kernel)`);
|
|
201
|
+
- the machinery of every enclosing class: its metaclass,
|
|
202
|
+
`__init_subclass__` and `__getattribute__`.
|
|
203
|
+
|
|
204
|
+
"Known" means one of two things. The decorator or base is on a verified
|
|
205
|
+
list, each entry recording the source it was read in: the standard
|
|
206
|
+
library's decorators, pytest's and click's, `rich.repr.auto`, and about
|
|
207
|
+
forty standard-library bases such as `unittest.TestCase`,
|
|
208
|
+
`asyncio.Protocol` and `logging.Handler`, which the suite re-checks by
|
|
209
|
+
introspection on every interpreter. Or it is a project decorator shown to
|
|
210
|
+
return, register or plainly wrap the function.
|
|
211
|
+
|
|
212
|
+
Anything else declines the pair as `decorator_may_transform_body[...]` or
|
|
213
|
+
`class_machinery_may_transform_methods[...]`, naming the culprit. Across
|
|
214
|
+
ten projects, 672 of 721 refactorings remain.
|
|
215
|
+
- With `--cross-module`, a block holding an `assert` is shared only between
|
|
216
|
+
modules pytest rewrites alike, following pytest's own rules and the
|
|
217
|
+
project's pytest configuration. Otherwise a failing assert would lose the
|
|
218
|
+
explanation pytest adds to its message.
|
|
219
|
+
- A `TestCase` subclass can now take a class-private method helper, which
|
|
220
|
+
neither unittest nor pytest collects as a test.
|
|
221
|
+
- An in-place `dry` refused whenever any `.towel-transaction-*` path
|
|
222
|
+
existed under its target. That included an empty directory, one inside
|
|
223
|
+
`.git`, and a journal naming only untouched files, and for the empty
|
|
224
|
+
directory the suggested `towel recover` failed too.
|
|
225
|
+
|
|
226
|
+
A run now stops only for a pending journal that may name a file it would
|
|
227
|
+
change: one whose manifest names such a file, or which has no readable
|
|
228
|
+
manifest. The refusal names the `towel recover` command, or says why
|
|
229
|
+
recovery would not read the journal and what to do instead. `preview`
|
|
230
|
+
and `rename-helpers` warn by the same rule.
|
|
231
|
+
- `towel recover` accepts a journal named through a symbolic link, such as
|
|
232
|
+
one under macOS's `/tmp`, and each refusal says why.
|
|
233
|
+
- A file's project root, coverage exclusions, builtin evidence and
|
|
234
|
+
declared dependencies were looked up at every call site: 12,205 times
|
|
235
|
+
for one input on a benchmark of 50 functions. They are now looked up
|
|
236
|
+
once per input for the run, and so are a function's own locals, keyed by
|
|
237
|
+
block site. CPU time against 1.732 went from 1.71 to 1.27 times on that
|
|
238
|
+
benchmark, and from 1.37 to 1.08 times on rich.
|
|
239
|
+
- A project's own package named `env` or `venv` is refactored. An
|
|
240
|
+
environment is now recognised by `pyvenv.cfg` or `conda-meta`, whatever
|
|
241
|
+
it is called.
|
|
242
|
+
- The rejection trace names each block's file:
|
|
243
|
+
`REJECT[reason]: path::function@(start, end) <-> ...`. A pair the
|
|
244
|
+
extractor cannot render is traced as `unsupported_extraction`, and one
|
|
245
|
+
found while clustering no longer ends the analysis.
|
|
246
|
+
`return_versus_variables` replaces the mislabelled
|
|
247
|
+
`incomplete_return_coverage_block2`.
|
|
248
|
+
- The tests and scripts shipped in the sdist carry the Apache licence
|
|
249
|
+
header, and `scripts/add_copyright_headers.py --check` enforces it.
|
|
250
|
+
- A typed run on a project reached through a symbolic link now verifies its
|
|
251
|
+
changes. On macOS that includes every temporary directory, since `/var`
|
|
252
|
+
is `/private/var`. mypy prints each file by its resolved path, and Towel
|
|
253
|
+
had matched its answers against the path it was asked about. So every
|
|
254
|
+
probe went unanswered, with three consequences:
|
|
255
|
+
- every proposal was declined as code the checker does not look at;
|
|
256
|
+
- every subtype question read yes;
|
|
257
|
+
- the check for names typed as `Any` saw no answers, so a missing module
|
|
258
|
+
could go unnoticed.
|
|
259
|
+
|
|
260
|
+
File identities now compare one resolved path throughout. The test suite
|
|
261
|
+
fails any typed test whose checker declined everything as unlooked and
|
|
262
|
+
verified nothing, unless that decline is what the test is about.
|
|
263
|
+
- A parallel run could hang for ever. Pair-evaluation workers were forked
|
|
264
|
+
while other threads ran:
|
|
265
|
+
- tqdm's monitor, in every run with the default progress bars;
|
|
266
|
+
- the progress heartbeat, from a directory run's second pass;
|
|
267
|
+
- each warm pyright session's reader.
|
|
268
|
+
|
|
269
|
+
A forked child keeps every lock as it stood. So a worker forked while one
|
|
270
|
+
of those threads was writing to stderr waited on stderr's lock when it
|
|
271
|
+
flushed at exit, and the run waited on the worker. Towel now ends its
|
|
272
|
+
threads for the moment of the fork and starts them again after, so
|
|
273
|
+
workers fork from a process with one thread. A thread Towel does not run,
|
|
274
|
+
or one that does not end within 2 s, keeps evaluation in the process, and
|
|
275
|
+
the run says so once, naming it. Python 3.12 and 3.13 no longer warn that
|
|
276
|
+
a multi-threaded process is forking.
|
|
277
|
+
- A block that rebinds a name bound before it is now declined, whatever
|
|
278
|
+
construct rebinds it: a `for` or `with` target, a `match` capture, a
|
|
279
|
+
nested `def` or `class`, an import, or a walrus. So
|
|
280
|
+
`i = -1; for i in xs: ...; i * 2` keeps its `-1` when `xs` is empty. It
|
|
281
|
+
had raised `UnboundLocalError` in the helper.
|
|
282
|
+
- `count += 1` and `del count` after a block now count as reads of `count`.
|
|
283
|
+
The helper returns it, or the block is declined, instead of the later
|
|
284
|
+
statement raising `UnboundLocalError`.
|
|
285
|
+
- A block that reads a local before binding it, such as
|
|
286
|
+
`scale = scale(n)`, is declined where the call site may not have the name
|
|
287
|
+
bound. The call had succeeded there, using the module's `scale`.
|
|
288
|
+
- Blocks equal up to their binders' names now share a helper only where no
|
|
289
|
+
read can find a renamed binder unbound. So an `UnboundLocalError` still
|
|
290
|
+
names each site's own variable.
|
|
291
|
+
- Definite assignment now unbinds a deleted name for everything the
|
|
292
|
+
deletion may precede: the next loop iteration, a handler or `finally`, a
|
|
293
|
+
`with`, `case` or `else` body, and an `except*` clause. Such a name had
|
|
294
|
+
been passed eagerly, and raised before an effect the original ran first.
|
|
295
|
+
- A block that started at the second statement of a `;` line, or ended at
|
|
296
|
+
the first, was spliced by whole lines, which deleted the other statement.
|
|
297
|
+
`a = n + 1; b = a * 2`, with the block at `b`, lost `a = n + 1`, and a
|
|
298
|
+
trailing `; log.append(...)` was lost the same way. The call now replaces
|
|
299
|
+
exactly the block's text. It keeps the rest of the first and last lines,
|
|
300
|
+
including a one-line compound body's header and a backslash continuation.
|
|
301
|
+
- A thunk was passed eagerly although the helper evaluated something first
|
|
302
|
+
that could run code or raise, so its effect moved ahead of that
|
|
303
|
+
something. The cases were:
|
|
304
|
+
- a lambda's default arguments;
|
|
305
|
+
- a set or dict display hashing its keys;
|
|
306
|
+
- `*` and `**` unpacking;
|
|
307
|
+
- a read of a global that raises `NameError`;
|
|
308
|
+
- dead code after `return` or `raise`.
|
|
309
|
+
|
|
310
|
+
A thunk now goes eagerly only after steps on an allowlist of forms, in
|
|
311
|
+
CPython's evaluation order, that can neither run code nor raise.
|
|
312
|
+
- A pair is declined as `directive_on_shared_line` when its block shares a
|
|
313
|
+
physical line with code that stays at the call site, and that line
|
|
314
|
+
carries a tool directive or is excluded from coverage. The directive
|
|
315
|
+
governs the whole line, and splicing in the call would part it from some
|
|
316
|
+
of that code.
|
|
317
|
+
- The import sorter could reorder a file's own imports, and so the order
|
|
318
|
+
their modules run in. With ruff excluding `pkg/app.py`, Towel rewrote
|
|
319
|
+
`from pkg import zeta` / `from pkg import alpha` as
|
|
320
|
+
`from pkg import alpha, zeta`, and a plugin registry's order flipped.
|
|
321
|
+
httpcore's byte-compared `_sync` twins were re-sorted the same way.
|
|
322
|
+
|
|
323
|
+
Sorting now respects each tool's own file selection, judged for the
|
|
324
|
+
project's file rather than the run's staged copy:
|
|
325
|
+
- ruff: `exclude`, `extend-exclude`, `lint.exclude` and
|
|
326
|
+
`per-file-ignores`;
|
|
327
|
+
- isort: `skip`, `extend_skip`, `skip_glob`, `extend_skip_glob` and
|
|
328
|
+
`skip_gitignore`, skipped directories included.
|
|
329
|
+
|
|
330
|
+
A file is sorted only if its original imports are already in the
|
|
331
|
+
sorter's order, and a result is kept only if the file's own imports keep
|
|
332
|
+
their order. So only the imports Towel added can move, and a file left
|
|
333
|
+
unsorted is reported once. `ruff format` and Black respect their
|
|
334
|
+
exclusions too.
|
|
335
|
+
- `# ty: ignore[...]` and `# pyrefly: ignore` were read as plain comments.
|
|
336
|
+
Towel moved the code they silenced into call-site lambdas and left the
|
|
337
|
+
ignores behind. On griffe, its own `ty check` went from 2 to 23
|
|
338
|
+
diagnostics. These are now directives, as are:
|
|
339
|
+
- `# zuban: ignore` and `# nosemgrep`;
|
|
340
|
+
- Fixit's `# lint-ignore` and `# lint-fixme`;
|
|
341
|
+
- ruff's `file-ignore`, `disable` and `enable`;
|
|
342
|
+
- pyrefly's `ignore-errors`.
|
|
343
|
+
|
|
344
|
+
An ignore on a line of its own governs the next line of code, as those
|
|
345
|
+
tools read it.
|
|
346
|
+
- With `--cross-module`, Towel took a project directory named like a
|
|
347
|
+
library it requires for that library, when its own interpreter lacked
|
|
348
|
+
the library. For example, `click/` sharing one module with click, while
|
|
349
|
+
the project depends on `click>=8`. The import it wrote then failed once
|
|
350
|
+
the package was installed. A requirement declared in any of these now
|
|
351
|
+
puts the name in doubt: pyproject.toml, setup.cfg, a uv, Poetry, PDM or
|
|
352
|
+
Pipenv lockfile, or requirements*.txt.
|
|
353
|
+
- With `--cross-module`, a library installed in an environment inside the
|
|
354
|
+
project root, such as the project's own `.venv`, was taken for the
|
|
355
|
+
project's own code. It now counts as installed outside the project.
|
|
356
|
+
- With `--cross-module`, Towel could host a helper in a module the build
|
|
357
|
+
leaves out of the wheel, so the installed package failed to import. It
|
|
358
|
+
now reads the declared exclusions of hatch, setuptools, MANIFEST.in,
|
|
359
|
+
Poetry, PDM, uv, flit and scikit-build-core, and every `.gitignore`. It
|
|
360
|
+
uses them only to put a host in doubt, never to name a module. An import
|
|
361
|
+
inside a function no longer shows that a directory ships.
|
|
362
|
+
- With `--cross-module`, a top-level module inside a package, imported by
|
|
363
|
+
its bare name from beside it, went unrefused. The borrower was given a
|
|
364
|
+
relative import that fails where the original works. It now refuses.
|
|
365
|
+
`from . import x` and `from pkg import x` of a missing submodule are now
|
|
366
|
+
reported, and their file is left unchanged.
|
|
367
|
+
- Each import problem's remedy now fits its kind:
|
|
368
|
+
- `--exclude` for a stray copy in the tree;
|
|
369
|
+
- an environment where the name is this tree, or one without it, for an
|
|
370
|
+
installed copy;
|
|
371
|
+
- a rename, `--exclude` or a dropped requirement, for a required
|
|
372
|
+
distribution.
|
|
373
|
+
- Typed mode now reads per-module `follow_imports` as mypy does. An error
|
|
374
|
+
in a module its own section follows was dropped, and a helper the
|
|
375
|
+
project's mypy rejects was let through.
|
|
376
|
+
- A typed run over part of a project (`towel dry tests tests`) found the
|
|
377
|
+
project's own packages in a stale installed copy, and wrote annotations
|
|
378
|
+
for the installed API. It now finds them in the tree.
|
|
379
|
+
- Probes now name modules as mypy does under `explicit_package_bases`,
|
|
380
|
+
`mypy_path` and namespace packages. A probe build that fails is now a
|
|
381
|
+
checker failure. It had been read as unreachable code, which ended PEP
|
|
382
|
+
420 projects at a false fixed point with exit status 0.
|
|
383
|
+
- A changed file outside the configured `files` is named as its importer
|
|
384
|
+
names it, so mypy no longer refuses the build with "found twice".
|
|
385
|
+
- A mypy configuration that mypy only warns about no longer refuses the
|
|
386
|
+
typed run, and the warning is passed on. Examples are an unknown option,
|
|
387
|
+
a global option in an override, or a dropped Python version.
|
|
388
|
+
- A typed run accounted for errors on the lines a change wrote by message
|
|
389
|
+
alone. Merging two copies of a block then freed the second copy's errors
|
|
390
|
+
to hide a new error with the same message. A helper typed `int | str`,
|
|
391
|
+
whose `p + p` repeated two pre-existing messages, was accepted, and the
|
|
392
|
+
project's mypy rejected it.
|
|
393
|
+
|
|
394
|
+
Each error is now accounted for by an error of the original that stood
|
|
395
|
+
where it stands:
|
|
396
|
+
- in the helper, the same message at the same statement of one copy of
|
|
397
|
+
the block, each error once;
|
|
398
|
+
- at a call site, its own copy's.
|
|
399
|
+
- A file whose imports the checker cannot resolve is now left alone even
|
|
400
|
+
when the configuration silences that report. Examples are
|
|
401
|
+
`ignore_missing_imports` and pyright's `reportMissingImports = "none"`.
|
|
402
|
+
Every checker is asked what each import binds. A subtype question about
|
|
403
|
+
a type the checker sees as `Any` answers unknown, so it no longer folds a
|
|
404
|
+
union.
|
|
405
|
+
- A file that cannot be decoded is left out of a typed run and left
|
|
406
|
+
unchanged, as without types, instead of refusing the run.
|
|
407
|
+
- Files that pyright's configuration excludes or ignores are known before
|
|
408
|
+
the run, and pyright is not asked about them. That removes a minute-long
|
|
409
|
+
stall. Another checker that checks them settles them, rather than their
|
|
410
|
+
being declined as unreachable. Where no checker covers a file, its helper
|
|
411
|
+
takes only the annotations its sites declare.
|
|
412
|
+
- A class the checker names by its whole path, which the helper's module
|
|
413
|
+
does not import, is now imported under `TYPE_CHECKING` and named
|
|
414
|
+
directly, as documented. It had been written `Any`.
|
|
415
|
+
- A verdict that nothing rebinds a name a block reads was memoized under the
|
|
416
|
+
structure of the block and its function. It then answered for identical
|
|
417
|
+
code whose enclosing function rebinds that name with `nonlocal`, in the
|
|
418
|
+
same module or in another. The name was passed to the helper as an
|
|
419
|
+
argument, and so read before the rebinding.
|
|
420
|
+
|
|
421
|
+
The same key let a top-level copy of a block lend its bindings and escape
|
|
422
|
+
verdict to an identical copy inside a loop. That copy was then extracted
|
|
423
|
+
without returning the variable the loop reads next.
|
|
424
|
+
|
|
425
|
+
Block guards and per-block analyses are now memoized per block site: the
|
|
426
|
+
module's source, and the positions of the function and the block. On
|
|
427
|
+
rich, one core, analysis takes about 6% longer, and the output is
|
|
428
|
+
unchanged. A test runs the analysis with every memo disabled and requires
|
|
429
|
+
the same verdicts, so a cache key that leaves out a dependency fails the
|
|
430
|
+
suite.
|
|
431
|
+
- A block that spells a class-private name only as a parameter, or in an
|
|
432
|
+
import's module or member, was moved out of its class. There the name is
|
|
433
|
+
not mangled:
|
|
434
|
+
- `(lambda __p=0: 7)(__p=y)` raised `TypeError` in a method and returned 7
|
|
435
|
+
in a module-level helper;
|
|
436
|
+
- `import __tool` inside `class A` imports `_A__tool`, but `__tool` in a
|
|
437
|
+
helper.
|
|
438
|
+
|
|
439
|
+
Every position CPython mangles now counts. This is checked against the
|
|
440
|
+
interpreter's own symbol tables for all 37 identifier positions in its
|
|
441
|
+
grammar. A call's keywords are compared with the parameters as the class
|
|
442
|
+
stores them, so the same call is declined even when the helper stays in
|
|
443
|
+
the class, where its `TypeError` would name the helper.
|
|
444
|
+
- A typing name that a helper's annotation needed was imported publicly
|
|
445
|
+
(`from typing import Callable`). That replaced a same-named binding in
|
|
446
|
+
every module that star-imports the host. It also replaced one in the host
|
|
447
|
+
itself, where a star import, a `try` or an assignment had bound the name.
|
|
448
|
+
`case Callable():` then raised `TypeError`, and a package's own `Any`
|
|
449
|
+
became `typing.Any`. On click, rich and packaging, 13 modules gained a
|
|
450
|
+
public name.
|
|
451
|
+
|
|
452
|
+
Typing names are now reached through a private `import typing as
|
|
453
|
+
_typing`, or through the module's own sole `import typing [as t]`.
|
|
454
|
+
- The `from typing import TYPE_CHECKING` written for a new guard rebound a
|
|
455
|
+
module's own `TYPE_CHECKING`, for example
|
|
456
|
+
`from pkg.flags import DEBUG as TYPE_CHECKING`, and turned its flag from
|
|
457
|
+
True to False. A new guard now reads `if _typing.TYPE_CHECKING:`, which
|
|
458
|
+
mypy and pyright both recognise, unless the module's sole binding of that
|
|
459
|
+
name is typing's own.
|
|
460
|
+
- A class imported for the checker is bound under a private alias
|
|
461
|
+
(`from pkg.models import Item as _Item`, spelled `"_Item"`), since
|
|
462
|
+
checkers carry that import into every star-importer.
|
|
463
|
+
- A new guard carries `# pragma: no cover` only where the project's
|
|
464
|
+
coverage exclusions would not otherwise match it, so coverage is
|
|
465
|
+
unchanged.
|
|
466
|
+
- A typing import was skipped when the same line appeared anywhere in the
|
|
467
|
+
file, even inside a function, and the helper's annotation then raised
|
|
468
|
+
`NameError` at import.
|
|
469
|
+
- On Python 3.11 and 3.12, Towel compared the syntax trees it builds with
|
|
470
|
+
parsed ones by `ast.dump`. Before 3.13, `ast.dump` spells a field the
|
|
471
|
+
constructor was not given differently from one set empty. On 3.12 the
|
|
472
|
+
helper's `FunctionDef` has no `type_params`, so nearly every helper was
|
|
473
|
+
declined as "could not be rendered". Other comparisons could also miss:
|
|
474
|
+
- the subtype relation that needs no checker;
|
|
475
|
+
- union normalisation;
|
|
476
|
+
- the instantiation check;
|
|
477
|
+
- structural keys and proposal identities.
|
|
478
|
+
|
|
479
|
+
Each miss cost a refactoring or precision, never soundness. Every such
|
|
480
|
+
comparison now uses one spelling on every supported Python, the one
|
|
481
|
+
3.13's `ast.dump` writes. On 3.12 this recovers 5 typed refactorings in
|
|
482
|
+
the corpus: packaging 2, mistune 2 and nox 1.
|
|
483
|
+
- The test suite passes on Python 3.11, 3.12 and 3.13. Fixtures in PEP 695
|
|
484
|
+
syntax, and tests of `typing.override`, are skipped where the running
|
|
485
|
+
Python lacks them.
|
|
486
|
+
- The fallback annotations of a typed run are built from the checker's
|
|
487
|
+
answer. Under strict mypy, the rung that made every annotation `Any` had
|
|
488
|
+
verified 4 times in 46, and 32 of its refusals were "Returning Any". Now
|
|
489
|
+
`Any` goes only where the errors point:
|
|
490
|
+
- a parameter read on a failing line;
|
|
491
|
+
- `Callable[[...], Any]` for a thunk whose result is at fault;
|
|
492
|
+
- the parameter an "Argument N" error names.
|
|
493
|
+
|
|
494
|
+
A return is loosened least, to `<declared> | Any`, which is also how a
|
|
495
|
+
helper returning `NotImplemented` is typed. Under mypy's
|
|
496
|
+
`warn_return_any`, the every-`Any` rung is not tried. The unannotated
|
|
497
|
+
helper is not tried under `disallow_untyped_defs`, or in a module whose
|
|
498
|
+
every function is annotated. Such a proposal is declined saying so;
|
|
499
|
+
before, idna's output failed its `mypy --strict` CI with 4 errors.
|
|
500
|
+
- An extraction that no helper signature can type is declined early, under
|
|
501
|
+
a named reason in the run's summary:
|
|
502
|
+
- it narrows what its caller reads after the call;
|
|
503
|
+
- it narrows what a call-site lambda reads;
|
|
504
|
+
- it declares its class's attributes;
|
|
505
|
+
- it completes its caller's partial type.
|
|
506
|
+
|
|
507
|
+
Two of these are decided from the proposal alone, without asking the
|
|
508
|
+
checker.
|
|
509
|
+
- A declined proposal is replayed at a rehearing, rather than checked
|
|
510
|
+
again, only while nothing it could depend on has changed: its rendered
|
|
511
|
+
files, the files its errors lie in and what they import, and the
|
|
512
|
+
baseline's record of known errors. On packaging, checks that exactly
|
|
513
|
+
repeated an earlier one went from 16 to 0.
|
|
514
|
+
- A number of annotation fixes:
|
|
515
|
+
- outside a class, a `Self` the sites revealed becomes a type variable
|
|
516
|
+
bound to their classes;
|
|
517
|
+
- no annotation is written as the string `"None"`, which mypy 2 rejects
|
|
518
|
+
under `native_parser`;
|
|
519
|
+
- a site revealed as `Any` no longer erases another site's type in a
|
|
520
|
+
join;
|
|
521
|
+
- a copied declaration gives way to the narrower type the block saw;
|
|
522
|
+
- a method helper is placed where the attributes it assigns keep their
|
|
523
|
+
declarations;
|
|
524
|
+
- every rung writes only the type-only imports its own annotations use.
|
|
525
|
+
- A class name shortened inside a subscripted annotation (`dict[Item, int]`)
|
|
526
|
+
was left unquoted while `Item` was imported only under `TYPE_CHECKING`.
|
|
527
|
+
mypy accepted it, but the module raised `NameError` on import.
|
|
528
|
+
- Coverage exclusions set in a project's configuration now count the way a
|
|
529
|
+
`# pragma: no cover` comment does. Examples are `if __name__ ==
|
|
530
|
+
.__main__.:`, `raise NotImplementedError` and `@overload` in
|
|
531
|
+
`exclude_lines` or `exclude_also`. Before, only the comment counted, so
|
|
532
|
+
such an entry could move excluded code into a helper that coverage
|
|
533
|
+
measures. Towel reads the configuration as coverage.py 7.16 does: from
|
|
534
|
+
`COVERAGE_RCFILE`, `.coveragerc`, `.coveragerc.toml`, `setup.cfg` or
|
|
535
|
+
`tox.ini` `[coverage:report]`, or `pyproject.toml`
|
|
536
|
+
`[tool.coverage.report]`. Its three default patterns count as well. Where
|
|
537
|
+
`exclude_lines` leaves the pragma out, a `# pragma: no cover` comment
|
|
538
|
+
excludes nothing, so it no longer declines a pair.
|
|
539
|
+
- A type-only import now joins the module's existing `TYPE_CHECKING` guard,
|
|
540
|
+
recognised by binding as the import graph recognises it. Towel had found
|
|
541
|
+
the guard by its exact text, and indented the new import four spaces:
|
|
542
|
+
- packaging's `if TYPE_CHECKING: # pragma: no cover` got a second guard;
|
|
543
|
+
- a guard body indented two spaces or with a tab raised `IndentationError`;
|
|
544
|
+
- in a module that rebinds `TYPE_CHECKING = True`, the import was joined
|
|
545
|
+
to a block that runs.
|
|
546
|
+
|
|
547
|
+
The import now takes the indentation of the guard's own body.
|
|
548
|
+
- A `--cross-module` run refuses only for a problem that leaves a name of the
|
|
549
|
+
code being refactored in doubt. Such problems are:
|
|
550
|
+
- an ambiguous name, such as a stale `build/lib` copy of the package, which
|
|
551
|
+
`--exclude build` still clears;
|
|
552
|
+
- a file reachable under two names;
|
|
553
|
+
- a top-level name found inside a package;
|
|
554
|
+
- a relative import that climbs out of its package.
|
|
555
|
+
|
|
556
|
+
An import of a module the tree lacks refuses nothing, whether the run is
|
|
557
|
+
from the root, on a package or on a sub-package. Such imports had stopped
|
|
558
|
+
every run on sphinx, astroid and prompt-toolkit, and on seven packages
|
|
559
|
+
whose modules import the `_version.py` their build generates, which a
|
|
560
|
+
fresh clone lacks. With the refusal set aside, the package's name was
|
|
561
|
+
flagged, so every helper its modules could share was declined: 3,858 of
|
|
562
|
+
the 3,860 cross-module pairs on sphinx.
|
|
563
|
+
|
|
564
|
+
Over 140 corpus projects, runs on the package refused 14 before and 4
|
|
565
|
+
after. Runs from the root, measured only after the first of these changes,
|
|
566
|
+
refused 24 and now refuse 18. Every remaining refusal is for a name in
|
|
567
|
+
doubt, such as the four module names sphinx's `tests/roots` each defines
|
|
568
|
+
more than once.
|
|
569
|
+
- In a `--cross-module` run, a file making such an import is left exactly as
|
|
570
|
+
it was. It hosts no helper, borrows none, and none of its own duplicates
|
|
571
|
+
are extracted. The run names each such file, with a remedy that fits:
|
|
572
|
+
test data or an example can be left out with `--exclude`, and a package's
|
|
573
|
+
generated module appears once the project is installed. When the file is
|
|
574
|
+
a package's `__init__.py`, that package's modules still share helpers
|
|
575
|
+
among themselves, and the run says in one line that they host none for
|
|
576
|
+
modules outside the package.
|
|
577
|
+
- A run on a sub-package now refuses, instead of going on with every helper
|
|
578
|
+
declined, when a relative import elsewhere in the package climbs out of
|
|
579
|
+
it.
|
|
580
|
+
- An import below a module file names the module it lacks: `util.gone`, not
|
|
581
|
+
`util`, when `util.py` exists. A module file that registers submodules at
|
|
582
|
+
run time, as `six.py` registers `six.moves`, is not reported as missing
|
|
583
|
+
them, wherever it sits.
|
|
584
|
+
- A typed run no longer changes code that the type checker does not look
|
|
585
|
+
at. That is code the checker takes to be unreachable on the platform and
|
|
586
|
+
Python version it checks for, and there it reports nothing. On darwin,
|
|
587
|
+
mypy skips a module guarded by `assert sys.platform == "win32"`, so
|
|
588
|
+
Towel's check of changes there was vacuous. trio's CI, which checks linux,
|
|
589
|
+
darwin and win32, found 14 errors in output Towel had accepted.
|
|
590
|
+
|
|
591
|
+
Every configured checker is now asked, by a `reveal_type` probe, whether
|
|
592
|
+
it looks at the lines a change writes. A change it does not look at is
|
|
593
|
+
declined as not verifiable. The regions are named before the run. trio's
|
|
594
|
+
CI now passes on all three platforms.
|
|
595
|
+
- An import under `if TYPE_CHECKING:` never runs, and the cycle guard no
|
|
596
|
+
longer follows it. Modules that name each other's classes only for the
|
|
597
|
+
checker can now share a helper. A type-only import that Towel writes no
|
|
598
|
+
longer refuses later pairs between the same two modules; on mistune it
|
|
599
|
+
had cost two typed refactorings.
|
|
600
|
+
|
|
601
|
+
The guard is recognised by resolving the name: typing's or
|
|
602
|
+
typing_extensions' `TYPE_CHECKING`, however imported or aliased, or the
|
|
603
|
+
module's own `TYPE_CHECKING = False`. Its `else` branch still counts as
|
|
604
|
+
an import that runs, as do `if not TYPE_CHECKING:` and a shadowed or
|
|
605
|
+
unresolvable name. A module's requirements now include the imports in the
|
|
606
|
+
guard's `else`, which had been dropped.
|
|
607
|
+
- A type variable over classes from two modules now imports the one its host
|
|
608
|
+
lacks, under `TYPE_CHECKING`, spelled as the program's own imports spell
|
|
609
|
+
it. packaging's validation and required-key errors, and mistune's
|
|
610
|
+
renderer `__call__`, are now typed without `Any`.
|
|
611
|
+
- Comments inside a moved block were lost with it, because the helper was
|
|
612
|
+
rendered from its syntax tree. That included tool directives, such as
|
|
613
|
+
asyncstdlib's and mashumaro's `# type: ignore`, as well as
|
|
614
|
+
`# pyright: ignore`, `# noqa` and `# pragma: no cover`, and every
|
|
615
|
+
explanatory comment. A lost directive changes what a checker, linter or
|
|
616
|
+
coverage tool reports.
|
|
617
|
+
|
|
618
|
+
The helper now carries each comment beside the code it was written for,
|
|
619
|
+
in order. It also keeps the grouping parentheses and trailing commas that
|
|
620
|
+
held a list split across lines, so Black and ruff reproduce the source
|
|
621
|
+
layout. Where a formatter would move a directive off its code's line,
|
|
622
|
+
that helper is written unformatted. Comments above or below a moved block
|
|
623
|
+
stay with its call, and `preview` shows the helper's comments.
|
|
624
|
+
- A typed run no longer refuses a project whose type check already reports
|
|
625
|
+
errors. It reports them first, and rejects a change only for an error they
|
|
626
|
+
do not account for:
|
|
627
|
+
- In a file no change touched, an error must match one at the same line.
|
|
628
|
+
- In a file a change touched, the two texts are aligned by a line diff. An
|
|
629
|
+
error on a line the change left alone must match the original's error
|
|
630
|
+
on that line. An error on a line the change wrote must match the
|
|
631
|
+
original's error where that code stood, as described above.
|
|
632
|
+
- A message that names a line fails closed.
|
|
633
|
+
- Each written change's check becomes the reference for the next.
|
|
634
|
+
- The cold confirmation compares the same way.
|
|
635
|
+
|
|
636
|
+
In a study of 20 corpus projects in their own environments, the old rule
|
|
637
|
+
let 5 through. 19 are now refactored with types, for 125 refactorings, 77
|
|
638
|
+
of them in the 14 newly admitted projects.
|
|
639
|
+
- With mypy and pyright both configured, the combined check stopped at the
|
|
640
|
+
first checker that reported anything. Against pre-existing mypy errors,
|
|
641
|
+
pyright was then never asked about any candidate. Every checker is now
|
|
642
|
+
asked until one reports a new error.
|
|
643
|
+
- A file where the original check leaves a name the checker cannot type is
|
|
644
|
+
not changed. Such names come from an unresolved or untyped import, a
|
|
645
|
+
missing stub, an untyped decorator or an `Any` base class. A change there
|
|
646
|
+
would be checked against `Any`, and could not fail. The run names these
|
|
647
|
+
files up front and counts their proposals as not verifiable.
|
|
648
|
+
- The cold confirmation checks the original cold before it treats as new an
|
|
649
|
+
error that only it reports. On trio, pyright's language server and command
|
|
650
|
+
line disagreed about files no change touched, so every typed run failed at
|
|
651
|
+
the end.
|
|
652
|
+
- Only a checker that cannot run still refuses a typed run. The corpus
|
|
653
|
+
harness reruns a project without types only in that case, and records
|
|
654
|
+
every other project's pre-existing errors.
|
|
655
|
+
- Typed runs read every type spelling that mypy 1.x, mypy 2.x and pyright
|
|
656
|
+
print. A regular expression took the last `) -> ` as the end of a
|
|
657
|
+
parameter list, so `lambda: parse_extras` in packaging's `_parser` was
|
|
658
|
+
annotated as the function the lambda returns. Now read correctly:
|
|
659
|
+
- a function returning `None` (`def (builtins.int)`), a function
|
|
660
|
+
returning a function, and a generic function;
|
|
661
|
+
- a named tuple or typed dict, and its constructor;
|
|
662
|
+
- `Union` and `Optional` beside `|`, and a literal whose text holds `<`.
|
|
663
|
+
|
|
664
|
+
Only a literal type the checker inferred is widened, and a declared one is
|
|
665
|
+
kept. `Union`, `Optional` and `Literal` are imported where they are
|
|
666
|
+
written.
|
|
667
|
+
- The type-variable rung recognises the program's own classes. It had
|
|
668
|
+
identified a class by where its import points or where it is defined,
|
|
669
|
+
which never matches the checker's `packaging.version.Version`. So the
|
|
670
|
+
row, and every type variable in it, was lost: rows formed in 13 of 81
|
|
671
|
+
attempts across packaging, rich, mistune and nox.
|
|
672
|
+
|
|
673
|
+
A class is now named by the absolute name the import model gives its
|
|
674
|
+
module. Where the program's imports name no module, the rung uses mypy's
|
|
675
|
+
`__init__` chain instead. Rows now form in 58 of 66 attempts. Along with
|
|
676
|
+
that:
|
|
677
|
+
- a type only a call site can name may stand inside a type variable;
|
|
678
|
+
- `Any` nested in a type is kept;
|
|
679
|
+
- the signature brings its typing imports;
|
|
680
|
+
- a parameter the body never reads is `object`;
|
|
681
|
+
- union members line up by structure;
|
|
682
|
+
- a class passed directly or through a thunk (`lambda: Row`) is
|
|
683
|
+
`type[Row]`, and a returned variable bound to a parameter takes that
|
|
684
|
+
argument's type.
|
|
685
|
+
- Sites in different modules that agree on every type get their common
|
|
686
|
+
signature, naming the host's classes. Before, the ordinary signature wrote
|
|
687
|
+
only builtins and fell back to `Any`, and mistune's block quote and spoiler
|
|
688
|
+
helpers were declined.
|
|
689
|
+
|
|
690
|
+
Together with the ladder and baseline changes above, typed refactorings
|
|
691
|
+
rise as follows, measured on Python 3.12.13 with `--cross-module` from
|
|
692
|
+
`eaa3882` to `336c890`:
|
|
693
|
+
|
|
694
|
+
| Project | Typed refactorings | Project checks per run |
|
|
695
|
+
|---|---|---|
|
|
696
|
+
| packaging | 10 to 20 | 68 to 36 |
|
|
697
|
+
| rich | 18 to 22 | 45 to 30 |
|
|
698
|
+
| mistune | 15 to 19 | 31 to 19 |
|
|
699
|
+
| nox | 5 to 5 | 9 to 7 |
|
|
700
|
+
| idna | 3 to 1 | 5 to 5 |
|
|
701
|
+
|
|
702
|
+
idna's output now passes its CI's `mypy --strict idna`. Every output
|
|
703
|
+
passes its project's own mypy and test suite exactly as the unchanged
|
|
704
|
+
project does, and every module imports as it did.
|
|
705
|
+
- Pyright's language server checks a project as `pyright` itself does. Towel
|
|
706
|
+
sent it a `python.analysis` section without `autoSearchPaths`, which the
|
|
707
|
+
server then leaves off and the command line always sets, so a src
|
|
708
|
+
layout's package was analysed as `src.<pkg>` and consumers outside `src`
|
|
709
|
+
imported the installed copy, which for an editable install is the user's
|
|
710
|
+
own tree. A candidate that removed `edit` from click's exports broke six
|
|
711
|
+
lines of its typing tests and was reported clean, and trio reported 1989
|
|
712
|
+
errors where its own pyright reports 1870. The server's settings are now
|
|
713
|
+
stated in full and match the command line's in every configuration
|
|
714
|
+
tested; on click, trio, jinja2, markupsafe, attrs and structlog the two
|
|
715
|
+
report identical errors. A consumer that imports its package through an
|
|
716
|
+
editable install is judged against the candidate wherever the package
|
|
717
|
+
lives (`python/`, `lib/`, `packages/<name>/src`, or configured
|
|
718
|
+
`extraPaths`), and pyright's command line is told the project it checks,
|
|
719
|
+
so its answers no longer depend on where the temporary directory lives.
|
|
720
|
+
- `s = super; s()` shared by sibling classes was moved into a module
|
|
721
|
+
function, where it raised `RuntimeError: super(): __class__ cell not
|
|
722
|
+
found`; `super()` reached through another name now stays where it is.
|
|
723
|
+
- A frame handle taken earlier in the loop body or branch that holds a
|
|
724
|
+
duplicated block (`frame = sys._getframe()`, read later as
|
|
725
|
+
`frame.f_locals`) no longer lets the block move; the check had stopped
|
|
726
|
+
walking at the block and never saw the handle.
|
|
727
|
+
- A directory run ends when a proposal renders exactly the bytes its files
|
|
728
|
+
already hold. The directory driver had counted such a proposal as applied,
|
|
729
|
+
so the next analysis found it again and, with no iteration bound, the loop
|
|
730
|
+
never stopped; the single-file driver has always had this guard. The
|
|
731
|
+
proposal is now declined and remembered until the project changes.
|
|
732
|
+
- Type probes at adjacent extraction sites retain their lexical scope even when
|
|
733
|
+
they share a source line. Conflicting mypy specialization notes for one probe
|
|
734
|
+
are treated as ambiguous evidence instead of silently retaining the last type.
|
|
735
|
+
- An extraction no longer gives a method a receiver it never needed. A method
|
|
736
|
+
reached through its class is an ordinary call with nothing bound, so
|
|
737
|
+
`Formatter.as_dollars(None, 1.5)` runs for as long as the body reads no
|
|
738
|
+
attribute of `self`, and routing the block it shares through
|
|
739
|
+
`self._extracted_func_0(...)` made that an `AttributeError` while every
|
|
740
|
+
genuine instance kept its answer and no checker said a word. Such a helper,
|
|
741
|
+
and one shared by static methods or by classmethods that never read `cls`,
|
|
742
|
+
is now a module-level function. An intermediate candidate made it a
|
|
743
|
+
`staticmethod` reached as `ClassName._extracted_func_0`, and the class's
|
|
744
|
+
name does not always reach the class: a parameter of that name, `del`,
|
|
745
|
+
`global` rebinding, a mangled `class __C`, a decorator returning something
|
|
746
|
+
else, or a call while the class body still runs each broke it. `__class__`
|
|
747
|
+
would reach it, but mypy does not accept it. Over packaging, click and rich
|
|
748
|
+
nothing is declined; click goes from 11 helpers to 10, one module function
|
|
749
|
+
serving a third class.
|
|
750
|
+
- An import Towel writes names a module the installed project has, because
|
|
751
|
+
its name is taken from the program's own imports instead of computed from
|
|
752
|
+
packaging metadata. Checked against built wheels, the old layout readers
|
|
753
|
+
named 102 files of seven corpus projects wrongly (a `setup.cfg` or
|
|
754
|
+
`setup.py` src layout, never read, became `src.foo.a`), 937 more when a
|
|
755
|
+
project directory shares its package's name (`foo.src.foo.a`), and
|
|
756
|
+
waitress' `src/waitress/task.py` as `src.waitress.task`. Without a project
|
|
757
|
+
mypy configuration the checker named modules from the same root, so it
|
|
758
|
+
accepted those imports, and the adopted output failed with
|
|
759
|
+
`ModuleNotFoundError`. Within a package Towel writes the spelling the
|
|
760
|
+
importing module already uses for its neighbours, or a relative import.
|
|
761
|
+
Across top-level packages it writes an import only where the importing
|
|
762
|
+
package already imports the other one, spelled as it spells it. It enters
|
|
763
|
+
a directory only where its own side already imports from it, so a library
|
|
764
|
+
never borrows from a test package its wheel leaves out. A relative import
|
|
765
|
+
never climbs above the importer's top package. What an import runs, for the
|
|
766
|
+
cycle guard and the import-time checks, is read the same way; the old
|
|
767
|
+
graph had matched filenames case-insensitively (`pygments/Lexer.py`) and
|
|
768
|
+
let eight pygments lexers import others at load time. An import into an
|
|
769
|
+
excluded directory, such as pip's `_vendor`, is treated as unknown rather
|
|
770
|
+
than empty. `rename-helpers` names modules the same way, so it works on src
|
|
771
|
+
layouts. The packaging readers are gone; `--prefer-absolute-imports`,
|
|
772
|
+
`--pep420` and their engine keywords are accepted as deprecated no-ops, and
|
|
773
|
+
`ProjectLayout` is removed. The new reject reason `unproven_import`
|
|
774
|
+
replaces `unknown_layout`.
|
|
775
|
+
- `towel dry TARGET OUT` refactors TARGET inside a private copy of its whole
|
|
776
|
+
project and writes only TARGET to OUT, when the run succeeds. It refactored a
|
|
777
|
+
copy of TARGET alone, so everything read during the run saw a directory with
|
|
778
|
+
no packaging metadata and no rest-of-project: the layout readers were never
|
|
779
|
+
reached (every import came out relative, which hid the defects above), and
|
|
780
|
+
an import cycle or an import-time effect through a module outside TARGET was
|
|
781
|
+
invisible, leaving the adopted package unimportable where the same run in
|
|
782
|
+
place was correct. Out-of-place and in-place runs now make the same
|
|
783
|
+
decisions; staging click, rich and pygments (460 files) takes 0.07 s. A
|
|
784
|
+
project root holding more than 20,000 Python files is refused with a
|
|
785
|
+
message. A failed or interrupted run leaves no OUT behind.
|
|
786
|
+
- The complete mypy check no longer calls a broken consumer clean. The
|
|
787
|
+
consumer scan cached an answer computed with the baseline's packages left
|
|
788
|
+
out and reused it for later sparse requests, so after the first check of
|
|
789
|
+
`towel dry . .` a sibling analysed package went unchecked; it never noticed a
|
|
790
|
+
file an applied refactoring had made into a consumer; it named a PEP 420
|
|
791
|
+
module `lib` where its importers say `nsp.lib`; it read `from app import
|
|
792
|
+
helpers` as an import of `app` alone; and it resolved an `__init__`'s
|
|
793
|
+
relative imports against the package's parent. It now keeps the whole import
|
|
794
|
+
graph, re-reads only files whose stamp changed, and applies exclusions per
|
|
795
|
+
request; at its file limit it fails the check instead of returning a partial
|
|
796
|
+
list. A mypy run that applied anything ends with one complete check from an
|
|
797
|
+
empty cache and a fresh scan, as a pyright run already did (0.6 s on click).
|
|
798
|
+
An error mypy reports without a line is counted, and a crashed worker's
|
|
799
|
+
standard error is quoted.
|
|
800
|
+
- A helper is placed only where it stays a plain member. Never in a
|
|
801
|
+
`Protocol`, where it became a protocol member and turned runtime-checkable
|
|
802
|
+
`isinstance` checks false; never in a class written on one line
|
|
803
|
+
(`class E(Exception): pass`), where it was written at column zero; and only
|
|
804
|
+
in a class, or through a base, whose every decorator is known to keep the
|
|
805
|
+
namespace (`dataclass`, `total_ordering`, `final`, `enum.unique`), since a
|
|
806
|
+
decorator that rebuilds or wraps it dropped or wrapped the helper. A module
|
|
807
|
+
helper is no longer placed after an assignment, decorator or class body that
|
|
808
|
+
could call it at import.
|
|
809
|
+
- A borrower is never made to import a host that requires a module the
|
|
810
|
+
borrower does not: `import tornado` is inert as a statement, but a helper
|
|
811
|
+
hosted in gunicorn's tornado worker made the sync worker unimportable
|
|
812
|
+
wherever tornado was absent. The standard library, declared dependencies,
|
|
813
|
+
and imports inside `try` require nothing. Over rich, click, packaging and
|
|
814
|
+
pygments the output is byte-identical.
|
|
815
|
+
- A call site whose generated thunk would read a local that may be unbound is
|
|
816
|
+
declined: the thunk raised `NameError` where the original raised
|
|
817
|
+
`UnboundLocalError`, and `except UnboundLocalError` stopped matching.
|
|
818
|
+
- Generated helper names avoid every name another source under the project
|
|
819
|
+
root defines as a class member or attribute, so a subclass outside the
|
|
820
|
+
target can no longer override a new helper.
|
|
821
|
+
- An escape in a string literal survives extraction into a file whose
|
|
822
|
+
encoding cannot hold the character it denotes. Rendering spells a constant
|
|
823
|
+
by value, so `"\u20ac"` in a Latin-1 file came back as `€`, which the file
|
|
824
|
+
cannot hold, and the run aborted. The escape is now written back, inside
|
|
825
|
+
the literal only and kept only when the syntax tree is unchanged by it; a
|
|
826
|
+
character no escape can spell is declined instead. A file nested too deeply
|
|
827
|
+
to analyze is skipped with a warning instead of ending directory mode with
|
|
828
|
+
`RecursionError`.
|
|
829
|
+
- A run whose every candidate the type checker could not judge (timeout or
|
|
830
|
+
crash) exits non-zero and says so, instead of printing "No refactorings
|
|
831
|
+
found!"; a run that applied some reports how many went unjudged. The summary
|
|
832
|
+
shows the run's actual termination reason.
|
|
833
|
+
- `rename-helpers` refuses a batch that would leave an import of the old name
|
|
834
|
+
behind, as it did on an out-of-place output whose imports name the package
|
|
835
|
+
rather than the output directory. Rename after adopting.
|
|
836
|
+
- A project whose own mypy configuration excludes files (`exclude`, or `files`
|
|
837
|
+
not naming them) is no longer refused for errors in files its own mypy run
|
|
838
|
+
never checks; an excluded file that checked code imports is still checked.
|
|
839
|
+
- A block passes the caller's names that only a definition inside it reads
|
|
840
|
+
where it stands: defaults and keyword-only defaults, decorators and
|
|
841
|
+
annotations, class bases, keywords and bodies. The helper raised
|
|
842
|
+
`NameError` before its first call. A bare `x: int` no longer counts as
|
|
843
|
+
binding `x`, which raised `UnboundLocalError`.
|
|
844
|
+
- A `match` pattern's class, dotted value and mapping keys are read from the
|
|
845
|
+
caller, and nothing in a pattern becomes a parameter except a class
|
|
846
|
+
pattern's class or the root of a dotted name. `case [1, x]` against
|
|
847
|
+
`case [2, x]` had become a capture that matched every pair, and
|
|
848
|
+
`case Color.RED` a class pattern that raised `TypeError`.
|
|
849
|
+
- A `type` statement's value and a type parameter's bound are closures
|
|
850
|
+
evaluated when first read, and are declined wherever the caller or the
|
|
851
|
+
block rebinds what they read, as lambdas are.
|
|
852
|
+
- A block is declined when a function, lambda, generator or class it creates
|
|
853
|
+
could be observed other than by calling it. Such an object would carry the
|
|
854
|
+
helper's `__qualname__`, and a unified lambda the template's parameter
|
|
855
|
+
names. It may still be called in the block, be the `key=` of `sorted`,
|
|
856
|
+
`min` or `max`, or be the function of a `map` or `filter` consumed there.
|
|
857
|
+
Rich makes 27 helpers instead of 28; packaging, click and pygments are
|
|
858
|
+
unchanged.
|
|
859
|
+
- Constants that compare equal but differ in type (`0`, `0.0` and `False`;
|
|
860
|
+
`1` and `True`) are different constants, extracted as arguments where the
|
|
861
|
+
instantiation check used to decline them. Pygments makes 74 helpers
|
|
862
|
+
instead of 72, and its lexers and formatters produce identical output over
|
|
863
|
+
12,679 observations.
|
|
864
|
+
- An integer literal too wide for decimal conversion no longer ends the run;
|
|
865
|
+
a block holding one of more than 640 digits is declined.
|
|
866
|
+
- A checker the project configures that is not installed refuses the typed
|
|
867
|
+
run before anything is written, naming the checker and its configuration.
|
|
868
|
+
It had been replaced by the other checker, or by none after a one-line
|
|
869
|
+
note, so a pyright-only project was refactored unverified with exit
|
|
870
|
+
status 0.
|
|
871
|
+
- mypy checks with the project's configured plugins, loaded as its own mypy
|
|
872
|
+
loads them; a plugin that cannot load refuses the run, quoting mypy's
|
|
873
|
+
error. Without them Towel accepted a helper the project's own mypy
|
|
874
|
+
rejected.
|
|
875
|
+
- A module that ships its own stub is checked through the stub, as mypy
|
|
876
|
+
checks it, so an importer of a name the implementation gained and the stub
|
|
877
|
+
lacks is refused rather than accepted.
|
|
878
|
+
- An in-place run refactors a private copy of the project and writes the
|
|
879
|
+
result once, as one journaled change, after its final confirmation
|
|
880
|
+
succeeds. A refused confirmation or an interruption leaves the project
|
|
881
|
+
unchanged, and a file edited during the run refuses the write instead of
|
|
882
|
+
losing the edit. Over click the output is byte-identical and 1.4% slower.
|
|
883
|
+
- A pyright configuration is read with pyright's own grammar, so a file
|
|
884
|
+
pyright rejects refuses the run before anything is written, naming the
|
|
885
|
+
file and position; the language server would otherwise have checked with
|
|
886
|
+
default settings. pyright's failures quote its standard error, and its
|
|
887
|
+
command-line probes are written only into Towel's private copy.
|
|
888
|
+
- A generated annotation that uses syntax newer than the project's oldest
|
|
889
|
+
Python (`int | None` before 3.10, `list[int]` before 3.9) is written as a
|
|
890
|
+
string in a module that does not postpone annotations. The oldest Python
|
|
891
|
+
comes from `requires-python`, else mypy's `python_version` or pyright's
|
|
892
|
+
`pythonVersion`. A project declaring `>=3.9` got a helper that raised
|
|
893
|
+
`TypeError` at import under 3.9.
|
|
894
|
+
- `dry` reports what it declined, counted by reason, instead of "No
|
|
895
|
+
refactorings found!" alone; a type-check refusal is no longer reported as
|
|
896
|
+
"could not be rendered"; and the `.towel-helpers.json` sidecar lists only
|
|
897
|
+
helpers present in the output.
|
|
898
|
+
- A cross-module helper no longer reads a builtin that one of its modules may
|
|
899
|
+
hold differently. The helper had read builtins in its host, so `len`
|
|
900
|
+
shadowed only in the borrower was read as the builtin, and a test patching
|
|
901
|
+
`open` into the borrower (`mock.patch("pkg.mod.open", ..., create=True)`,
|
|
902
|
+
the documented idiom) stopped reaching the moved code. The pair is now
|
|
903
|
+
declined when a participating module binds the name, may bind it through a
|
|
904
|
+
star import, rebinds `__builtins__` or writes its own namespace at run
|
|
905
|
+
time, or when the project's code or tests patch the name into one of them
|
|
906
|
+
(`mock.patch`, `patch.object`, `monkeypatch.setattr`, and targets they
|
|
907
|
+
compute). No helper takes a builtin as a parameter. A patch applied from
|
|
908
|
+
outside the project is not seen. A relative import inside an extracted
|
|
909
|
+
block stays within its own package.
|
|
910
|
+
- No helper takes a builtin as a parameter within one module either. Where
|
|
911
|
+
one function binds `len` (`second(rows, name, len=...)`) and its duplicate
|
|
912
|
+
reads the builtin, the helper had taken `len`, and the first function
|
|
913
|
+
passed it the builtin; a block reading the builtin had also joined a
|
|
914
|
+
helper whose other sites passed their own `len`, and `__import__` or a
|
|
915
|
+
tuple of builtins could be passed as a value. Each is declined
|
|
916
|
+
(`builtin_argument`); where both functions bind the name, each still
|
|
917
|
+
passes its own.
|
|
918
|
+
- One predicate now decides what importing a module runs, for choosing a
|
|
919
|
+
host and for placing a helper. Decorators, defaults, annotations,
|
|
920
|
+
metaclasses and `__init_subclass__` count as code; a `TYPE_CHECKING` block
|
|
921
|
+
and a short list of callables verified at run time do not. A registration
|
|
922
|
+
decorator in the host had run when the borrower was imported.
|
|
923
|
+
- A helper is hosted only in a module that ships with the module importing
|
|
924
|
+
it, never in `tests/` or `examples/` for the package to import. A module
|
|
925
|
+
with a stub (`.pyi`, `typings/`, `<pkg>-stubs`) never hosts a cross-module
|
|
926
|
+
helper, and a module that can run as a script (`__main__.py`, a main guard
|
|
927
|
+
in any form, a `#!` line) stays runnable by its path. Dependencies
|
|
928
|
+
declared by Poetry or in setup.cfg `install_requires` count as declared.
|
|
929
|
+
- One existing function is never rewritten to call another. When duplicates
|
|
930
|
+
are whole function bodies, every site calls one new helper, so patching
|
|
931
|
+
`mod.f1` in a test no longer changes `f2`. `reuse_existing_functions` no
|
|
932
|
+
longer has any effect.
|
|
933
|
+
- A class keeps a method helper only when its receivers are unannotated or
|
|
934
|
+
annotated as the class, `Self` or a type variable bound to it, and its
|
|
935
|
+
metaclass and every `__init_subclass__` it runs are known to leave an
|
|
936
|
+
added function alone. Otherwise the helper is a module-level function
|
|
937
|
+
taking the receiver.
|
|
938
|
+
- A helper goes into a class only when every duplicate it replaces is a
|
|
939
|
+
method of that class; blocks shared by different classes become
|
|
940
|
+
module-level functions taking the receiver. Towel had hoisted method
|
|
941
|
+
helpers into ancestors that held none of the code, among them click's
|
|
942
|
+
`UsageError`, rich's `JupyterMixin`, packaging's `BaseSpecifier` and
|
|
943
|
+
pygments' `Formatter` and `Lexer`. These are public bases that other code
|
|
944
|
+
subclasses, where any subclass could take the helper's place
|
|
945
|
+
(`docs/DECISIONS.md` gives the count measured when the rule was chosen).
|
|
946
|
+
Whether such a function belongs in a class is left to whoever reviews the
|
|
947
|
+
output (see `docs/DECISIONS.md`). pygments makes 71 refactorings instead
|
|
948
|
+
of 77 and rich 30 instead of 33; click and packaging are unchanged, and
|
|
949
|
+
all four behave identically on smoke programs.
|
|
950
|
+
- A method helper is class-private (`__extracted_func_N`, stored as
|
|
951
|
+
`_Cls__extracted_func_N`), so no subclass in or outside the project can
|
|
952
|
+
override it; a class that answers unknown attribute names through
|
|
953
|
+
`__getattr__`, as tinydb's `Query` does, keeps answering the helper's old
|
|
954
|
+
name. A class whose hierarchy defines `__getattribute__` gets a
|
|
955
|
+
module-level helper instead, since it intercepts every lookup.
|
|
956
|
+
- `rename-helpers` renames a class-private helper by its class
|
|
957
|
+
(`path.py:Class.__extracted_func_N`) to another class-private name; a
|
|
958
|
+
non-private new name, or the stored name spelled elsewhere, refuses the
|
|
959
|
+
batch. `reuse_existing_functions` is still accepted, as a documented
|
|
960
|
+
no-op.
|
|
961
|
+
- A project that configures no mypy is checked as its own `mypy` would check
|
|
962
|
+
the same files, with mypy's defaults. Towel had forced
|
|
963
|
+
`check_untyped_defs`, `ignore_missing_imports` and `explicit_package_bases`
|
|
964
|
+
on it, and each changed the verdict: dacite, clean under `mypy .`, was
|
|
965
|
+
refused for 53 errors inside unannotated tests, and a root-relative
|
|
966
|
+
`src.foo.a` import was accepted. Probes that infer a helper's types still
|
|
967
|
+
check unannotated bodies, in a cache of their own. Such a project is now
|
|
968
|
+
refused a typed run wherever its own `mypy` fails; configure mypy or pass
|
|
969
|
+
`--no-types`.
|
|
970
|
+
- A check resolves a module's imports where the project's own mypy does. The
|
|
971
|
+
project root had been searched first, so a test's `import helpers` was
|
|
972
|
+
checked against a root `helpers.py` instead of the one beside it.
|
|
973
|
+
- A literal that a tool reads without running the program stays where the
|
|
974
|
+
tool reads it. python-statemachine's `_("There should be exactly one
|
|
975
|
+
initial state: {!r}")` had become `_(__param_1)`, and pybabel extracted
|
|
976
|
+
neither message; click had lost four of its 71 the same way. The message
|
|
977
|
+
and context arguments of Babel's, Django's and Flask-Babel's markers, and
|
|
978
|
+
of keywords a project configures, are never parameters; nor are the names,
|
|
979
|
+
fields and types of the functional typing forms, `Literal[...]`, or the
|
|
980
|
+
type argument of typing's `cast` and `assert_type`, where no annotation
|
|
981
|
+
makes a parameter acceptable to mypy or pyright. A typing form is
|
|
982
|
+
recognized by what its callee is bound to, however it is spelled
|
|
983
|
+
(`from typing import TypeVar as TV`, `typing.cast`, `typing_extensions`,
|
|
984
|
+
a project module that re-exports it), so a project's own `cast`, such as
|
|
985
|
+
sqlglot's `exp.cast(column, to)` or SQLAlchemy's, stays an ordinary call.
|
|
986
|
+
Only click changes over the four reference packages: 10 refactorings
|
|
987
|
+
instead of 11, with every message still extracted.
|
|
988
|
+
- A helper that computes nothing is no longer extracted, whatever
|
|
989
|
+
`skip_trivial_helpers` says: two unrelated `return` statements had become
|
|
990
|
+
`def _extracted_func_24(__param_0, self): return __param_0`, which rich's
|
|
991
|
+
`Tag.markup` and `MofNCompleteColumn.render` called across two modules;
|
|
992
|
+
packaging and pygments had one each, and those are exactly what is gone.
|
|
993
|
+
- The fixed point ends for a stated reason rather than an incidental one. On
|
|
994
|
+
sqlglot it once extracted 597 helpers, each forwarding to the previous one
|
|
995
|
+
with its tuple permuted, until ordinary SQL raised `RecursionError`. A
|
|
996
|
+
helper that only calls generated helpers and repacks their results is now
|
|
997
|
+
declined in every configuration, forwarding is recognized whatever order
|
|
998
|
+
results come back in, and a lambda handed on whole is passed through
|
|
999
|
+
rather than wrapped again. With the escape guard switched off, sqlglot's
|
|
1000
|
+
DuckDB generator went from no fixed point in 900 s to one in 18 s.
|
|
1001
|
+
- A base-class name is resolved as the binding in effect where the class
|
|
1002
|
+
statement runs, not by finding a class of that qualname anywhere in the
|
|
1003
|
+
file. Python binds globals as a module executes, so `Base = object`
|
|
1004
|
+
written between two subclasses gives them different bases. The checks on
|
|
1005
|
+
a host class's bases (its metaclass, `__init_subclass__` and
|
|
1006
|
+
`__getattribute__`) therefore read the base the class really has. A
|
|
1007
|
+
binding that cannot be established there counts as unknown, and the
|
|
1008
|
+
helper is a module-level function.
|
|
1009
|
+
- A copied annotation is no longer evaluated a second time. An annotation is an
|
|
1010
|
+
expression, and the copy runs at the helper's own `def`:
|
|
1011
|
+
`Annotated[int, mark('a')]` called `mark` again at import, and an annotation
|
|
1012
|
+
the source had quoted was unquoted into one that called it for the first
|
|
1013
|
+
time. An annotation is written bare only when evaluating it runs none of the
|
|
1014
|
+
program's own code; anything holding a call, a lambda, a comprehension, a
|
|
1015
|
+
conditional or an f-string is written as a string, which a checker reads
|
|
1016
|
+
identically and the interpreter never evaluates.
|
|
1017
|
+
- A complete project check covers the modules that import the change. mypy
|
|
1018
|
+
follows imports out of the files it is given and reaches no caller, so a
|
|
1019
|
+
subclass in another package, unchanged and never imported by the package it
|
|
1020
|
+
extends, was broken by a helper whose name it already used while the check
|
|
1021
|
+
reported clean. Those modules are found by one `ast` pass per project. The
|
|
1022
|
+
project root is not walked in its place: repositories hold files no checker
|
|
1023
|
+
can build, and one of them fails the build and refuses the project.
|
|
1024
|
+
- A rejected candidate no longer answers for the project that follows it.
|
|
1025
|
+
Its text was checked, so mypy cached each of its modules against the real
|
|
1026
|
+
file's mtime and size, which mypy trusts without hashing. Those paths were
|
|
1027
|
+
given text again only when the next request named them, and the next request
|
|
1028
|
+
is sparse, so a provider left speculatively returning `str` went on answering
|
|
1029
|
+
`str` while its file returned `int`. Such a path is now given what its file
|
|
1030
|
+
holds, named or not. On Sphinx this costs 3.5% and changes no output.
|
|
1031
|
+
- Two unrelated files that infer one module name are no longer collapsed into
|
|
1032
|
+
one, which dropped the second file's errors and reported the project clean.
|
|
1033
|
+
Only a stub and the implementation beside it collapse; anything else keeps
|
|
1034
|
+
mypy's duplicate-module refusal.
|
|
1035
|
+
- A project whose checker cannot start at all -- a config naming a Python
|
|
1036
|
+
version mypy has dropped -- is told how to proceed, as a project with
|
|
1037
|
+
pre-existing errors already was.
|
|
1038
|
+
- The mypy worker's cost per request no longer grows over a run. Successive
|
|
1039
|
+
`build.build` calls in one process keep every rechecked module's tree alive
|
|
1040
|
+
(mypy 1.19; about 250,000 objects per build of `sphinx.application`), and
|
|
1041
|
+
each build opens with a full collection over all of it, so requests slowed
|
|
1042
|
+
linearly and a run quadratically: 14 s per request and a 14 GB worker after
|
|
1043
|
+
80 minutes. Each build now runs in a forked child that exits when it has
|
|
1044
|
+
answered. The same 117 recorded requests take 86.5 s instead of 855 s, at a
|
|
1045
|
+
flat 0.75 s and 60 MB, with identical diagnostics.
|
|
1046
|
+
- A complete project check no longer makes mypy recheck every analyzed module.
|
|
1047
|
+
mypy consults its cache only for a module it reads itself, and every module
|
|
1048
|
+
was supplied as text. Text a file already holds is now withheld. A path that
|
|
1049
|
+
has once been supplied with differing text stays supplied for the life of
|
|
1050
|
+
the cache, because mypy records the file's mtime and size beside the text's
|
|
1051
|
+
hash and would otherwise answer for the file with its verdict on the text.
|
|
1052
|
+
- The Pyright language server's silence is taken as a verdict only after it
|
|
1053
|
+
has answered for the change. A server slow to begin is as silent as one that
|
|
1054
|
+
has finished, and a candidate that broke three consumers read as clean when
|
|
1055
|
+
the server was delayed past the 0.35 s quiet period. Each check now carries a
|
|
1056
|
+
marker the server must publish first.
|
|
1057
|
+
- A candidate is checked against the project as it now stands, which is
|
|
1058
|
+
decided by the content of each checker input rather than by its size and
|
|
1059
|
+
timestamp, so an edit by something other than Towel cannot leave a verdict
|
|
1060
|
+
standing against a project the copy no longer matches. The language
|
|
1061
|
+
server's private copy restored every file a candidate did not supply to the
|
|
1062
|
+
bytes it first saw, so in-place, single-file and library runs lost every
|
|
1063
|
+
refactoring already applied: a valid follow-up was rejected and a breaking
|
|
1064
|
+
candidate accepted. The copy now follows the project and tells the server
|
|
1065
|
+
exactly which files were created, changed or deleted.
|
|
1066
|
+
- A supplied-text record cut short by an interrupted append is repaired instead
|
|
1067
|
+
of failing every later request, and stopping the mypy worker stops the build
|
|
1068
|
+
it was waiting for.
|
|
1069
|
+
|
|
1070
|
+
- An extraction never separates a narrowing test from code that depends on it.
|
|
1071
|
+
A checker narrows a variable along a control-flow region, so moving the test
|
|
1072
|
+
into a helper while an expression that needs it stays at the call site left
|
|
1073
|
+
each half well typed and the pair not. Declined where the proposal is built,
|
|
1074
|
+
before any checker is asked, so the same extractions happen whether or not
|
|
1075
|
+
type checking is on.
|
|
1076
|
+
- A method's receiver is no longer annotated from its call sites. It was the
|
|
1077
|
+
union of the classes that happened to call it, which a checker rejects,
|
|
1078
|
+
since an explicit receiver must be a supertype of its own class, and which
|
|
1079
|
+
understated the method's domain in any case: a helper on a base class is
|
|
1080
|
+
inherited by every subclass. One such annotation was enough to send a whole
|
|
1081
|
+
helper down to the all-`Any` rung, so signatures that had lost every type
|
|
1082
|
+
recover them.
|
|
1083
|
+
- A class passed to a helper is typed `type[C]`. A checker shows a reference
|
|
1084
|
+
to a class as its constructor's signature, which reads as something merely
|
|
1085
|
+
callable and is rejected where a type is wanted, by `isinstance` among
|
|
1086
|
+
others.
|
|
1087
|
+
- An inferred annotation that names a class the module cannot reach imports it
|
|
1088
|
+
under `TYPE_CHECKING`. An ordinary import would often close a cycle, the
|
|
1089
|
+
extraction having just made the other module import this one, and a name
|
|
1090
|
+
wanted only by an annotation need not exist at run time.
|
|
1091
|
+
- An imported helper is named after the project it belongs to, not after the
|
|
1092
|
+
directory a run is staging its output in. A module named after a scratch
|
|
1093
|
+
path cannot be resolved by the checker and breaks as soon as the reviewed
|
|
1094
|
+
output is adopted into the place it was written for.
|
|
1095
|
+
|
|
1096
|
+
### Changed
|
|
1097
|
+
- The default suite now runs the round-3 semantic auditor's generator of
|
|
1098
|
+
near-duplicate code, seeded: 406 cases in about 45 s. The hostile
|
|
1099
|
+
batteries hold every case that audit reported as a P1, each pinned in the
|
|
1100
|
+
state its fix leaves it, and a sample of the cases it found sound.
|
|
1101
|
+
`just fuzz` runs the generator over thousands of fresh seeds as a
|
|
1102
|
+
release-candidate step, and writes each failure as a hostile fixture
|
|
1103
|
+
ready to commit.
|
|
1104
|
+
- A coverage configuration that coverage.py could not read is reported once
|
|
1105
|
+
per project root, and coverage.py's defaults are used in its place.
|
|
1106
|
+
- A pair whose tool directives cannot move soundly is declined:
|
|
1107
|
+
- directives that differ between its sites (`directives_differ`): the
|
|
1108
|
+
helper has one line where the sites had several, so an ignore only one
|
|
1109
|
+
site needed would either silence the other or be lost;
|
|
1110
|
+
- any tool directive that reaches code, other than a name or a literal,
|
|
1111
|
+
which would become a call argument (`directive_on_argument`). Checker,
|
|
1112
|
+
linter, coverage, bandit and formatter directives all count. The
|
|
1113
|
+
argument would sit outside the directive's reach. jinja2's `as_const`
|
|
1114
|
+
extraction had put two `attr-defined` errors at its call sites this way.
|
|
1115
|
+
No directive is copied onto a call;
|
|
1116
|
+
- a region or file directive that would reach past the moved code
|
|
1117
|
+
(`directive_outlives_block`);
|
|
1118
|
+
- a coverage pragma or pylint `disable` around every site's block that
|
|
1119
|
+
would not reach the helper (`directive_around_block`). A module helper
|
|
1120
|
+
for pygments' `__main__`-only functions took `_lua_builtins.py` from
|
|
1121
|
+
100% to 40% line coverage;
|
|
1122
|
+
- a block whose first statement coverage excludes, since the call that
|
|
1123
|
+
replaces it would be measured (`excluded_block_start`).
|
|
1124
|
+
|
|
1125
|
+
Explanatory comments from every site are kept, the first site's first.
|
|
1126
|
+
- Helpers are shared across modules only with `--cross-module`
|
|
1127
|
+
(`cross_module_helpers=True` for library use), because a user
|
|
1128
|
+
deduplicating a package may not expect Towel to add imports between its
|
|
1129
|
+
modules. By default duplicates pair only within a module, the pair budget
|
|
1130
|
+
is spent only on pairs a run can form, and Towel writes no import between
|
|
1131
|
+
the project's modules that runs; an annotation that needs another module's
|
|
1132
|
+
type still gets a type-only import under `if TYPE_CHECKING:`. With
|
|
1133
|
+
`--cross-module`, `dry` and `preview` name the import problems they meet,
|
|
1134
|
+
with the `--exclude` remedy, and refuse a run when one concerns the package
|
|
1135
|
+
being refactored, such as a stale `build/lib` copy of it; `preview` accepts
|
|
1136
|
+
`--exclude`. Over click, rich, packaging and pygments, the default drops
|
|
1137
|
+
exactly the cross-module helpers (0, 3, 5 and 13 of them).
|
|
1138
|
+
- Code that calls zero-argument `super()` is extracted into a method helper
|
|
1139
|
+
of the class that holds both duplicates, where it resolves as it did,
|
|
1140
|
+
diamonds and subclass overrides included: the helper has the class's own
|
|
1141
|
+
`__class__` cell and the method's receiver. Where no such helper can be
|
|
1142
|
+
written the pair is declined, never given a module function
|
|
1143
|
+
(`needs_class_body`, `super_in_call`). Output over click, rich, packaging
|
|
1144
|
+
and pygments is unchanged.
|
|
1145
|
+
- A long run says what it is doing. Verifying a proposal the project rejects
|
|
1146
|
+
advances no counter, so the progress bar could stand still for minutes,
|
|
1147
|
+
which is indistinguishable from a hung run; the display is now redrawn while
|
|
1148
|
+
a proposal is being weighed and names the proposal it is weighing.
|
|
1149
|
+
- `towel dry` reports what a typed run involves before asking to proceed: how
|
|
1150
|
+
many files a candidate is verified against, how many third-party packages
|
|
1151
|
+
their import graph pulls in, and that the number of checks depends on how
|
|
1152
|
+
many proposals the project rejects. It states no duration, that being
|
|
1153
|
+
governed by the rejections and so unknown in advance.
|
|
1154
|
+
- Pyright is consulted through one long-lived language server per project and
|
|
1155
|
+
a persistent private copy, instead of a fresh `pyright --outputjson` and a
|
|
1156
|
+
fresh copy per check. The command line remains the fallback and reaches the
|
|
1157
|
+
same verdicts.
|
|
1158
|
+
- Every configured checker must accept a candidate, so the first rejection is
|
|
1159
|
+
the verdict and the remaining checkers are not asked. Verdicts are remembered
|
|
1160
|
+
per candidate and project state.
|
|
1161
|
+
- An unannotated helper is checked only when every error of the all-`Any`
|
|
1162
|
+
rejection lies inside the helper's own definition. To a caller the two are
|
|
1163
|
+
the same function, so an error anywhere else survives the change.
|
|
1164
|
+
- A proposal the project declines is remembered for the whole run instead of
|
|
1165
|
+
until the next analysis. When nothing more applies and something was
|
|
1166
|
+
declined that the project has changed under since, the run holds a
|
|
1167
|
+
rehearing: the memory is cleared and the whole project analysed once more,
|
|
1168
|
+
so every declined proposal is heard against the project as it now stands.
|
|
1169
|
+
Only a rehearing that applies nothing ends the run, which is a stronger
|
|
1170
|
+
guarantee than before, when the terminating analysis was restricted to the
|
|
1171
|
+
files that had changed. Of the 382 extractions an uncapped Sphinx run
|
|
1172
|
+
considers, 24 had every annotation variant refused and not one of those was
|
|
1173
|
+
ever accepted at a later analysis.
|
|
1174
|
+
- A typed run that was checked through a language server is confirmed once
|
|
1175
|
+
more at the end by a pyright started from nothing, sharing none of the
|
|
1176
|
+
session's state. A session that fails part way through a run still earns
|
|
1177
|
+
that confirmation, being exactly when the verdicts already given are worth a
|
|
1178
|
+
second opinion, and a confirmation that cannot run refuses the run rather
|
|
1179
|
+
than reporting success. A language server answers when it has gone quiet, and
|
|
1180
|
+
while a marker it must publish first keeps silence from being read as a
|
|
1181
|
+
verdict before it has begun, one cold check over the finished project turns
|
|
1182
|
+
any residue of that kind from a silent wrong answer into a loud one. About
|
|
1183
|
+
ten seconds on Sphinx, and no flag: a guarantee that holds only when asked
|
|
1184
|
+
for is not one.
|
|
1185
|
+
|
|
1186
|
+
### Preserving type relationships
|
|
1187
|
+
|
|
1188
|
+
An extracted helper used to lose the relationships among its types. Where two
|
|
1189
|
+
call sites passed `int` and `str`, the shared parameter became `int | str` and
|
|
1190
|
+
so did the return, which says nothing about the two travelling together: the
|
|
1191
|
+
helper could be read as taking an `int` and returning a `str`. Towel now
|
|
1192
|
+
anti-unifies the types alongside the code, so the helper's signature carries
|
|
1193
|
+
the correlation the call sites had.
|
|
1194
|
+
|
|
1195
|
+
### Added
|
|
1196
|
+
- A library API for errors and reports:
|
|
1197
|
+
- exceptions in `towel.unification.exceptions`, based on `TowelError`:
|
|
1198
|
+
`CheckerUnavailableError`, `UntypeableExtraction` (with `.reason`, an
|
|
1199
|
+
`Untypeable`, and `.detail`), `UnverifiableChangeError` and
|
|
1200
|
+
`UncheckedCodeError`, `AmbiguousImportsError` and
|
|
1201
|
+
`ProjectScanLimitError`; `UnsupportedLayoutError` is kept, though no
|
|
1202
|
+
longer raised;
|
|
1203
|
+
- `engine.run_report`, a frozen `RunReport` whose `declined_pairs` and
|
|
1204
|
+
`declined_proposals` count declines by reason; `engine.declined_pairs`
|
|
1205
|
+
for the latest analysis; and `engine.checker_failures`, how many
|
|
1206
|
+
proposals the last run dropped because the checker could not run.
|
|
1207
|
+
- `--parameterize-builtins` (`parameterize_builtins=True`), off by default:
|
|
1208
|
+
where a builtin that duplicated code reads may differ between its sites (a
|
|
1209
|
+
function's own `len` against the builtin, or with `--cross-module` a module
|
|
1210
|
+
that may hold the name), each site passes its own binding to the helper as
|
|
1211
|
+
a parameter instead of the pair being declined. A builtin every site reads
|
|
1212
|
+
alike is still read directly. In typed code the parameter keeps the
|
|
1213
|
+
checker's signature where it can be written, else `type[C]` for a class
|
|
1214
|
+
(`type[str]`) or `Callable[..., R]` for a callable (`Callable[..., int]`
|
|
1215
|
+
for `len`).
|
|
1216
|
+
- Extracted module-level helpers can preserve relationships among argument and
|
|
1217
|
+
return types through type anti-unification. Nested containers, multiple type
|
|
1218
|
+
parameters, and supported existing generic binders receive fresh helper type
|
|
1219
|
+
parameters. Concrete disagreements can use constrained `TypeVar` declarations
|
|
1220
|
+
when an unrestricted generic body does not type-check.
|
|
1221
|
+
- Generic signatures, their imports, and their declarations are checked with
|
|
1222
|
+
the complete prospective project and committed as one transaction. Failed
|
|
1223
|
+
attempts leave no declarations behind. Generated syntax remains compatible
|
|
1224
|
+
with Python 3.11; existing PEP 695 input requires Python 3.12 or newer.
|
|
1225
|
+
- Generic instance and class helper methods preserve argument/result
|
|
1226
|
+
relationships and retain the host's type parameters. A module-level helper
|
|
1227
|
+
shared by methods that never read their receiver infers fresh parameters
|
|
1228
|
+
from its explicit arguments. Fresh declarations precede the host class at
|
|
1229
|
+
module scope, preserving descriptor behavior and declaration rollback.
|
|
1230
|
+
|
|
1231
|
+
### Fixed (release harness)
|
|
1232
|
+
- The corpus runs Towel the way a user runs it: as `towel` inside each
|
|
1233
|
+
project's own environment. That environment holds the project installed
|
|
1234
|
+
editable from the tree under test, Towel's `format` and `types` extras,
|
|
1235
|
+
and one candidate wheel, checked against `--towel-src` in every
|
|
1236
|
+
environment. mypy, pyright, Black, ruff and isort are taken at the
|
|
1237
|
+
versions the project pins: its lock file first, then the `rev` of the
|
|
1238
|
+
tool's `.pre-commit-config.yaml` hook, then an exact pin in a requirements
|
|
1239
|
+
file (httpx's `requirements.txt` has `mypy==1.17.1`). The environment also holds whatever the project declares its
|
|
1240
|
+
own type check needs: typing-named dependency groups and extras, the
|
|
1241
|
+
dependencies of tox environments and nox sessions that run a checker, its
|
|
1242
|
+
pre-commit mypy and pyright hooks' `additional_dependencies`, and
|
|
1243
|
+
requirements files named for typing. That install only adds; nothing
|
|
1244
|
+
already there may change. Towel had run in the harness's own interpreter,
|
|
1245
|
+
which held Towel's tools and none of the project's dependencies. mypy
|
|
1246
|
+
therefore reported a project's third-party imports as errors: packaging's
|
|
1247
|
+
1158 errors are 14 in its own environment and none once its typing
|
|
1248
|
+
dependencies are installed, and its typed run now completes. The import
|
|
1249
|
+
model found the tools' own copies of click and packaging, and called those
|
|
1250
|
+
projects' names ambiguous. Each result records the interpreter, the
|
|
1251
|
+
candidate, every tool's version and what chose it, and what was installed
|
|
1252
|
+
for typing.
|
|
1253
|
+
- The project's editable install follows the tree each test run exercises:
|
|
1254
|
+
the clone for the baseline, the refactored copy for Towel and the run
|
|
1255
|
+
after it, and the original package for a retest of the original. A test
|
|
1256
|
+
that imports the installed copy therefore tests the same code as the rest
|
|
1257
|
+
of its run. The run after refactoring had reached the unrefactored clone
|
|
1258
|
+
that way, so a regression there could pass as flaky.
|
|
1259
|
+
- Every corpus refactor passes `--cross-module`. A manifest entry turns it
|
|
1260
|
+
off only with a `cross_module_reason`, and the summary names each such
|
|
1261
|
+
project.
|
|
1262
|
+
- The sdist no longer ships a built wheel: `recursive-include scripts *`
|
|
1263
|
+
picked up the corpus image's copy whenever one had been built there.
|
|
1264
|
+
- Two runs that failed *different* tests can no longer both count as a PASS. A
|
|
1265
|
+
failure line is `FAILED <node id> - <message>`, and the id was taken by
|
|
1266
|
+
splitting at the first `" - "` -- which a parametrized id may contain, so
|
|
1267
|
+
`test_case[same - before]` and `test_case[same - after]` both truncated to
|
|
1268
|
+
`test_case[same`. The harness compares those sets to award PASS, so a failure
|
|
1269
|
+
Towel introduced could hide behind a pre-existing failure in the same
|
|
1270
|
+
parametrized test. Only a separator outside brackets ends the id now.
|
|
1271
|
+
- A phase that times out takes its descendants with it. Killing the immediate
|
|
1272
|
+
process left a suite's own workers, a server it started or a build it spawned
|
|
1273
|
+
running, writing into the scratch tree that the project's later phases read.
|
|
1274
|
+
Each phase leads a process group ended with it, and the worker watchdog ends
|
|
1275
|
+
those groups too.
|
|
1276
|
+
- The corpus runs the default type policy and reruns without types only the
|
|
1277
|
+
projects Towel declines to verify, naming them and their reason in the
|
|
1278
|
+
report; their verdicts are evidence about the untyped path alone. The refusal
|
|
1279
|
+
is first held to its wording -- the count, a diagnostic naming its file, and
|
|
1280
|
+
the way forward -- and one that fails that is a verdict of its own rather
|
|
1281
|
+
than a project quietly skipped.
|
|
1282
|
+
- The ecosystem check gives each corpus project its own output directory.
|
|
1283
|
+
Towel writes its recovery journal to the common parent of the files a
|
|
1284
|
+
transaction changes and refuses to start beneath a pending journal that may
|
|
1285
|
+
cover its targets. A one-module project's single output file put that journal
|
|
1286
|
+
directly in the shared work directory, an ancestor of every other project, so
|
|
1287
|
+
concurrent projects refused each other at `--workers 4`. The runtime is
|
|
1288
|
+
unchanged; its refusal was correct.
|
|
1289
|
+
|
|
1290
|
+
### Notes
|
|
1291
|
+
- Towel keeps a precise ordinary signature when one verifies against the whole
|
|
1292
|
+
project, and reaches for a generic signature only when that ordinary
|
|
1293
|
+
signature does not. A generic candidate is therefore an alternative to losing
|
|
1294
|
+
the annotation, not a replacement for an exact one.
|
|
1295
|
+
- Declined by design, with no weakening of the annotations that do ship: a
|
|
1296
|
+
helper hosted inside a function, variadic type parameters, a source method
|
|
1297
|
+
whose `self` or `cls` carries an explicit annotation, and evidence that
|
|
1298
|
+
resolves to `Any` or an unknown name.
|
|
1299
|
+
- A fresh parameter whose bound mentions another helper parameter (`U` bounded
|
|
1300
|
+
by `list[T]`) is declined; an ordinary module `TypeVar` bound cannot depend on
|
|
1301
|
+
another function's binder. Where a constrained generic function makes mypy
|
|
1302
|
+
report several specializations for one probe, the evidence is treated as
|
|
1303
|
+
ambiguous rather than resolved arbitrarily. Both are recorded in
|
|
1304
|
+
[the type-parameter design](docs/proposals/type-parameters.md).
|
|
1305
|
+
|
|
1306
|
+
## [1.732.post1] - 2026-09-19
|
|
1307
|
+
|
|
1308
|
+
### Fixed
|
|
1309
|
+
- README documentation links use absolute URLs pinned to the release tag, so
|
|
1310
|
+
they resolve from PyPI as well as GitHub. The Python implementation is
|
|
1311
|
+
unchanged from 1.732.
|
|
1312
|
+
- Regression checks reject repository-relative README links and stale release
|
|
1313
|
+
tags in documentation URLs. The release procedure verifies GitHub source,
|
|
1314
|
+
documentation, and CI before uploading packages to PyPI.
|
|
1315
|
+
|
|
1316
|
+
## [1.732] - 2026-09-19
|
|
1317
|
+
|
|
1318
|
+
Changes since 1.618.
|
|
1319
|
+
|
|
1320
|
+
### Added
|
|
1321
|
+
- A duplicate that is the whole body of a plain module-level function now
|
|
1322
|
+
calls that function instead of extracting a helper. Two identical functions
|
|
1323
|
+
no longer become a helper plus two forwarders: the first-defined one is kept
|
|
1324
|
+
and the other calls it, and a matching block inside a larger function calls
|
|
1325
|
+
the existing function directly. Arguments follow the function's parameter
|
|
1326
|
+
order; names the body reads from its own module (functions, classes,
|
|
1327
|
+
absolute imports) are not passed. Across files the call is imported like a
|
|
1328
|
+
helper and refused when it would close an import cycle; a decorated, async,
|
|
1329
|
+
variadic, shadowed, or rebound function falls back to ordinary extraction.
|
|
1330
|
+
A function whose body is the block followed by a plain `return` of the
|
|
1331
|
+
block's live variables is reused too; the other sites unpack its result in
|
|
1332
|
+
its order. Construct the engine with `reuse_existing_functions=False` to
|
|
1333
|
+
restore the old behavior.
|
|
1334
|
+
- A helper that returns live variables now admits every further same-file
|
|
1335
|
+
occurrence whose call assigns them, so twelve identical functions become
|
|
1336
|
+
one helper with twelve calls on the first pass rather than a pair per
|
|
1337
|
+
pass. Repeated passes used to stack such helpers into a chain of
|
|
1338
|
+
forwarders, each returning the next one's result; a proposal that would
|
|
1339
|
+
reduce a helper an earlier pass inserted to a one-line forwarder is now
|
|
1340
|
+
declined instead (part of `skip_trivial_helpers`).
|
|
1341
|
+
- `towel dry` formats the code it inserts with the formatter the project
|
|
1342
|
+
configures (`pip install "code-towel[format]"`): `ruff format` when
|
|
1343
|
+
`[tool.ruff]`, `ruff.toml` or `.ruff.toml` is present and ruff is
|
|
1344
|
+
installed, otherwise Black with the project's own line length and string
|
|
1345
|
+
quoting. Only the
|
|
1346
|
+
generated helper and the rewritten call statements are formatted, never
|
|
1347
|
+
the surrounding file, and each snippet is checked to have the same syntax
|
|
1348
|
+
tree before and after. Inserted imports are sorted the way the project
|
|
1349
|
+
sorts them, with ruff's `I` rules when selected or isort when configured;
|
|
1350
|
+
a sorter may only reorder or merge the import statements, which is
|
|
1351
|
+
verified. Pass `--no-format` to insert the unformatted rendering; without
|
|
1352
|
+
a formatter a note says so. Library callers pass any `snippet_formatter`
|
|
1353
|
+
and `file_finisher` callables to the engine.
|
|
1354
|
+
- Black's line length for generated code is the limit the project declares
|
|
1355
|
+
anywhere: `[tool.black]`, `[tool.ruff]`, `[tool.pycodestyle]`, or a
|
|
1356
|
+
`[flake8]`/`[pycodestyle]` section in `setup.cfg`, `tox.ini` or `.flake8`
|
|
1357
|
+
(pycodestyle's own dog-food test failed on an 86-character generated call
|
|
1358
|
+
under Black's default of 88 against its declared 79).
|
|
1359
|
+
- Extracted helpers carry the type annotations their call sites declare. A
|
|
1360
|
+
parameter is annotated when every site passes an annotated, never-rebound
|
|
1361
|
+
parameter of its enclosing function, or a literal of one builtin type, and
|
|
1362
|
+
the sites agree; the return is annotated from the sites' declared return
|
|
1363
|
+
type, from annotated locals the helper returns, or as `None` for a helper
|
|
1364
|
+
that returns nothing. Nothing is inferred. Annotations are copied unquoted
|
|
1365
|
+
only where they cannot fail to resolve (builtins, deferred annotations, or
|
|
1366
|
+
names bound by a module-level import) and as strings otherwise; across
|
|
1367
|
+
modules only builtin names are used. Code without annotations stays that
|
|
1368
|
+
way. Construct the engine with `annotate_helpers=False` to disable it.
|
|
1369
|
+
- When a type checker is installed (`pip install "code-towel[types]"`), the
|
|
1370
|
+
argument expressions and return values those copied annotations could not
|
|
1371
|
+
name are typed by it. The checker is the one the project configures: mypy
|
|
1372
|
+
for a project with `[tool.mypy]`, `mypy.ini`, `.mypy.ini` or a `[mypy]`
|
|
1373
|
+
section in `setup.cfg`, pyright for one with `[tool.pyright]` or
|
|
1374
|
+
`pyrightconfig.json`; for a project configuring both,
|
|
1375
|
+
mypy infers while both verify the prospective project for new errors.
|
|
1376
|
+
For inference, Pyright probes a temporary sibling copy of the module;
|
|
1377
|
+
mypy reveals expressions in an in-memory copy of the site's module, at
|
|
1378
|
+
the point where the call will stand, using an owned worker with incremental
|
|
1379
|
+
caches. Verification checks the modified files together with unchanged
|
|
1380
|
+
consumers under the project's configured scope. A revealed type is written only
|
|
1381
|
+
when it is not bare `Any` and every name in it resolves where the helper
|
|
1382
|
+
is defined. `--no-types` leaves helpers unannotated; library callers pass
|
|
1383
|
+
a `type_oracle` to the engine.
|
|
1384
|
+
- Sites that disagree on a parameter's type, or on a revealed return type,
|
|
1385
|
+
join into a union (`int | None`, `int | str`); sites that declare
|
|
1386
|
+
different return types take the meet of the declarations, found through
|
|
1387
|
+
the checker's subtype relation. Once a helper carries any annotation,
|
|
1388
|
+
whatever is still bare becomes `Any`, so the signature is complete (a
|
|
1389
|
+
partial one is an error under mypy's `disallow-incomplete-defs`), and
|
|
1390
|
+
`from typing import Any` is added to the host when it lacks it. Code with
|
|
1391
|
+
no annotations stays bare.
|
|
1392
|
+
- With mypy installed the subtype relation is mypy's own, asked through
|
|
1393
|
+
probe functions appended to an in-memory copy of the host module: unions
|
|
1394
|
+
are normalized by it (`int | bool` is `int`, `float | int` is `float`, a
|
|
1395
|
+
subclass under its base disappears), the declared return types' meet is
|
|
1396
|
+
found through it, and the helper's revealed return type is written only
|
|
1397
|
+
when mypy confirms it is a subtype of every site's declared return type,
|
|
1398
|
+
which is what keeps the sites type-checking. Without mypy Towel copies
|
|
1399
|
+
and does not reason: unions are written unreduced and the meet needs
|
|
1400
|
+
identical declarations, since there is no second implementation of the
|
|
1401
|
+
subtype relation.
|
|
1402
|
+
- Thunk and callee arguments get `Callable` annotations from mypy's callable
|
|
1403
|
+
spelling (`Callable[[], int]`, `Callable[[int, str], bool]`,
|
|
1404
|
+
`Callable[..., T]`), with `from typing import Callable` added as needed.
|
|
1405
|
+
- A module-level helper whose annotations name classes or functions defined
|
|
1406
|
+
in the module is placed after the last such definition, so those names are
|
|
1407
|
+
written bare instead of as quoted forward references; it stays at the top,
|
|
1408
|
+
with quotes, when any statement before that point could run code at
|
|
1409
|
+
import time.
|
|
1410
|
+
- A subscripted annotation is written bare only when it evaluates at
|
|
1411
|
+
definition time: a PEP 585 builtin generic, a name from `typing` or
|
|
1412
|
+
`collections.abc`, or a module that defers annotations. Anything else is a
|
|
1413
|
+
string annotation: `memoryview[int]`, copied from tornado's own signatures,
|
|
1414
|
+
raised `TypeError` at import on an interpreter where `memoryview` is not
|
|
1415
|
+
generic.
|
|
1416
|
+
- With a clean original type-checking baseline, helper extractions and existing
|
|
1417
|
+
function reuse check all modified files together in the prospective project,
|
|
1418
|
+
including unchanged consumers. New errors make generated helper annotations
|
|
1419
|
+
fall back to `Any`, then to none; every variant must pass. Reused functions
|
|
1420
|
+
keep their existing signatures, and incompatible calls decline the reuse.
|
|
1421
|
+
Checker failure or remaining new errors decline the proposal. Cross-file
|
|
1422
|
+
helper imports no longer lose valid annotations because their host was stale.
|
|
1423
|
+
- Before oracle inference or verification, the original complete project is
|
|
1424
|
+
checked. If it already has type errors, Towel aborts and asks the user to
|
|
1425
|
+
fix them or explicitly rerun with `--no-types`. That option preserves
|
|
1426
|
+
existing source annotations and generates unannotated helpers. Checker
|
|
1427
|
+
crashes and timeouts remain distinct failures; checking is never silently disabled.
|
|
1428
|
+
A clean baseline keeps verification enabled for subsequent proposals.
|
|
1429
|
+
- A [proposal for type-parameter inference](docs/proposals/type-parameters.md)
|
|
1430
|
+
records how generic helpers could preserve relationships among arguments
|
|
1431
|
+
and return values. This work is deferred beyond 1.732.
|
|
1432
|
+
- Property-based tests (hypothesis, in the `dev` extra) check that alpha-variant
|
|
1433
|
+
blocks unify with a renaming-only substitution, that definite assignment
|
|
1434
|
+
agrees with a path-enumerating reference, and that every generated helper
|
|
1435
|
+
passes the instantiation round trip; `visitors.py` has direct tests; the
|
|
1436
|
+
cross-file hostile battery pins which packages must transform; ten
|
|
1437
|
+
cross-file integration tests fail instead of skipping when a fixture is
|
|
1438
|
+
missing; six assertion-free tests assert what their names claim.
|
|
1439
|
+
- The suite now drives the forked pair-evaluation pool on a small project and
|
|
1440
|
+
checks it against the serial path, including the fallback when a worker
|
|
1441
|
+
raises or dies; corrupts the recovery journal one guard at a time and
|
|
1442
|
+
round-trips interrupted transactions under Hypothesis; drops a proposal
|
|
1443
|
+
that goes stale mid-run and covers the three progress displays; reaches
|
|
1444
|
+
the return-coverage and free-variable-lifetime rejections by name (and
|
|
1445
|
+
pins that the first block's coverage guard is shadowed by block
|
|
1446
|
+
enumeration); and adds idempotence, byte-convention and observational
|
|
1447
|
+
equivalence properties, with `with` in the flow grammar. The cross-file
|
|
1448
|
+
timing test no longer measures the machine, the watchdog test finishes
|
|
1449
|
+
in a fraction of a second, and both hostile batteries execute fixtures
|
|
1450
|
+
the same isolated way. Three hostile fixtures pin the closure, `dir()`
|
|
1451
|
+
and unbound-global cases below, and a forwarder-chain suite pins that
|
|
1452
|
+
twelve identical functions yield one helper.
|
|
1453
|
+
- CI runs `pip-audit --strict` against the locked dependency set, and
|
|
1454
|
+
Dependabot proposes weekly, grouped minor/patch updates for the workflow
|
|
1455
|
+
actions and the `uv`-managed Python dependencies.
|
|
1456
|
+
- The candidate pairs an analysis evaluates are bounded by a configurable
|
|
1457
|
+
candidate-pair budget (`--max-pairs`, default 20,000,000; past it the
|
|
1458
|
+
largest groups of similar blocks are left out with a warning), and the
|
|
1459
|
+
minimum block length (`--min-lines`) and helper parameter limit
|
|
1460
|
+
(`--max-parameters`) are exposed on the command line.
|
|
1461
|
+
|
|
1462
|
+
### Changed
|
|
1463
|
+
- Boolean options come in `--x/--no-x` pairs with the default on:
|
|
1464
|
+
`--types/--no-types`, `--format/--no-format`, `--interactive/--no-interactive`,
|
|
1465
|
+
and the tri-state `--prefer-absolute-imports/--no-prefer-absolute-imports`
|
|
1466
|
+
and `--pep420/--no-pep420` (unset lets the project decide). `--max-refactorings N`
|
|
1467
|
+
names what `--max-iterations` always did, and `rename-helpers --preview`
|
|
1468
|
+
replaces `--dry-run`, since "dry" already means DRY here.
|
|
1469
|
+
- A helper whose body only binds parameters and literals to names and returns
|
|
1470
|
+
them is not proposed: the call that unpacks the tuple is longer than the
|
|
1471
|
+
assignments it replaces and shares no logic. The trivial-forwarding filter
|
|
1472
|
+
also recognizes `name = call(...)` followed by `return name`, and the tuple
|
|
1473
|
+
form `a, b = call(...)` then `return (a, b)`; without it, once Black
|
|
1474
|
+
wrapped such a body over the three-line minimum, two generated helpers of
|
|
1475
|
+
that shape paired with each other and extracted a third, without end (h2).
|
|
1476
|
+
- In directory mode, a global re-pass after the first re-pairs functions in
|
|
1477
|
+
rewritten files and files with deferred verification refusals. Deferred
|
|
1478
|
+
proposals are retried only after the project changes, since verification can
|
|
1479
|
+
depend on unchanged consumers elsewhere in the project. Other unchanged
|
|
1480
|
+
pairs retain their verdict under the conditions explained in
|
|
1481
|
+
`docs/ARCHITECTURE.md`; `incremental_global_passes=False` restores full
|
|
1482
|
+
re-pairing.
|
|
1483
|
+
- Analysis facts are computed once per function instead of once per candidate
|
|
1484
|
+
block (definite assignment, locally bound names, nested scopes), and the
|
|
1485
|
+
same-file clustering pass applies its constant-time filters before the
|
|
1486
|
+
semantic guards. Together these remove about half of the AST traversal on
|
|
1487
|
+
a 16k-line project with an identical proposal list.
|
|
1488
|
+
- Candidate blocks are bucketed on their whole statement-type sequence,
|
|
1489
|
+
which the unifier requires equal, so far fewer pairs reach the filter; the
|
|
1490
|
+
unifier's bound-variable search caches each node's source text instead of
|
|
1491
|
+
re-rendering it per query; the value-producing check is memoized per
|
|
1492
|
+
block; and five visitor classes that were rebuilt on every call are
|
|
1493
|
+
module-level. On Towel's own source these remove a further 28% of all
|
|
1494
|
+
function calls (about half in total since 1.618) with an identical
|
|
1495
|
+
proposal list.
|
|
1496
|
+
- The engine is assembled from mixins, one module per responsibility
|
|
1497
|
+
(`pair_evaluation`, `placement`, `reuse`, `insertion`,
|
|
1498
|
+
`annotation_wiring`, `materialize`, `clustering`, `parallel`,
|
|
1499
|
+
`fixed_point`), over an `EngineState` that declares the state and
|
|
1500
|
+
operations each may rely on; `refactor_engine.py` went from 5,600 lines
|
|
1501
|
+
to 1,400 and the 871-line pair decision is eleven typed stages. The
|
|
1502
|
+
outputs are byte-identical on the exactness baselines.
|
|
1503
|
+
- A second pass over the same seams: the per-block analyses are a
|
|
1504
|
+
`BlockAnalysis` mixin of their own and the engine core is about 800 lines;
|
|
1505
|
+
the unifier is split the same way (`UnifierState` under constant consistency,
|
|
1506
|
+
parameterization and literal promotion, with `Substitution` and the
|
|
1507
|
+
binding-context finder in leaf modules); every state stub names the class
|
|
1508
|
+
that implements it. The functions of an analysis are indexed once
|
|
1509
|
+
(`FunctionIndex`: by file, by name, by enclosing range) instead of scanned
|
|
1510
|
+
per pair, and the engine's per-function maps are weak. Materialization,
|
|
1511
|
+
the directory driver's progress reporting and the rename planner are
|
|
1512
|
+
each a set of named steps rather than one long function. Blocks are
|
|
1513
|
+
narrowed by checks rather than casts, and the `dry` change sidecar is
|
|
1514
|
+
validated on read.
|
|
1515
|
+
- A third structural pass: the block signatures, clustering guards, cache
|
|
1516
|
+
keys, pair context and stage records are typed; the remaining long
|
|
1517
|
+
functions (materialization, the drivers, layout discovery, the pairing
|
|
1518
|
+
loop, the call generator, the clustering pass, the rename planner, the
|
|
1519
|
+
compound-node unifier, the thunk walker) are named steps or dispatch
|
|
1520
|
+
tables; every function and closure with no caller is removed; mypy runs
|
|
1521
|
+
with `warn_unreachable`, and flake8 checks `tests/` for pyflakes errors.
|
|
1522
|
+
One `BoundedCache` replaces three LRU implementations and the parse cache
|
|
1523
|
+
is the engine's own; `FunctionNode` is the one name for a function
|
|
1524
|
+
definition node; the defaults live in `towel.unification.defaults`;
|
|
1525
|
+
`towel.source_text` decodes and re-encodes sources.
|
|
1526
|
+
- The engine computes what it can once per statement instead of once per
|
|
1527
|
+
block: block signatures, return checks, the frame-sensitivity and
|
|
1528
|
+
name-binding guards, structural digests, the unifier's per-statement
|
|
1529
|
+
facts and the substitution's keys are memoized per AST node and folded
|
|
1530
|
+
over each block, the bound-variable query is memoized per block and
|
|
1531
|
+
target text, a pair's structural ids are resolved once and passed to its
|
|
1532
|
+
guards, and each class's file path is resolved once per run. On the
|
|
1533
|
+
Towel source of that day (`towel dry src/towel`, one worker, 45 applied)
|
|
1534
|
+
a run took 6.4 s instead of 10.9 s with `--no-types --no-format` and
|
|
1535
|
+
10.4 s instead of 15.0 s with the defaults; the profiled run went from
|
|
1536
|
+
32.7 s and 293 million calls to 20.3 s and 154 million. The current
|
|
1537
|
+
figures, on a smaller source, are in `docs/KNOWN_LIMITATIONS.md`. Every
|
|
1538
|
+
proposal and rendering is byte-identical on the exactness baselines. The
|
|
1539
|
+
structural-id and source-digest caches, and the definite-assignment
|
|
1540
|
+
facts, no longer keep a re-parsed file's old tree alive; peak memory fell
|
|
1541
|
+
from 186 MB to 179 MB (1.11 GB to 1.06 GB with type checking).
|
|
1542
|
+
- A fourth pass, after an audit that found the same concepts written
|
|
1543
|
+
several ways: the local name an import alias binds, the names a
|
|
1544
|
+
statement binds (`bindings_of`, replacing five collectors that
|
|
1545
|
+
disagreed), the names an expression reads, span containment, the
|
|
1546
|
+
method-kind literal, the helper-home and cluster-context records and
|
|
1547
|
+
package-chain ascent each have one implementation; the unifier's
|
|
1548
|
+
dispatch table holds methods rather than their names; blocks are
|
|
1549
|
+
sequences of statements throughout, so the casts that the wider
|
|
1550
|
+
annotation forced are gone; two typing helpers (`all_instances`,
|
|
1551
|
+
`visit_as`) replace most of the rest; each command reads its arguments
|
|
1552
|
+
once into a typed record; the rename command decodes sources the way
|
|
1553
|
+
the refactoring commands do; and comments that restated the next line
|
|
1554
|
+
are removed. The engine's mixins now inherit the mixins they call, so
|
|
1555
|
+
the forty-nine one-caller stubs on `EngineState` are gone and it declares
|
|
1556
|
+
only the shared attributes and the eleven operations the core provides.
|
|
1557
|
+
The import-graph resolution is its own module (`import_graph.py`);
|
|
1558
|
+
parsing yields a `RawModule` and scope analysis a `ParsedModule` whose
|
|
1559
|
+
analyzer and root scope are required rather than Optional; the
|
|
1560
|
+
`verbose` flag threaded through eleven signatures and read once is gone
|
|
1561
|
+
(the logger's level decides); and the `get_` prefix on a handful of
|
|
1562
|
+
functions is dropped (`free_variables`, `used_names`,
|
|
1563
|
+
`enclosing_names`, `affected_lines`, `bound_variables_in_context`).
|
|
1564
|
+
- The test suite is type-checked with the same strict flags as the source
|
|
1565
|
+
(fifteen `type: ignore` comments ignored nothing; the AST accesses behind
|
|
1566
|
+
the rest are `isinstance` assertions now), coverage traces the forked
|
|
1567
|
+
pair workers (`parallel.py` 85% to 95%), the helpers copied across test
|
|
1568
|
+
modules live in `tests/test_helpers.py`, thirty-odd non-emptiness assertions state
|
|
1569
|
+
the value they are about, the goldens expected to equal their inputs are
|
|
1570
|
+
named and checked exactly, the example3 tests run the cross-file path
|
|
1571
|
+
they describe, a symlinked input directory is pinned, and the engine and
|
|
1572
|
+
command-line branches the suite left untested have tests.
|
|
1573
|
+
- The clustering pass scans a file for the sites that can share a helper
|
|
1574
|
+
once per distinct helper template instead of once per pair, and the
|
|
1575
|
+
reuse redirect finds a function whose body starts at a site through an
|
|
1576
|
+
index instead of scanning the file per replacement: 50 identical
|
|
1577
|
+
functions took 17 s and 100 took 134 s at 1.618, and 6.8 s and 36 s at
|
|
1578
|
+
commit a0596f0 (September 18, 2026, one core of a machine shared with
|
|
1579
|
+
other work); measured with `scripts/bench_similar_blocks.py` at commit
|
|
1580
|
+
5ff2458 (September 19, 2026, one core), 50 take 4.0 s and 100 take
|
|
1581
|
+
15.9 s, with identical output. Orphan detection, the instantiation
|
|
1582
|
+
check's normalized block and the class-private-name scan are memoized
|
|
1583
|
+
on structure, so the re-parse after each applied proposal hits too. The
|
|
1584
|
+
analysis session grows to the number of files and the source bytes an
|
|
1585
|
+
analysis covers, so a project above 128 files or 8 MiB of source no
|
|
1586
|
+
longer re-parses everything on every pass (trio's second pass: 144
|
|
1587
|
+
parses and 7.2 s, now none and 5.0 s). Both insertion-position parses
|
|
1588
|
+
go through the engine's parse memo.
|
|
1589
|
+
- The AST visitors are built on three Template Method bases in
|
|
1590
|
+
`visitors.py`: `OwnScopeVisitor` for collectors that read one scope's own
|
|
1591
|
+
code, `DefinitionDepthVisitor` for those that track how deeply a
|
|
1592
|
+
definition sits, and `ScopeVisitor` for the analyses that follow lexical
|
|
1593
|
+
scopes, which now share one visiting order for definitions and
|
|
1594
|
+
comprehensions and override hooks where they deviate on purpose.
|
|
1595
|
+
- `refactor_directory_to_fixed_point` returns a `TerminationReason` literal;
|
|
1596
|
+
`CodeBlockPair` carries every field pairing always gives it (only the
|
|
1597
|
+
class and enclosing-function names are Optional) and answers
|
|
1598
|
+
`is_cross_file`; `RefactoringProposal.replacements` holds `Replacement`
|
|
1599
|
+
values only, the tuple coercion is gone; the unused exception classes are
|
|
1600
|
+
gone and `UnsupportedLayoutError` joins `RefactoringError` under
|
|
1601
|
+
`TowelError` (and is still a `ValueError`); `towel.project_layout` is a
|
|
1602
|
+
top-level module (the old path
|
|
1603
|
+
re-exports it); `is_package_dir` takes only the path, since whether a
|
|
1604
|
+
bare directory is a namespace package is the layout's decision.
|
|
1605
|
+
- Progress modes are a `ProgressMode` literal (`normalize_progress`,
|
|
1606
|
+
`wants_bar`, `DEFAULT_PROGRESS`); `orphaned_variables` returns the set of
|
|
1607
|
+
orphaned names and `bound_names_in_block` the names a block binds; the
|
|
1608
|
+
engine's `type_inferrer` parameter is `type_oracle` and the
|
|
1609
|
+
`TypeInferrer` alias is gone, `TypeOracle` is the name. Module-internal
|
|
1610
|
+
helpers that read as public are private, and the predicates that ask
|
|
1611
|
+
whether a project configures a tool all say `project_configures_*`.
|
|
1612
|
+
- `run_pipeline` takes the engine it drives (`engine=`) and the pipeline no
|
|
1613
|
+
longer imports the engine; the two phase entry points it calls are public
|
|
1614
|
+
(`find_block_pairs`, `process_block_pairs`). `from towel.unification
|
|
1615
|
+
import UnificationRefactorEngine` still works and loads the engine lazily.
|
|
1616
|
+
- `formatter_for_project`, `import_sorter_for_project`, and
|
|
1617
|
+
`type_oracle_for_project` return a `ToolChoice` (`tool`, `note`) instead
|
|
1618
|
+
of a tuple; `TypeOracle.is_subtype` returns `Subtyping` verdicts
|
|
1619
|
+
(`YES`, `NO`, `UNKNOWN`) instead of `Optional[bool]`. Rejection reasons
|
|
1620
|
+
are a `RejectReason` enum, evaluation kinds a `ParameterKind` literal,
|
|
1621
|
+
the rename inventory a `TypedDict`, and the applied-change log is
|
|
1622
|
+
`engine.change_log`, a sequence of `AppliedChange`.
|
|
1623
|
+
- The library prints no diagnostics. Warnings go to the `towel` logger,
|
|
1624
|
+
which reaches stderr even when nothing configures logging, and the
|
|
1625
|
+
rejection, validation, overlap, and type traces go to `towel.rejections`,
|
|
1626
|
+
`towel.validation`, `towel.overlap`, and `towel.types` at DEBUG. The
|
|
1627
|
+
documented environment variables still switch them on; they and
|
|
1628
|
+
`TOWEL_WORKERS` are read once into `Settings` at engine construction
|
|
1629
|
+
(`settings=` overrides them for library callers), and the engine
|
|
1630
|
+
constructor no longer changes logger levels
|
|
1631
|
+
(`Settings.enable_debug_logging` is the library caller's to call).
|
|
1632
|
+
- Helpers other modules imported under leading underscores are public:
|
|
1633
|
+
`find_project_root`, `load_pyproject`, `is_package_dir`,
|
|
1634
|
+
`walk_own_scope`, `body_without_docstring`, `stored_names`, `reindent`,
|
|
1635
|
+
`relative_import_module`. Two implementations of the docstring stripper,
|
|
1636
|
+
the own-scope walker, the `__param_N` minting rule, and the subtype
|
|
1637
|
+
verdict reader became one each, and ten duplications Towel found in its
|
|
1638
|
+
own source are named helpers.
|
|
1639
|
+
- The import-graph tables are an `ImportGraphCache` the engine owns per run,
|
|
1640
|
+
bounded, instead of process-global dictionaries, and a required argument
|
|
1641
|
+
of the cycle and resolution checks; the apply path parses each modified
|
|
1642
|
+
file once per source text and re-reads a file only when its stat changes;
|
|
1643
|
+
the pair loop skips two functions whose blocks share no bucket key before
|
|
1644
|
+
visiting any block.
|
|
1645
|
+
- Two catches that turned bugs into silent defaults (a module-name fallback
|
|
1646
|
+
catching every exception, a call renderer swallowing a `TypeError`) are
|
|
1647
|
+
narrowed or removed; the nine copies of `try/except/pass` around
|
|
1648
|
+
progress-bar calls are one `quietly`; four unjustified `type: ignore`
|
|
1649
|
+
comments and three `pragma: no cover` exclusions are gone.
|
|
1650
|
+
- Pair evaluation keeps one proposal per distinct refactoring (the same
|
|
1651
|
+
helper over the same clustered sites, whichever pair found it first)
|
|
1652
|
+
instead of every pair's copy. A file of sixty near-identical functions
|
|
1653
|
+
under the pair budget peaked at 33.6 GB holding the copies and now peaks
|
|
1654
|
+
at 0.74 GB; the output is the same, since the overlap filter never chose
|
|
1655
|
+
a later copy.
|
|
1656
|
+
- A proposal whose helper, home and call sites repeat an earlier pair's is
|
|
1657
|
+
declined (`duplicate_proposal`) before the reuse redirect, the forwarder
|
|
1658
|
+
filter and annotation run on it; the clustering scan's key no longer
|
|
1659
|
+
carries names the template block never reads, which made it differ per
|
|
1660
|
+
function position; and the function lookup every clustered site makes is
|
|
1661
|
+
memoized. The clustering scan cache is bounded by the sites it holds
|
|
1662
|
+
(200,000) as well as by its entry count, since a scan of a file of a
|
|
1663
|
+
thousand near-identical functions holds a thousand sites. Such a file,
|
|
1664
|
+
which the module-name rule now admits where the module-data rule used to
|
|
1665
|
+
decline every pair in seconds, is evaluated in full: a thousand
|
|
1666
|
+
near-identical five-line functions reading one module global take 33
|
|
1667
|
+
minutes on one core (commit `5ff2458`, September 19, 2026) and yield one
|
|
1668
|
+
helper with 669 sites, peaking at 6.6 GB (8.8 GB before the scan cache
|
|
1669
|
+
was bounded); its pair evaluation is what `--max-pairs` bounds. A
|
|
1670
|
+
per-candidate memo beneath the scan, which no run ever read, is gone.
|
|
1671
|
+
- The verdict of the instantiation check is memoized on the helper, call
|
|
1672
|
+
and block, which it repeated many times over across pair and cluster
|
|
1673
|
+
evaluation: a file of a hundred similar functions took 31 s instead of
|
|
1674
|
+
43 s when the memo landed (commit 77d18a8); the current figure is in the
|
|
1675
|
+
clustering entry above.
|
|
1676
|
+
- The `format` extra's Black floor is the version the goldens were
|
|
1677
|
+
generated with (`black>=26.3.1`, the same floor as the `dev` extra); it
|
|
1678
|
+
was two majors lower, so a user at the extra's floor could get output the
|
|
1679
|
+
goldens do not show. The sdist no longer ships the `.templates` scratch
|
|
1680
|
+
directory or two debugging scripts.
|
|
1681
|
+
- The help says what the commands do: usage lines read `towel`, each
|
|
1682
|
+
boolean option's help names its default, the retired spellings are left
|
|
1683
|
+
out, and `preview` no longer promises per-phase summaries under
|
|
1684
|
+
`--progress detail`, which only the fixed-point driver emits.
|
|
1685
|
+
- The structural memo is keyed on a block's structure, so equal blocks share
|
|
1686
|
+
one entry wherever they appear.
|
|
1687
|
+
|
|
1688
|
+
### Deprecated
|
|
1689
|
+
- The `--max-iterations`, `--non-interactive` and `rename-helpers
|
|
1690
|
+
--dry-run` spellings still parse but are left out of the help; use
|
|
1691
|
+
`--max-refactorings`, `--no-interactive` and `--preview`.
|
|
1692
|
+
|
|
1693
|
+
### Removed
|
|
1694
|
+
- `nominal_unifier.py`, which nothing imported, and the TOML backport for
|
|
1695
|
+
Python 3.10, below the supported floor, with its `tomli` dev dependency.
|
|
1696
|
+
- `ImportGraphCache.clear`, which nothing called: the cache is built per run
|
|
1697
|
+
and dropped with the engine.
|
|
1698
|
+
|
|
1699
|
+
### Fixed
|
|
1700
|
+
- The ecosystem release gate requires completed, nonempty test runs. Setup
|
|
1701
|
+
failures, incomplete runs, unknown verdicts, changed failing-test identities,
|
|
1702
|
+
and missing tests cannot pass merely because exit codes or totals match.
|
|
1703
|
+
Documented known failures and flaky-test reruns cannot hide a changed test
|
|
1704
|
+
count; matching pre-existing failures remain visible in the report. Pytest
|
|
1705
|
+
commands request complete tallies and failure identities; custom runner
|
|
1706
|
+
failure statuses must be declared, and retests retain their actual statuses.
|
|
1707
|
+
Retests remove broad selectors wherever they occur and require the exact
|
|
1708
|
+
selected test count; ambiguous command options decline the retest. Agreement
|
|
1709
|
+
on isolated tests requires confirmation with the complete original test
|
|
1710
|
+
command, so test-order regressions cannot disappear from the check. Failed
|
|
1711
|
+
Git observations cannot become `NO_CHANGE`, and added, deleted, or renamed
|
|
1712
|
+
paths count as changes. Source provenance names the checkout actually used;
|
|
1713
|
+
archives explicitly require a separately retained source manifest.
|
|
1714
|
+
The corpus manifest supplies Cheroot's declared test plugins and SimPy's
|
|
1715
|
+
benchmark fixture, and runs PLY's test scripts from the project root so each
|
|
1716
|
+
suite imports the source tree under test.
|
|
1717
|
+
The harness records its requested typing mode. Its explicit `--no-types`
|
|
1718
|
+
option supports behavioral validation of projects without a complete typing
|
|
1719
|
+
environment; it never retries a failed typed run by silently opting out.
|
|
1720
|
+
- The worker-cleanup regression fails when process inspection fails, instead
|
|
1721
|
+
of treating an empty result from a failed command as proof that workers exited.
|
|
1722
|
+
- Mypy runs in an owned persistent worker with incremental caches and periodic
|
|
1723
|
+
garbage collection. Its process-global state cannot freeze or unfreeze a
|
|
1724
|
+
library caller's heap, concurrent requests are serialized, and explicit
|
|
1725
|
+
cleanup covers both combined checkers and failed optional-dependency setup.
|
|
1726
|
+
The CLI preserves input-project configuration while checking the copied
|
|
1727
|
+
output under its original module identities, and closes the checker on
|
|
1728
|
+
success and failure. Project type rules are honored while plugins,
|
|
1729
|
+
configured executables and report destinations remain disabled.
|
|
1730
|
+
(1.772 replaced that periodic collection: each build now runs in a forked
|
|
1731
|
+
child of the worker which exits when it has answered.)
|
|
1732
|
+
- Mypy diagnostic presentation is normalized for in-memory sources. A
|
|
1733
|
+
project's `pretty = true` setting no longer crashes when a subtype probe
|
|
1734
|
+
or prospective helper extends beyond the on-disk file; project type rules
|
|
1735
|
+
remain unchanged.
|
|
1736
|
+
- Checker outcomes distinguish infrastructure failure from valid diagnostics.
|
|
1737
|
+
A pyright timeout, malformed output or failure is never evidence of a clean
|
|
1738
|
+
project or a successful subtype relation.
|
|
1739
|
+
- Lexical binding analysis recognizes PEP 695 type parameters and shadowed
|
|
1740
|
+
builtins, preserving generic identity and conditional-name evaluation.
|
|
1741
|
+
- Ruff subprocesses exclude the current project from Python's import path.
|
|
1742
|
+
Import sorting preserves ordered providers of shared bindings and statement
|
|
1743
|
+
boundaries. Permanent filesystem conflicts stop instead of retrying forever.
|
|
1744
|
+
Directory mode also terminates after all proposals in an unchanged project
|
|
1745
|
+
fail rendering or type verification, and retries deferred proposals after
|
|
1746
|
+
another successful change can alter their checking context.
|
|
1747
|
+
- Helper renaming preserves builtin reads, keyword callers, exact selection
|
|
1748
|
+
filters and Python protocol behavior. Static aliases and re-exports are
|
|
1749
|
+
tracked; ambiguous callable/module aliases, visible reflection and dynamic
|
|
1750
|
+
keyword dictionaries are refused before writing. Source discovery prunes
|
|
1751
|
+
environments and excluded directories and reports unreadable consumers.
|
|
1752
|
+
- The eager-argument guard is rebuilt on control flow. Twelve shapes that
|
|
1753
|
+
passed a differing name eagerly where the original read it only on some
|
|
1754
|
+
path are thunked or declined: a failed optional import, a `TYPE_CHECKING`
|
|
1755
|
+
import, a class attribute read bare in a method, a name bound later,
|
|
1756
|
+
deleted, or bound conditionally in the enclosing function, and at module
|
|
1757
|
+
level a name bound by `except ... as`, a `match` capture, a later `def`,
|
|
1758
|
+
or a later import.
|
|
1759
|
+
- A free variable the two blocks share follows the same rule as a differing
|
|
1760
|
+
argument: it is passed eagerly only when the call site resolves it on
|
|
1761
|
+
every path, and as a thunk otherwise. A name bound nowhere, read only on
|
|
1762
|
+
a branch no run takes, used to be hoisted into an eager argument and raise
|
|
1763
|
+
`NameError` at every call (found by the new property test); a helper
|
|
1764
|
+
defined below its callers, called once at import time before its
|
|
1765
|
+
definition, raised the same way. Both are now read where the block read
|
|
1766
|
+
them: a same-module helper reads the name bare (next entry), and a
|
|
1767
|
+
cross-file helper takes it as a thunk.
|
|
1768
|
+
- A free name that both sites resolve at module scope, or nowhere, is no
|
|
1769
|
+
longer a parameter of a same-module helper: the helper reads it bare,
|
|
1770
|
+
which is the lookup the block made, at the moment the block made it. A
|
|
1771
|
+
helper defined below its callers, a class, an import, or module data a
|
|
1772
|
+
callback rebinds between two reads (previously declined as
|
|
1773
|
+
`module_data_lookup` or `rebound_external_binding`) all extract, with
|
|
1774
|
+
fewer parameters and no thunk; hyper-h2 loses seven such parameters and
|
|
1775
|
+
gains three extractions, and its own 1,662 tests pass on the output. A
|
|
1776
|
+
clustered occurrence whose same-spelled name is a local does not join;
|
|
1777
|
+
cross-file helpers still take the name as a parameter, and module data
|
|
1778
|
+
a callback may rebind still declines a cross-file pair. So does a
|
|
1779
|
+
same-file pair whose helper is hosted in a shared ancestor class defined
|
|
1780
|
+
in another module: the pair is decided again with every name a
|
|
1781
|
+
parameter, since the ancestor's module need not import them (the
|
|
1782
|
+
release-candidate ecosystem run found oauthlib's `BearerToken` read bare
|
|
1783
|
+
in `introspect.py`, which raised NameError in 55 tests). `__class__`, the
|
|
1784
|
+
cell zero-argument `super()` reads, names the defining class and stays a
|
|
1785
|
+
parameter of a method helper.
|
|
1786
|
+
- A call to a function or method of the same module whose body reads a
|
|
1787
|
+
frame relative to its caller (`sys._getframe(n)`, `inspect.stack()`, a
|
|
1788
|
+
`stacklevel=`), directly or through other such functions, is a frame read
|
|
1789
|
+
at its call site, and the block stays where it is. The module-name rule
|
|
1790
|
+
had admitted blocks that typing_extensions' `_caller()` reads through,
|
|
1791
|
+
which a reflection guard had declined only by accident, and six
|
|
1792
|
+
`TypeAliasType` pickling tests failed in the release-candidate ecosystem
|
|
1793
|
+
run.
|
|
1794
|
+
- A call site that would pass a callee as
|
|
1795
|
+
`lambda *args, **kwargs: callee(*args, **kwargs)` is declined by name
|
|
1796
|
+
(`forwarded_callee`). It used to be declined by accident, because the
|
|
1797
|
+
lambda's own parameters were counted as names the site could not resolve;
|
|
1798
|
+
that miscount also declined any thunk containing a lambda, such as
|
|
1799
|
+
`lambda: sorted(items, key=lambda x: x)`, which now extracts.
|
|
1800
|
+
- A comprehension's target shadows only its own expressions: `[i for i in
|
|
1801
|
+
xs]` used to become `[__param_0() for i in xs]` while a free read of `i`
|
|
1802
|
+
after the comprehension stayed literal.
|
|
1803
|
+
- The frame-reading builtins, `eval`/`exec`, `sys._getframe` and
|
|
1804
|
+
`inspect.currentframe` anywhere in the enclosing function decline the
|
|
1805
|
+
block, not only inside it, and their aliases, imported or assigned
|
|
1806
|
+
(`e = eval`, `warn = warnings.warn`, `gf = sys._getframe`, an alias of an
|
|
1807
|
+
alias), are resolved through the module's bindings.
|
|
1808
|
+
- `warnings.warn` without `stacklevel`, and a `stacklevel` reached through
|
|
1809
|
+
an alias, decline the block.
|
|
1810
|
+
- An object a class instantiation or a resource factory (`open`,
|
|
1811
|
+
`connect`, `socket`, `mkdtemp`, `Popen`, `urlopen`, ...) binds in the
|
|
1812
|
+
block is returned from the helper rather than dropped when the helper's
|
|
1813
|
+
frame ends, since a later read may observe its lifetime (a temporary
|
|
1814
|
+
file, a weak reference).
|
|
1815
|
+
- A helper import goes after the module's last leading import, before its
|
|
1816
|
+
first definition (after the docstring when there are no imports), so a
|
|
1817
|
+
script that runs a statement before its imports keeps that statement
|
|
1818
|
+
first. A host for a cross-file helper is accepted only when the borrower
|
|
1819
|
+
already imports it or every module the new import would load is
|
|
1820
|
+
definition-only, so a helper import cannot run a module that prints at
|
|
1821
|
+
import time (`xf13_import_time_effects` is declined).
|
|
1822
|
+
- A form feed or a Unicode line separator in a source no longer crashes the
|
|
1823
|
+
splice: lines are counted the way the tokenizer counts them.
|
|
1824
|
+
- Async comprehensions are declined.
|
|
1825
|
+
- A rendering failure in one proposal ends that proposal, not the run.
|
|
1826
|
+
- A relative output path no longer ends the fixed point early.
|
|
1827
|
+
- Type inference no longer switches off when the working directory is
|
|
1828
|
+
above the output.
|
|
1829
|
+
- A pending transaction journal blocks a run only when its manifest names
|
|
1830
|
+
a file the run would change (a journal without a readable manifest
|
|
1831
|
+
blocks everything beneath it), and journals of concurrent runs never
|
|
1832
|
+
share a name.
|
|
1833
|
+
- A killed run's pyright probe cannot be mistaken for source.
|
|
1834
|
+
- The mypy cache and partial-copy directories are cleaned on SIGTERM.
|
|
1835
|
+
- An ASCII locale no longer breaks a run.
|
|
1836
|
+
- The change sidecar is written atomically.
|
|
1837
|
+
- `--interactive` with a closed stdin declines instead of raising.
|
|
1838
|
+
- Settings are read from the environment once per run.
|
|
1839
|
+
- Three ways an extraction could change what a program does, each found by
|
|
1840
|
+
executing crafted inputs before and after refactoring: a closure created
|
|
1841
|
+
inside the block over a name the block binds (a loop target) kept the
|
|
1842
|
+
helper's cell where the original saw the caller's later rebinding; an
|
|
1843
|
+
argument-free `dir()` read the helper's frame; and a differing free name
|
|
1844
|
+
was passed eagerly on the assumption that a name has no failure mode,
|
|
1845
|
+
so a global the module never binds raised `NameError` at the call site
|
|
1846
|
+
where the original read it only in a branch it did not take. The first
|
|
1847
|
+
two are declined; the third follows the shared-free-variable rule above.
|
|
1848
|
+
- Single-file `dry` on a file it cannot decode copied it and then stopped
|
|
1849
|
+
with a codec message naming nothing, and on a file with a syntax error
|
|
1850
|
+
reported success over an unchanged copy; both are an error naming the
|
|
1851
|
+
file (and the line). A negative `--max-refactorings` was accepted and
|
|
1852
|
+
meant "run to a fixed point"; it is refused. `preview` of one file showed
|
|
1853
|
+
a progress bar under `--progress none`, and the banner said helpers go at
|
|
1854
|
+
the end of files.
|
|
1855
|
+
- With `--types`, the variables a helper returns are revealed under each
|
|
1856
|
+
call site's own spelling; a site whose names alpha-renaming spelled
|
|
1857
|
+
differently from the helper's used to probe names that did not exist
|
|
1858
|
+
there, so the helper's return type stayed `Any` and parameter unions the
|
|
1859
|
+
return type would have invalidated went unchecked.
|
|
1860
|
+
- A refactored file keeps its own encoding, byte-order mark and newline
|
|
1861
|
+
convention: a CRLF file used to come back with every line changed, a file
|
|
1862
|
+
with a UTF-8 BOM was skipped, and one latin-1 file with a coding cookie
|
|
1863
|
+
failed a whole directory run. Sources are decoded as the interpreter
|
|
1864
|
+
decodes them and written back in the file's own bytes; `rename-helpers`
|
|
1865
|
+
reads the same way and keeps each file's encoding.
|
|
1866
|
+
- A project whose packaging layout Towel cannot model (a `hatch.toml`, some
|
|
1867
|
+
flit, poetry and pdm forms) no longer aborts the run when it has a
|
|
1868
|
+
cross-file candidate: the pair is declined (`unknown_layout` in the
|
|
1869
|
+
rejection trace) and every same-file extraction proceeds. Layout
|
|
1870
|
+
discovery raises `UnsupportedLayoutError`, and the command line reports
|
|
1871
|
+
any `TowelError` as an error line instead of a traceback.
|
|
1872
|
+
- `--progress none` is silent through the localized and stale re-analyses
|
|
1873
|
+
that follow each applied proposal, the single-file driver takes a
|
|
1874
|
+
progress mode, and `preview` accepts `--progress`. Both drivers run to a
|
|
1875
|
+
fixed point by default, as the CLI always did; they used to stop after
|
|
1876
|
+
ten iterations, and the goldens generated through them were ten-step
|
|
1877
|
+
snapshots rather than fixed points.
|
|
1878
|
+
- An import sorter's result is accepted only if it permutes or merges import
|
|
1879
|
+
statements, at any depth (a sorter also orders the imports under
|
|
1880
|
+
`if TYPE_CHECKING:`); anything else is discarded and the file is left as
|
|
1881
|
+
Towel assembled it, rather than failing the refactoring (trio).
|
|
1882
|
+
- `rename-helpers --list --json` no longer crashes on a symlinked module.
|
|
1883
|
+
- A confirmation prompt at a closed stdin declines instead of raising.
|
|
1884
|
+
- An isort skip setting, a failing or hung ruff, a hung pyright, and pyright
|
|
1885
|
+
output of an unexpected shape each degrade with a warning naming the file
|
|
1886
|
+
instead of aborting or being swallowed.
|
|
1887
|
+
- A non-integer or non-positive `TOWEL_WORKERS` is reported, and a
|
|
1888
|
+
configured worker count never reaches a platform without `fork`.
|
|
1889
|
+
- A corrupt transaction manifest and a concurrent transaction are reported
|
|
1890
|
+
as the journal's own errors.
|
|
1891
|
+
- The engine forgets a rewritten file's cached lines when it invalidates
|
|
1892
|
+
the file.
|
|
1893
|
+
- The parallel fallback no longer hides worker exceptions behind a serial
|
|
1894
|
+
retry.
|
|
1895
|
+
- The command line's stderr handler follows a redirected stderr.
|
|
1896
|
+
- The cross-file fixture package named `lib` was hidden from fresh checkouts
|
|
1897
|
+
by the `lib/` ignore pattern; the pattern exempts the fixtures.
|
|
1898
|
+
- Two blocks that differed only in the spelling of a lambda's parameter
|
|
1899
|
+
(`lambda value: value * 2` against `lambda other: other * 2`), or of an
|
|
1900
|
+
assignment expression's target (`(t := f())` against `(temp := f())`),
|
|
1901
|
+
were declined. Lambda parameters are now renamed within their own lambda
|
|
1902
|
+
by the instantiation check, which leaves a free name spelled the same
|
|
1903
|
+
outside the lambda alone, and walrus targets are block-level binders for
|
|
1904
|
+
the unifier, since an assignment expression binds in the enclosing scope.
|
|
1905
|
+
- A block that binds a variable read after it is extracted again, with the
|
|
1906
|
+
helper returning the variable and the call rebinding it (`total =
|
|
1907
|
+
helper(order)`), as the README has always shown. The orphan guard added for
|
|
1908
|
+
networkx in 1.618 did not know the generated call rebinds a returned
|
|
1909
|
+
variable, so it rejected every such block; only blocks that returned or
|
|
1910
|
+
bound nothing live could be extracted.
|
|
1911
|
+
- The import-cycle guard treats `from . import name` (and `from .. import
|
|
1912
|
+
name`) as an edge to the package's `__init__`, which that import runs
|
|
1913
|
+
whether or not `name` is a submodule. A helper hosted in a submodule that
|
|
1914
|
+
reaches back into its package this way was imported by the package
|
|
1915
|
+
initializer, which the submodule then imported half-initialized
|
|
1916
|
+
(beautifulsoup4's tests package). The initializer, which the submodule
|
|
1917
|
+
already imports, is now the host.
|
|
1918
|
+
- The import-cycle guard resolves a file's own package's absolute imports
|
|
1919
|
+
inside the tree being refactored, by suffix, even when a full-path match
|
|
1920
|
+
exists elsewhere: an out-of-place output beside the original clone
|
|
1921
|
+
(`sphinx-cleaned` next to `sphinx`) had `from sphinx.transforms import X`
|
|
1922
|
+
resolved against the original, where the helper import that closed the
|
|
1923
|
+
cycle did not exist, and sphinx's transforms package broke on import.
|
|
1924
|
+
- A union of forward references is written as one string (`'Left | Right'`),
|
|
1925
|
+
not as `'Left' | 'Right'`, which is a TypeError at definition (Towel's own
|
|
1926
|
+
suite, refactored by Towel).
|
|
1927
|
+
- Definite-assignment analysis now knows that `except E as name` deletes
|
|
1928
|
+
`name` when the handler exits, and that a `del` nested in a branch unbinds
|
|
1929
|
+
its target on that path. The returned-variable check relies on this; without
|
|
1930
|
+
it a helper could return a handler's name and raise `UnboundLocalError`
|
|
1931
|
+
where the original code did not.
|
|
1932
|
+
- With mypy, a union whose members the checker could not all order (some
|
|
1933
|
+
verdicts unanswerable: A under B, B under C, C under A) let every member
|
|
1934
|
+
absorb every other, emptied the union, and the join raised `IndexError`
|
|
1935
|
+
(sphinx, after eighteen minutes of refactoring). A member now absorbs
|
|
1936
|
+
another only on a definite `YES`, a normalization that would keep nothing
|
|
1937
|
+
keeps the union as it was, and a join with no members writes no
|
|
1938
|
+
annotation.
|
|
1939
|
+
- A function preceded by `@overload` stubs is a valid reuse target: the
|
|
1940
|
+
reuse check verifies the last module-level definition of the name, which
|
|
1941
|
+
is the runtime binding the calls resolve to, instead of refusing a name
|
|
1942
|
+
defined more than once (rfc3986's `normalize_query`).
|
|
1943
|
+
- A type mypy reveals with the path of an out-of-place output directory
|
|
1944
|
+
that is not an identifier (`h2-dbg.stream.H2Stream`, which parsed as a
|
|
1945
|
+
subtraction and was written as `self: h2 - H2Stream`) is rewritten with a
|
|
1946
|
+
placeholder for that component, and a revealed type is accepted only when
|
|
1947
|
+
its tree is made of names, attributes, subscripts, tuples, constants and
|
|
1948
|
+
unions.
|
|
1949
|
+
- `rename-helpers` with a rename file it cannot open or parse exits 1 with
|
|
1950
|
+
`Error: ...` on stderr like every other command failure; it used to print
|
|
1951
|
+
the failure to stdout without the prefix.
|
|
1952
|
+
- Invariants the engine enforced with `assert`, which `python -O` removes
|
|
1953
|
+
(a helper without a host name, a node without an end position), are
|
|
1954
|
+
exceptions that survive `-O`; a rendered module that does not parse
|
|
1955
|
+
raises `RefactoringError` naming the parse error instead of being dropped
|
|
1956
|
+
as "no insertion point"; and when applying a change fails, the cleanup's
|
|
1957
|
+
own error no longer hides the failure that triggered it. The cross-file
|
|
1958
|
+
reuse redirect follows the same import-time rule as cross-file helpers: a
|
|
1959
|
+
site is not redirected to a function in another module when the import
|
|
1960
|
+
would make the site's module load one that runs code at import time.
|
|
1961
|
+
- Ctrl-C prints one line instead of a traceback, and a reader that closes
|
|
1962
|
+
the pipe ends the run quietly; `preview` and `rename-helpers` warn about
|
|
1963
|
+
a pending journal and an in-place `dry` refuses before analysing; two
|
|
1964
|
+
source paths that differ only by case are refused before an out-of-place
|
|
1965
|
+
copy would merge them on a case-insensitive volume; a virtual environment
|
|
1966
|
+
inside the input is recognized by its `pyvenv.cfg` whatever it is named;
|
|
1967
|
+
the inline progress bar goes to stderr like tqdm's, so redirected output
|
|
1968
|
+
stays clean, and a run that asked for tqdm without it installed says so
|
|
1969
|
+
once.
|
|
1970
|
+
- A single-file run prints the frame-sensitivity warning that directory
|
|
1971
|
+
runs already printed when a file reads `locals()`, `eval` or the frame.
|
|
1972
|
+
- A `pyproject.toml` that does not parse is reported (and no layout
|
|
1973
|
+
information is taken from it) instead of being read as empty.
|
|
1974
|
+
|
|
1975
|
+
### Security
|
|
1976
|
+
- The pyright oracle starts `python -I -m pyright`. It runs from the
|
|
1977
|
+
module's directory, and `-m` put that directory first on `sys.path`, so a
|
|
1978
|
+
project package named like a standard module was imported and executed
|
|
1979
|
+
in its place: sphinx's `locale` package ran when pyright's launcher
|
|
1980
|
+
imported `subprocess`, and pyright produced nothing for flask, pytest,
|
|
1981
|
+
structlog, sphinx, trio and werkzeug in the release-candidate ecosystem
|
|
1982
|
+
run.
|
|
1983
|
+
- Pyright runs with Towel's interpreter (`--pythonpath`), so a `venv`
|
|
1984
|
+
setting in the analyzed project's pyright configuration can no longer
|
|
1985
|
+
execute that project's interpreter; SECURITY.md says exactly what each
|
|
1986
|
+
checker does and when to pass `--no-types`. Ruff and pyright are resolved
|
|
1987
|
+
from the current interpreter before PATH. The ecosystem job runs
|
|
1988
|
+
third-party test suites without the checkout token persisted and with a
|
|
1989
|
+
scratch HOME; the workflows pin actions to commits. The rename file is
|
|
1990
|
+
read as UTF-8 and the change sidecar is never written through a symlink.
|
|
1991
|
+
- The pyright probe is created with `mkstemp` in the user's package
|
|
1992
|
+
(exclusive, owner-only, never following a symlink) and removed at
|
|
1993
|
+
interpreter exit if a crash skips the cleanup; it used to be a
|
|
1994
|
+
predictable, world-readable file left behind by a kill.
|
|
1995
|
+
- The ecosystem check (`scripts/ecosystem_check.py`, `just ecosystem`)
|
|
1996
|
+
clones public repositories and runs their setup, dependency installation
|
|
1997
|
+
and test suites with the caller's privileges. It now refuses to run
|
|
1998
|
+
unless the caller passes `--run-untrusted-code` or sets
|
|
1999
|
+
`TOWEL_ECOSYSTEM_RUN_UNTRUSTED=1`, the refusal says what would execute
|
|
2000
|
+
and recommends a disposable machine or container, and each manifest
|
|
2001
|
+
entry's `prepare` command runs without a shell. Every project in the
|
|
2002
|
+
manifest is pinned to the full commit the release-gate run tested rather
|
|
2003
|
+
than `HEAD`, so a hijacked upstream cannot put unreviewed code into the
|
|
2004
|
+
run; `--print-pins` lists the commits a run tested for refreshing the pins.
|
|
2005
|
+
`towel-main` alone still tracks `main`. Its work directory is a fresh
|
|
2006
|
+
private temporary directory unless `--work` names one, so another local
|
|
2007
|
+
user cannot pre-create the tree it clones into and executes from.
|
|
2008
|
+
|
|
2009
|
+
## [1.618] - 2026-09-17
|
|
2010
|
+
|
|
2011
|
+
### Changed
|
|
2012
|
+
- Cross-file helpers are imported relatively by default (`from .module import
|
|
2013
|
+
helper`), which stays valid when an out-of-place output is adopted into its
|
|
2014
|
+
real location. An absolute import is used only when a packaging marker
|
|
2015
|
+
(`pyproject.toml`/`setup.*`) anchors the module name, so the previous
|
|
2016
|
+
behavior is preserved for in-place refactoring of a packaged project.
|
|
2017
|
+
- Trivial forwarding helpers are no longer proposed: a block whose helper body
|
|
2018
|
+
would be a single `raise`, a `return` of one call, or a bare call adds
|
|
2019
|
+
indirection without sharing logic. Construct the engine with
|
|
2020
|
+
`skip_trivial_helpers=False` to restore the old behavior.
|
|
2021
|
+
- A cross-file helper is placed in a module that does not close an import cycle.
|
|
2022
|
+
When the shared block spans modules, the helper is hosted in one the others
|
|
2023
|
+
already import rather than adding a back-edge; the extraction is declined only
|
|
2024
|
+
when no placement is safe (a genuine pre-existing cycle). This replaces the
|
|
2025
|
+
earlier behavior of hosting the helper in the first module and declining
|
|
2026
|
+
whenever that would cycle.
|
|
2027
|
+
|
|
2028
|
+
### Fixed
|
|
2029
|
+
- The rename tool no longer refuses a module merely because it contains a local
|
|
2030
|
+
variable or parameter named `vars`, `globals`, `locals`, `eval`, or `exec`;
|
|
2031
|
+
it flags only a genuine reference to the builtin.
|
|
2032
|
+
|
|
2033
|
+
## [1.414] - 2026-09-15
|
|
2034
|
+
|
|
2035
|
+
First release prepared under the open-source audit. Beta: the engineering and
|
|
2036
|
+
evidence are strong, but Towel rewrites source code and has documented
|
|
2037
|
+
limitations it cannot always detect, so preview, review the diff, and run the
|
|
2038
|
+
affected project's own tests.
|
|
2039
|
+
|
|
2040
|
+
### Added
|
|
2041
|
+
- Cross-file refactoring with import-root inference for setuptools, Hatch,
|
|
2042
|
+
Flit, Poetry, and pdm project layouts.
|
|
2043
|
+
- `towel rename-helpers`: a JSON inventory of extracted helpers and an atomic,
|
|
2044
|
+
checked rename batch, intended to be driven by a coding assistant.
|
|
2045
|
+
- `towel dry --exclude DIR` to leave a directory out of directory mode.
|
|
2046
|
+
- A pre-run scan that warns, before refactoring a directory, about modules that
|
|
2047
|
+
inspect frames or tracebacks, attribute warnings by `stacklevel`, or read
|
|
2048
|
+
source through `inspect.getsource`.
|
|
2049
|
+
- A standing ecosystem check over 91 public projects
|
|
2050
|
+
(`scripts/ecosystem_check.py`, `just ecosystem`), run weekly in CI.
|
|
2051
|
+
- Fork-based parallel analysis, enabled by a timed probe and capped by
|
|
2052
|
+
available memory, with a per-worker watchdog so a killed run leaves no
|
|
2053
|
+
workers behind. `TOWEL_WORKERS` controls it.
|
|
2054
|
+
|
|
2055
|
+
### Changed
|
|
2056
|
+
- Every accepted proposal is verified by instantiating the helper with each
|
|
2057
|
+
call site's arguments and comparing against the block it replaces, up to
|
|
2058
|
+
renamed binders. Arguments that could have observable effects or fresh
|
|
2059
|
+
identity are evaluated inside the helper at their original position.
|
|
2060
|
+
- Analysis is memoized by block structure and shares parsed graphs by
|
|
2061
|
+
reference, checked under `TOWEL_CHECK_AST_IMMUTABLE=1`.
|
|
2062
|
+
- Minimum Python is 3.11; the matrix covers 3.11 through 3.13.
|
|
2063
|
+
|
|
2064
|
+
### Fixed
|
|
2065
|
+
- A large set of soundness and scope defects found by adversarial batteries and
|
|
2066
|
+
the ecosystem check, each now covered by a fixture and a row in
|
|
2067
|
+
[docs/ADVERSARIAL_REVIEW.md](docs/ADVERSARIAL_REVIEW.md).
|
|
2068
|
+
|
|
2069
|
+
### Notes
|
|
2070
|
+
- The PyPI releases `1.0.0`–`1.0.4` are yanked for broken import handling and
|
|
2071
|
+
are not a recommended installation target.
|
|
2072
|
+
|
|
2073
|
+
[Unreleased]: https://github.com/ericeallen/towel/compare/v1.772...HEAD
|
|
2074
|
+
[1.772]: https://github.com/ericeallen/towel/compare/v1.732.post1...v1.772
|
|
2075
|
+
[1.732.post1]: https://github.com/ericeallen/towel/compare/v1.732...v1.732.post1
|
|
2076
|
+
[1.732]: https://github.com/ericeallen/towel/compare/v1.618...v1.732
|
|
2077
|
+
[1.618]: https://github.com/ericeallen/towel/compare/v1.414...v1.618
|
|
2078
|
+
[1.414]: https://github.com/ericeallen/towel/releases/tag/v1.414
|