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
2 changes: 1 addition & 1 deletion distutils/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def __init__(self, dist: Distribution) -> None:
# timestamps, but methods defined *here* assume that
# 'self.force' exists for all commands. So define it here
# just to be safe.
self.force = None
self.force: bool | None = None
Comment thread
jaraco marked this conversation as resolved.

# The 'help' flag is just used for command-line parsing, so
# none of that complicated bureaucracy is needed.
Expand Down
10 changes: 5 additions & 5 deletions distutils/command/build_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ class build_ext(Command):
('help-compiler', None, "list available compilers", show_compilers),
]

def initialize_options(self):
def initialize_options(self) -> None:
self.extensions = None
self.build_lib = None
self.plat_name = None
self.build_temp = None
self.build_lib: str = None # Should always be set in finalize_options
self.plat_name: str = None # Should always be set in finalize_options
self.build_temp: str = None # Should always be set in finalize_options
Comment on lines +114 to +116

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.

@Avasam This change was necessary to get the build to pass. Is this also acceptable?

@Avasam Avasam Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not quite:

  1. By adding -> None to initialize_options, mypy now parses and type-checks this function. So ALL members you initialize and don't explicitly type can only ever be None. (I've very purposefully avoided dealing with that until more of the project's typing could be cleared up) (surely one of the mypy PRs still left would've detected that issue)
  2. The annoying thing with this initialize/finalize setup in general, is that you get false-positives comparing against None in finalize_options. Including user-defined overloads of finalize_options.
    For now, I've been fixing types to X | None when it could still be None after finalize_options. And fixing bad None defaults when that was appropriate. The third case (as per point 1) I've mostly left alone for now ^^"

If you HAVE to decide, I'd say keeping the type accurate to AFTER running finalize_options is best, and it'll just be a known distutils/setuptools typing caveat that there will be false-positives when comparing against None in initialize_options/finalize_options. (so what you have right now, but type ALL members before adding -> None to initialize_options)

But all is not lost, there's one more trick you can use we developed in typeshed: MaybeNone aka "The Any Trick"

(it'll obviously reduce type safety, but false-negatives are generally preferred over false-positives, especially for a legacy system like this)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In case of doubt, let's keep working through other PRs and I can tackle this more in depth

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.

I'm not sure I follow, but this does allow the tests to pass, so let's go with it for now and we can refine subsequently. Thanks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

See the new failing test results in #368 for what I meant

distutils/command/build_ext.py:114: error: Incompatible types in assignment (expression has type "None", variable has type "str")  [assignment]
distutils/command/build_ext.py:115: error: Incompatible types in assignment (expression has type "None", variable has type "str")  [assignment]
distutils/command/build_ext.py:116: error: Incompatible types in assignment (expression has type "None", variable has type "str")  [assignment]
distutils/command/build_ext.py:128: error: Incompatible types in assignment (expression has type "None", variable has type "bool")  [assignment]
distutils/command/build_ext.py:176: error: Incompatible types in assignment (expression has type "str | None", variable has type "None")  [assignment]
distutils/command/build_ext.py:178: error: Incompatible types in assignment (expression has type "list[Extension] | None", variable has type "None")  [assignment]
distutils/command/build_ext.py:185: error: Incompatible types in assignment (expression has type "list[str]", variable has type "None")  [assignment]
distutils/command/build_ext.py:206: error: Incompatible types in assignment (expression has type "list[Never]", variable has type "None")  [assignment]
distutils/command/build_ext.py:208: error: Incompatible types in assignment (expression has type "list[Never]", variable has type "None")  [assignment]
distutils/command/build_ext.py:213: error: Incompatible types in assignment (expression has type "list[Never]", variable has type "None")  [assignment]
distutils/command/build_ext.py:280: error: Incompatible types in assignment (expression has type "list[Never]", variable has type "None")  [assignment]

self.inplace = False
self.package = None

Expand All @@ -125,7 +125,7 @@ def initialize_options(self):
self.rpath = None
self.link_objects = None
self.debug = None
self.force = None
self.force: bool = None # Should always be set in finalize_options

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not actually type-safe and easy to break in the future, but this is only place in distutils we assume a type for after finalize_options.

Alternatively this can be kept as bool | None, which still works everywhere expecting a falsy/truthy value. But in some places where an actual boolean is expected (like https://github.com/pypa/setuptools/blob/d198e86f57231e83de87975c5c82bc40c196da79/setuptools/command/build_ext.py#L225-L227 ) we'd have to coerce to a boolean: bool(self.force)

self.compiler = None
self.swig = None
self.swig_cpp = None
Expand Down
Loading