Skip to content

--exclude regular expression leading "/" does not match in current directory #19012

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
jpgoldberg opened this issue May 1, 2025 · 2 comments
Labels
bug mypy got something wrong topic-configuration Configuration files and flags

Comments

@jpgoldberg
Copy link

jpgoldberg commented May 1, 2025

This might be a documentation error, but I expect that the documentation does reflect intended behavior so I am reporting it here.

Bug Report

The documentation includes the following example for --exclude

Similarly, you can ignore discovering directories with a given name by e.g. --exclude /build/ or those matching a subpath with --exclude /project/vendor/.

The implicature from the documentation (and just typical user expectation) is that --exclude /build/ would exclude ./build/b.py and ./some_dir/build/e.py, while all others would be checked. However ./build/b.py is not excluded. But /build/ and /project/ will not match (and therefore will not exclude) a build or project directory directly in the current directory.

Some examples will better illustrate.

To Reproduce

I created a directory structure

% tree .
.
├── a.py
├── build
│   └── b.py
├── extra_build
│   └── d.py
└── some_dir
    ├── build
    │   └── e.py
    └── c.py

The Python files are all identical and contain

from typing import TYPE_CHECKING

def main() -> None:
    v: str = "a literal string"
    if TYPE_CHECKING:
        reveal_type(v)
    else:
        print(f"Not type checking {__file__}")

if __name__ == "__main__":
    main()

Then run

 mypy --exclude '/build/' --config-file= .

in the directory that contains a.py.

Expected Behavior

a.py:7: note: Revealed type is "builtins.str"
some_dir/c.py:7: note: Revealed type is "builtins.str"
extra_build/d.py:7: note: Revealed type is "builtins.str"
Success: no issues found in 3 source files

That is, I would expect that build/b.py would not be type checked.

Actual Behavior

a.py:7: note: Revealed type is "builtins.str"
some_dir/c.py:7: note: Revealed type is "builtins.str"
extra_build/d.py:7: note: Revealed type is "builtins.str"
build/b.py:7: note: Revealed type is "builtins.str"
Success: no issues found in 4 source files

Note that build/b.py is erroneously type checked.

Your Environment

  • Mypy version used: mypy 1.14.1 (compiled: yes)
  • Mypy command-line flags: --exclude '/build/' --config-file= .
  • Mypy configuration options from mypy.ini (and other config files): None
  • Python version used: 3.12.5
  • OS: macOS 15.4
  • Shell: zsh

Update: Now tested with mypy 1.15.0 and Python 3.13.3. The behavior remains as described above.

Obvious explanation

It appears that when the documentation was written, the expectation was that regular expression would have been tested against "./build/b.py'", which would match as expected. But instead it is being tested against "build/b.py`" which does not match the regular expression

Addition samples show what happens with different expressions

--exclude 'build' correctly matches (excludes) build/b.py, but incorrectly matches (excludes) extra_build/d.py

% mypy --exclude 'build/' --config-file= . 
a.py:7: note: Revealed type is "builtins.str"
some_dir/c.py:7: note: Revealed type is "builtins.str"

--exclude '\bbuild/' does match what is expected for the structure I gave,

mypy --exclude '\bbuild/' --config-file= .
a.py:7: note: Revealed type is "builtins.str"
some_dir/c.py:7: note: Revealed type is "builtins.str"
extra_build/d.py:7: note: Revealed type is "builtins.str"

But that would not help if I constructed a subdirectory with a name like foo+build.

Although it definitely would be possible to construct a regular expression that does the right thing, it is going to be far from obvious how to do so, and unless there is some RE magic that I don't know or don't recall, it is not going to be pretty.

I have not looked at the source, but I expect that it will be easier to just prepend the OS specific version of "`./'" to what is being matched.

Meta

I am sure that I am not the first person to wish that we could go back in time and use some other form of file globbing here instead of regular expressions. But we have the history we have.

Also, I do recognize that it is much better and easier to manage exclude patterns in configuration files instead of on the command line, but I did have reason to use the command line, and so I encountered this issue.

@jpgoldberg jpgoldberg added the bug mypy got something wrong label May 1, 2025
@sterliakov
Copy link
Collaborator

The regex magic is as simple as doing ^build if I'm not mistaken.

However, your suggestion to prepend ./ sounds quite reasonable, especially given that our documentation and mypy --help assume that the leading slash is there. It'd be also consistent with gitignore syntax modulo globs vs regexes.

In mypy --help we see

  --exclude PATTERN         Regular expression to match file names, directory names or paths which mypy should ignore while recursively discovering files to check, e.g. --exclude '/setup\.py$'. May be
                            specified more than once, eg. --exclude a --exclude b

...which will exclude any setup.py not in the project root - quite the opposite of where I'd expect to find a file named setup.py.

I suspect that this filtering was unintentionally broken at some point, to be honest. I also wonder if --exclude-gitignore does the right thing, as .gitignore can reasonably contain /file.py exactly to ignore file.py in the repo root.

That code lives in modulefinder.py:matches_exclude - please feel free to submit a PR! I'm not sure about solving backwards compat problem, though.

@sterliakov sterliakov added the topic-configuration Configuration files and flags label May 1, 2025
@wyattscarpenter
Copy link
Contributor

I just edited modulefinder.py:matches_exclude for #19102 and I can confirm that it's very simple to modify; if you want to take a crack at it I encourage you to. (I also don't really care about the rest of exclude's behavior or understand what the desirable behavior is or whether this should be a behavior or docs fix, but I have the utmost faith in you.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-configuration Configuration files and flags
Projects
None yet
Development

No branches or pull requests

3 participants