Skip to content
Merged

Work #207

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a0920d6
stop the server deadlocking with its own workers, and let a check wal…
KotlinIsland Sep 18, 2026
613d199
give an enum member the truthiness of the value it was declared with
KotlinIsland Sep 18, 2026
420cd5c
add a repeated tuple type, `(A, B) * int`, for a tuple multiplied by …
KotlinIsland Sep 13, 2026
72d807a
add `used-underscore-name`, a warning for using a name a leading unde…
KotlinIsland Sep 20, 2026
bd51666
infer `asserts` guards from unannotated bodies that return nothing, a…
KotlinIsland Sep 20, 2026
f4d7655
stamp the document version on the edits `by/transpileForBuild` returns
KotlinIsland Sep 15, 2026
8899ff2
re-stage a set of files in one request instead of one file at a time
KotlinIsland Sep 15, 2026
184f41c
add `by/buildOutput`, which maps a source to its built file and back
KotlinIsland Sep 15, 2026
20298a1
add `by/entryPoint`, `by/testItems` and `by/runModules`
KotlinIsland Sep 15, 2026
8785d9e
add `by/syntaxOutline`, and highlight paired keywords
KotlinIsland Sep 15, 2026
59011ba
write a build with no `--out` into the project's own build directory
KotlinIsland Sep 15, 2026
f8fe6b4
add `by run --launcher` to start the program through another program
KotlinIsland Sep 15, 2026
291d1a6
measure `by/alignmentGroups` columns by display width
KotlinIsland Sep 15, 2026
af3264a
add `by run --in-build`, which runs the program in the staged tree
KotlinIsland Sep 16, 2026
d5c13ab
keep the check mode out of `should_check_file`
KotlinIsland Sep 16, 2026
3823099
add refactoring code actions, starting with inline variable
KotlinIsland Sep 15, 2026
35f4f4d
add extract variable and introduce constant refactorings
KotlinIsland Sep 16, 2026
f1f086b
add an extract function refactoring
KotlinIsland Sep 16, 2026
c5a7929
add an add-return-annotation refactoring
KotlinIsland Sep 16, 2026
1370418
add data class conversion refactorings
KotlinIsland Sep 16, 2026
49aacc2
add a quick fix that writes `?.` for an attribute only `None` lacks
KotlinIsland Sep 16, 2026
c1de0cd
work out only the refactorings a client asked for
KotlinIsland Sep 21, 2026
f97599b
document the program-model requests and keyword highlighting
KotlinIsland Sep 21, 2026
cc426a9
raise the linux and windows test jobs' timeouts to 120 minutes
KotlinIsland Sep 23, 2026
3407b48
[ty] Re-enable the symlink file-watching test (#28703)
MichaReiser Sep 19, 2026
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
14 changes: 12 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,12 @@ jobs:
# work — and the 1942s run reached 45m mid-way through the dogfood steps and was killed.
# Nextest's own total is the number to compare here: it stays flat across the merge, so the
# budget is for the runner's variance, not for the suite growing.
timeout-minutes: 75
#
# 75 in turn was overrun on 2026-09-23 by two runs of the same code a few hours apart:
# one finished in 46 minutes, and the other, on a newer runner image, summed 244 minutes
# of test time against 125 for the fast one and was killed at test 15000 of 15397. A
# machine half as fast is within the variance seen, and it takes the job past 90 minutes
timeout-minutes: 120
env:
# Line-tables-only debug info: faster builds, backtraces still work.
CARGO_PROFILE_DEV_DEBUG: line-tables-only
Expand Down Expand Up @@ -660,7 +665,12 @@ jobs:
# windows is the slow half of this matrix and came within two minutes of the previous
# 45-minute budget on 2026-08-13 (42m54s, against 24m56s for macos on the same run), so it
# gets the same headroom the linux job needed for the same reason
timeout-minutes: 75
#
# that figure predates windows pinning an interpreter, which is what lets `by_build`
# compile anything there. with it, `by_build` alone is 77 of the 249 minutes of test time,
# and a whole run took 74m50s on 2026-09-23 on an ordinary runner — no headroom left at
# 75 for the variance the linux job sees
timeout-minutes: 120
env:
# Line-tables-only debug info: faster builds, backtraces still work.
CARGO_PROFILE_DEV_DEBUG: line-tables-only
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/by_stage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ ty_project = { workspace = true }
ty_python_semantic = { workspace = true }

anyhow = { workspace = true }
dunce = { workspace = true }
rayon = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
Expand Down
11 changes: 9 additions & 2 deletions crates/by_stage/src/emit.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Checking a project's sources and turning the ones that pass into python.
//!
//! The one transpile path. `by run`, `by build`, `by transpile <dir>` and the
//! server's single-file re-stage all arrive here, which is the point: two paths
//! server's re-stage of an edit all arrive here, which is the point: two paths
//! could disagree about what a file lowers to, and a build tree whose modules were
//! written by one and re-staged by the other is a tree the debugger will refuse —
//! or worse, accept while the line table describes something else.
Expand Down Expand Up @@ -130,9 +130,16 @@ pub fn check_and_transpile(
// batching files into fewer jobs measured 4.5x slower, and cloning only every
// 32 files gives the saving up entirely — so anything that reduces the number
// of clones silently undoes this
//
// ignoring the check mode, because these files are what the output is made of:
// a language server whose mode reports diagnostics only for open files still
// has to refuse a closed one that does not check, before its bytes are written
// into a tree a program is running out of
let checked: Vec<Vec<Diagnostic>> = handles
.par_iter()
.map_with_db(db, |db, (_, file)| db.check_file(*file))
.map_with_db(db, |db, (_, file)| {
ty_project::check_file_ignoring_check_mode(db, *file)
})
.collect();
for ((_, file), diags) in handles.iter().zip(checked) {
if diags.iter().any(is_unusable_source) {
Expand Down
112 changes: 112 additions & 0 deletions crates/by_stage/src/layout.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! Where a project's build writes, and which file in it came from which source.
//!
//! An editor needs this for everything that relates a `.by` to what it becomes: opening the
//! generated module, going back from a generated module to its source, keeping the output out of
//! the editor's own index, telling a test runner not to collect a second copy of every test. Each of
//! those used to be an editor's guess — `out/<path relative to the project>.py` — and every part of
//! that guess went wrong: the directory is `build`, and the path inside it follows the *module*
//! tree, not the directory tree (see [`crate::staging::transpiled_destination`]). The build is the
//! only thing that knows, so this is the build's answer, asked of the same database and the same
//! file set a build reads.

use std::path::{Path, PathBuf};

use ty_project::{Db as _, ProjectDatabase};

use crate::project::{BY_SOURCES, module_roots, project_sources};
use crate::staging::transpiled_destination;

/// The directory `by build` and `by compile` write to when they are not given `--out`, relative
/// to the project root — see [`default_build_directory`].
const DEFAULT_OUTPUT_DIRECTORY: &str = "build";

/// Where the project rooted at `project_root` is built when no `--out` names somewhere else.
///
/// Relative to the project, not to wherever a command happens to run: the output belongs to the
/// project the way its sources and its `.venv` do, so `by build` in `tests/` writes the same tree
/// as `by build` at the root, and the one place an editor is told the build writes is the place
/// it does. An explicit `--out` is the caller's own path and stays relative to where they typed it.
pub fn default_build_directory(project_root: &Path) -> PathBuf {
project_root.join(DEFAULT_OUTPUT_DIRECTORY)
}

/// A path's place in its project's build.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildOutput {
/// The project root: the project a `by build` run anywhere inside it builds.
project_root: PathBuf,

/// Where `by build`, run anywhere in the project with no `--out`, writes the project.
build_directory: PathBuf,

/// When the path is a `.by` or `.byi` the build is made of, the file it is written to.
generated: Option<PathBuf>,

/// When the path is a file in [`Self::build_directory`] that a source is written to, that
/// source.
source: Option<PathBuf>,
}

/// Where `path` stands in the build of the project `db` holds.
///
/// Answers for any path, including one that does not exist yet — a generated file before the first
/// build is still the place that build will write. Neither side of the mapping is checked against
/// the disk.
pub fn build_output(db: &ProjectDatabase, path: &Path) -> BuildOutput {
let declared_root = db.project().root(db).as_std_path();
let root = canonical(declared_root);
let build_directory = default_build_directory(&root);
let roots = module_roots(db, &root);
let sources = project_sources(db, BY_SOURCES, &root, Some(&build_directory));
let wanted = canonical(path);

// resolved once for the whole sweep. both lookups below compare against every source, and
// `canonical` walks a path making a syscall per level, so resolving inside them would ask
// the file system the same questions once per source per lookup
let resolved: Vec<(PathBuf, PathBuf)> = sources
.iter()
.map(|(source, _)| {
let source = canonical(source);
let destination = transpiled_destination(&roots, &root, &source);
(source, destination)
})
.collect();

let generated = resolved
.iter()
.find(|(source, _)| *source == wanted)
.map(|(_, destination)| build_directory.join(destination));

let source = wanted
.strip_prefix(&build_directory)
.ok()
.and_then(|relative| {
resolved
.iter()
.find(|(_, destination)| destination == relative)
.map(|(source, _)| source.clone())
});

BuildOutput {
project_root: root,
build_directory,
generated,
source,
}
}

/// `path` with its links resolved, or as given when it cannot be — a path that does not exist yet is
/// resolved through its nearest parent that does, so it still compares equal to the resolved form.
///
/// On Windows resolving answers with a verbatim `\\?\C:\…` path, which is dropped back to the
/// `C:\…` spelling a client sends and a client can open.
fn canonical(path: &Path) -> PathBuf {
if let Ok(resolved) = std::fs::canonicalize(path) {
return dunce::simplified(&resolved).to_path_buf();
}
match (path.parent(), path.file_name()) {
(Some(parent), Some(name)) => canonical(parent).join(name),
_ => path.to_path_buf(),
}
}
9 changes: 8 additions & 1 deletion crates/by_stage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,19 @@
//! entry at a time by a re-stage.
//! - [`record`] — `_by_build.json`, what a tree says about the build that wrote
//! it.
//! - [`restage`] — one file's slot in an existing tree, recomputed.
//! - [`restage`] — the slots of an edit's files in an existing tree, and the
//! one sourcemap describing them, recomputed.
//! - [`layout`] — where a project's build writes, and which output came from
//! which source, for an editor to ask.
//! - [`run_module`] — which file `by run <module>` executes, and the module a
//! file runs as.

pub mod emit;
pub mod layout;
pub mod project;
pub mod record;
pub mod restage;
pub mod run_module;
pub mod runtime;
pub mod sourcemap;
pub mod staging;
Expand Down
Loading
Loading