Skip to content

Do not duplicate Tool class creation#4838

Merged
bdbaddog merged 9 commits into
SCons:masterfrom
mwichmann:tool-init
Jun 22, 2026
Merged

Do not duplicate Tool class creation#4838
bdbaddog merged 9 commits into
SCons:masterfrom
mwichmann:tool-init

Conversation

@mwichmann

@mwichmann mwichmann commented Mar 11, 2026

Copy link
Copy Markdown
Collaborator

If "default" is part of the tool list (including common case of implicit inclusion when no tools are declared), the default.py tool will be run. This calls a function to generate the default tool list, which as a side effect creates a Tool instance for each tool examined. The tool then loops through the returned list, creating a fresh Tool instance for each tool. To alleviate this duplication, the Tool instances are returned in the default list rather than the tool name string. If an attempt is made to instantiate a Tool with the name argument being an existing instance, it is returned as-is, else the normal instantion takes place as before.

In the default tool - use the routine the SCons.Platform module provides for getting the default tool list (DefaultToolList). It does the same thing as default.generate did directly, but this way "we're using the API" and not duplicating an existing routine.

Contributor Checklist:

  • I have created a new test or updated the unit tests to cover the new/changed functionality.
  • I have updated CHANGES.txt and RELEASE.txt (and read the README.rst).
  • I have updated the appropriate documentation

@mwichmann mwichmann added the Tools Issues related to tools subsystem label Mar 11, 2026
Comment thread SCons/Tool/__init__.py
@mwichmann

Copy link
Copy Markdown
Collaborator Author

Well, it's Python... but I don't see how, no.

@bdbaddog

Copy link
Copy Markdown
Contributor

Any work outstanding to take the "Draft" label off this one?

@mwichmann mwichmann marked this pull request as ready for review April 12, 2026 12:39
@mwichmann mwichmann added this to 4.11 Apr 12, 2026
@mwichmann mwichmann moved this to In review in 4.11 Apr 12, 2026
@mwichmann mwichmann added this to the NextRelease milestone Apr 12, 2026
@mwichmann

Copy link
Copy Markdown
Collaborator Author

No. I've made the change and added the changelog snip.

If "default" is part of the tool list, the default.py tool will be run.
This calls a function to generate the default tool list, which as
a side effect creates a Tool instance for each tool examined. The
tool then loops through the returned list, creating a Tool instance
for each tool.  To alleviate this duplication, the Tool instances
are returned in the default list, and if an attempt is made to instantiate
a Tool with the "name" argument being an instance already, it is
returned as-is.

In the default tool - use the routine the SCons.Platform module
provides for getting the default tool list.  It does the same thing as
default.generate did directly, but this way "we're using the API".

Signed-off-by: Mats Wichmann <mats@linux.com>
just an import and a precautionary "str" conversion

Signed-off-by: Mats Wichmann <mats@linux.com>
@mwichmann

Copy link
Copy Markdown
Collaborator Author

oops, had to force-push a rebase as the just-added CHANGES and RELEASE things conflicted - master had moved on.

Comment thread SCons/Tool/__init__.py Outdated
else:
kw = self.init_kw
env.AppendUnique(TOOLS=[self.name])
env.AppendUnique(TOOLS=[str(self.name)])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this needed? isn't .name already a string?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

hmm, probably you're right. I'll check eventually.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reverted.

@mwichmann

Copy link
Copy Markdown
Collaborator Author

Had one of those branch-out-of-sync problems that didn't want to resolve, so made this edit through the github UI.

bdbaddog and others added 3 commits May 5, 2026 16:31
Narrow Tool.__eq__ to only compare against Tool instances and strings
(returning NotImplemented otherwise) instead of stringifying any object,
removing accidental cross-type matches while preserving name-based
equality used by membership tests and sets. Add a docstring documenting
the comparison semantics, correct the __new__ return annotation, and add
test_Tool_instance covering instance passthrough, equality, and hashing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread SCons/Tool/__init__.py
return self.name == other.name
if isinstance(other, str):
return self.name == other
return NotImplemented

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

If you do this, the method annotation is no longer correct.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I asked the AI about that.. apparently mypy any pyright will handle NotImplemented even if bool is the specified return value.. thoughts?

In CPython's typeshed (the canonical type stubs that mypy/pyright use), the object.__eq__ signature is:

  def __eq__(self, value: object, /) -> bool: ...

  It's annotated -> bool — even though object.__eq__ and virtually every real __eq__ can return NotImplemented. Typeshed deliberately does not write -> bool |
  type(NotImplemented).

  Why they do it that way:

  1. NotImplemented is a protocol sentinel, not a normal return value. When a.__eq__(b) returns NotImplemented, the Python interpreter intercepts it and tries the
  reflected b.__eq__(a), ultimately falling back to identity. Code essentially never does result = a.__eq__(b) and then inspects result — you write a == b, which the
  interpreter resolves to a real bool. So from a caller's perspective, the effective type of == is bool, and that's what typeshed annotates.
  2. The honest "full" type is ugly and rarely used. type(NotImplemented) is types.NotImplementedType (exposed as typing-friendly only in 3.10+). Writing -> bool |
  NotImplementedType is technically more precise but typeshed chose bool for ergonomics, and the type checkers special-case NotImplemented in __eq__/__lt__/etc. so
  returning it from a -> bool-annotated dunder is not flagged as an error.

  So "correct per typeshed" means: the established convention — the one mypy and pyright are tuned to expect — is to annotate __eq__ as -> bool and let the type
  checker handle the NotImplemented special-case. My proposed -> bool | type(NotImplemented) was more literally accurate but against that convention, and it's also
  slightly off as a type expression (type(NotImplemented) is a runtime value, not the clean typing form types.NotImplementedType).

  Bottom line: the existing -> bool is already the conventional, checker-approved annotation. There's nothing to fix on __eq__. My earlier framing of -> bool |
  type(NotImplemented) as "more correct" was misleading — I'd recommend leaving it as -> bool.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Huh. So "this has happened before" (unsurprisingly) and there's a special case for it. I didn't know that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(at least according to claude.. ;) I'll double check independently.. hmm other sources seem to say the same, but the mypy docs don't explicitly say the above.
I think it's probably ok as is.

@bdbaddog bdbaddog merged commit 960f328 into SCons:master Jun 22, 2026
10 of 12 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in 4.11 Jun 22, 2026
@mwichmann mwichmann deleted the tool-init branch June 22, 2026 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Tools Issues related to tools subsystem

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants