Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,27 @@ final-status-level = "slow"
# than the 21% of headroom that left, and the same commit timed out at 360s on two
# runs out of three while passing at 284s on the third. 10 minutes puts the cost
# back under half the ceiling and still catches a deadlock.
#
# And it drifted back again, the same way. On windows the harness measured 561.1s
# on main against the 600s ceiling — 6.5% of headroom — and basedpython-ui's
# mdtests, which took the runnable corpus from 1086 blocks to 1137 (+4.7%), were
# enough to exceed it. The cost is a transpile/python subprocess pair per block,
# and windows spawns processes far more slowly than linux, so windows is where the
# ceiling binds first. 20 minutes puts the cost back under half, as before.
#
# The harness already parallelises internally over a work-stealing pool, so this
# is not a serialisation problem: it is that the corpus only ever grows while a CI
# runner has two cores. Cutting the per-block cost is the lever, and the harness
# now takes the cheapest one — a block naming a module the interpreter does not
# have is dropped before it is transpiled rather than after, which measured 46.3s
# to 40.4s (12.7%) locally. That more than pays for the blocks added here, but the
# ceiling is raised anyway: 561.1s of 600s was already too close before any of
# them. The next lever, if this drifts a fourth time, is calling the transpiler
# in-process instead of spawning `by` per block, which would remove half the
# process spawns outright.
[[profile.ci.overrides]]
filter = 'test(clean_mdtest_blocks_run)'
slow-timeout = { period = "60s", terminate-after = 10 }
slow-timeout = { period = "60s", terminate-after = 20 }

# External-dependency mdtests provision a real virtualenv with `uv` (a network
# install of the framework plus its stubs) before type-checking, so on the fork's
Expand Down
29 changes: 29 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ repos:
files: '^(docs/basedpython/.*\.md|python/basedpython-pygments/.*|scripts/check_by_lexer\.py)$'
pass_filenames: false
priority: 0

- id: cargo-doc
name: check `cargo doc` and `cargo test --doc`
entry: >
bash -c 'RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items -p ty_python_semantic
-p ty_python_core -p ty_module_resolver -p ty_site_packages -p ty_combine -p ty_project -p ty_ide -p ty_wasm
-p ty_vendored -p ty_static -p ty -p ty_test -p ruff_db -p ruff_python_formatter
&& cargo test --all-features --doc'
types: [rust]
language: system
pass_filenames: false
priority: 0

# Prettier
- repo: https://github.com/rbubley/mirrors-prettier
rev: 0ee178619d696787ca73d210cc191d720868c631 # frozen: v3.9.6
Expand Down Expand Up @@ -183,3 +196,19 @@ repos:
files: '^crates/.*/resources/(mdtest|lint_docs)/.*\.md$'
pass_filenames: true
priority: 2

# Priority 3: regeneration runs after everything that rewrites a codegen input.
# A lint doc's bytes are copied verbatim into `ty.schema.json` and
# `crates/ty/docs/rules.md`, and three earlier hooks reformat those docs
# (mdformat, markdownlint-fix, `mdtest format`). Regenerating before them
# leaves both artifacts holding the unformatted text, which `ruff_dev`'s
# `generate_ty_schema` and `ty_rules_up_to_date` tests then fail on.
- repo: local
hooks:
- id: generate-all
name: regenerate schemas and docs from the lint docs
entry: cargo dev generate-all
language: system
files: '^crates/.*/resources/lint_docs/.*\.md$'
pass_filenames: false
priority: 3
52 changes: 52 additions & 0 deletions crates/by_transforms/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1676,6 +1676,58 @@ mod cross_file {
transpile_typed(project.db(), file, config, Some(&rebuild)).expect("transpile failed")
}

/// a literal conversion whose target is reached through a package
/// re-export: `Dp` is declared in `ui.geometry` and re-exported by
/// `ui/__init__`, and the file only imports `ui`. the conversion must
/// import the class from its declaring module — spelled through the
/// package the file does import — under the mangled alias, instead of
/// giving up because no import names `ui.geometry` itself
#[test]
fn conversion_target_reached_through_a_re_export_imports_its_declaring_module() {
let project = project_db(&[
("/ui/__init__.by", "from .geometry export Dp\n"),
(
"/ui/geometry.by",
"frozen data class Dp:\n value: float\n\n class def __of__(cls, value: int | float) -> Dp:\n return Dp(float(value))\n",
),
(
"/main.by",
"from ui import Dp\n\ndef pad(amount: Dp) -> float:\n return amount.value\n\nprint(pad(8))\n",
),
]);
let out = transpile_file(&project, "/main.by", &Config::test_default());
assert!(
out.contains("from ui.geometry import Dp as _by_conv__Dp"),
"the declaring module is spelled through the imported package, got:\n{out}"
);
assert!(
out.contains("print(pad(_by_conv__Dp.__of__(8)))"),
"the literal converts through the alias, got:\n{out}"
);
}

/// the same through a relative import inside the package: the spelling
/// stays relative, so it does not depend on how the package is rooted
#[test]
fn conversion_target_reached_through_a_relative_re_export_stays_relative() {
let project = project_db(&[
("/ui/__init__.by", "from .geometry export Dp\n"),
(
"/ui/geometry.by",
"frozen data class Dp:\n value: float\n\n class def __of__(cls, value: int | float) -> Dp:\n return Dp(float(value))\n",
),
(
"/ui/widgets.by",
"from . import Dp\n\ndef pad(amount: Dp) -> float:\n return amount.value\n\nprint(pad(8))\n",
),
]);
let out = transpile_file(&project, "/ui/widgets.by", &Config::test_default());
assert!(
out.contains("from .geometry import Dp as _by_conv__Dp"),
"got:\n{out}"
);
assert!(out.contains("_by_conv__Dp.__of__(8)"), "got:\n{out}");
}
/// `f[int](1)` must lower to `f(1)` only because ty resolves the imported
/// `f` to a generic *function* (constructor calls like `Foo[int](1)` keep
/// their args). that resolution requires cross-module type info — the
Expand Down
Loading
Loading