Skip to content

[ty] Anchor default exclude patterns at the project root - #28463

Merged
MichaReiser merged 3 commits into
mainfrom
micha/ty-force-exclude-project-root
Sep 11, 2026
Merged

MichaReiser merged 3 commits into
mainfrom
micha/ty-force-exclude-project-root

Conversation

@MichaReiser

@MichaReiser MichaReiser commented Sep 9, 2026 •

Copy link
Copy Markdown
Member

Summary

Follow up to #28451 to address @zsol's finding that --force-exclude excluded all project files when a project was within a excluded-by-default directory. E.g., checking ty check --force-exclude dist/project --project dist/project excluded project because it matched the **/exclude pattern.

The approach taken in this PR is to anchor all default exclude patterns with the project root. That is, **/exclude gets rewritten to <project>/**/exclude. This is the same as we do for all patterns in a ty.toml.

This behavior is the most consistent in my view and ensures the default exclude values behave exactly the same as when a user adds them manually (or similar patterns) in their configuration.

The main downside of it is that ty stops ignoring folders matching the default exclude patterns when checking a folder outside a project. Let's say you have

- project/
 | - main.py
- sibling
 |- .venv/some.py
 |- main.py

and you run

ty check . ../

in project. ty will now also check .venv/some.py because the **/.venv pattern doesn't match the path in sibling.

I previously considered taking the same approach as Ruff. That is, for dist/project, check dist/project (matches no exclude pattern) but don't check dist/ as we do today (because it's outside the project root and which would match **/dist). The advantage of this approach is that it would keep excluding sibling/.venv. However, I do think the consistency with a user written configuration is more important and makes default excludes less surprising/magical.

Test Plan

Added CLI tests

@astral-sh-bot astral-sh-bot Bot added the ty The ty type checker label Sep 9, 2026
@astral-sh-bot

astral-sh-bot Bot commented Sep 9, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 97.84%. The percentage of expected errors that received a diagnostic held steady at 96.45%. The number of fully passing files held steady at 128/145.

@astral-sh-bot

astral-sh-bot Bot commented Sep 9, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

ecosystem-analyzer results

No diagnostic changes detected ✅

Full report with detailed diff (timing results)

@MichaReiser MichaReiser added the cli Related to the command-line interface of Ruff or ty label Sep 9, 2026
@MichaReiser
MichaReiser requested a review from zsol September 9, 2026 16:55
@MichaReiser
MichaReiser marked this pull request as ready for review September 9, 2026 16:55
@MichaReiser
MichaReiser requested review from a team as code owners September 9, 2026 16:55
@MichaReiser

Copy link
Copy Markdown
Member Author

I'm very inclined to change the implementation to

I considered simply anchoring all default exclude patterns with the project_root (the same as we do for patterns in a ty.toml). However, this has the downside that sibling/.venv would no longer be excluded. This seemed worse to me. The counter argument is that a "/tests" exclude in a ty.toml also doesn't apply to sibling/tests because all patterns in a ty.toml are anchored to the project.root (it is rewritten to //tests. Simply anchoring all default patterns with the project root would certainly be easier to implement.

It feels more consistent and matches the behavior you get when you add an exclude in the config.

@MichaReiser MichaReiser changed the title [ty] Anchor force-exclude checks at the project root [ty] Anchor default exclude patterns at the project root Sep 9, 2026
for pattern in default_patterns {
PortableGlobPattern::parse(pattern, PortableGlobKind::Exclude)
.and_then(|exclude| Ok(excludes.add(&exclude.into_absolute(""))?))
.and_then(|exclude| Ok(excludes.add(&exclude.into_absolute(project_root))?))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should escape project_root in case it contains characters that would be interpreted specially in a glob

@MichaReiser MichaReiser Sep 11, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nice catch. I'll address this in a separate PR as this also applies to user provided excludes

Comment on lines -1343 to +1345
.and_then(|exclude| Ok(excludes.add(&exclude.into_absolute(""))?))
.and_then(|exclude| Ok(excludes.add(&exclude.into_absolute(project_root))?))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can project_root change after we construct patterns? Like in LSP/watch mode. If so, we'll need to recompute these patterns

@MichaReiser MichaReiser Sep 11, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It can, but the settings are recomputed in that case. The only case where we have an incorrectly anchored exclude is if loading the new settings fail. ty then uses the old settings until the user fixes the settings. This seems fine to me and is also true for user provided excludes

@MichaReiser
MichaReiser merged commit ed76d0f into main Sep 11, 2026
71 checks passed
@MichaReiser
MichaReiser deleted the micha/ty-force-exclude-project-root branch September 11, 2026 10:28
carljm added a commit that referenced this pull request Sep 11, 2026
…aliases

* origin/main: (30 commits)
  [ty] Respect instance dictionary storage for slotted classes (#27749)
  Add a GitHub repository threat model for Ruff (#28395)
  [ty] Preserve wrapped signatures in nominal descriptor checks (#28466)
  [ty] Avoid rebinding extracted method calls (#28469)
  Recognize quoted types in `typing.TypeForm` (#28507)
  Support conditional assignment to `__lazy_modules__` (#28491)
  Add support for `__lazy_modules__` (#28459)
  [`pyupgrade`] Stop recommending deprecated `ByteString` aliases (`UP035`) (#28498)
  [ty] Anchor default exclude patterns at the project root (#28463)
  [ty] Add playground command to toggle inlay hints (#28517)
  [ty] Ignore divergent markers when detecting descriptors (#28514)
  [ty] Reuse rendered union elements when displaying types (#28494)
  [ty] Fix assignability of bounded typevars to intersection types (#28479)
  [ty] Retain package listings between resolution steps (#28278)
  Add accessors for comparison expressions (#28485)
  [ty] Refactor module resolution to use a `ModuleDirectory` abstraction. (#28418)
  [ty] Reduce retained AST memory by shrinking expressions (#28335)
  Bump version to 0.16.7 (#28496)
  Install rustfmt before linting releases (#28495)
  ensure prepare release changes pass prek (#28488)
  ...

# Conflicts:
#	crates/ty_python_semantic/src/types/bool.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli Related to the command-line interface of Ruff or ty ty The ty type checker

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants