Do not duplicate Tool class creation#4838
Conversation
|
Well, it's Python... but I don't see how, no. |
|
Any work outstanding to take the "Draft" label off this one? |
|
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>
|
oops, had to force-push a rebase as the just-added CHANGES and RELEASE things conflicted - master had moved on. |
| else: | ||
| kw = self.init_kw | ||
| env.AppendUnique(TOOLS=[self.name]) | ||
| env.AppendUnique(TOOLS=[str(self.name)]) |
There was a problem hiding this comment.
Is this needed? isn't .name already a string?
There was a problem hiding this comment.
hmm, probably you're right. I'll check eventually.
Responding to review comment.
|
Had one of those branch-out-of-sync problems that didn't want to resolve, so made this edit through the github UI. |
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>
| return self.name == other.name | ||
| if isinstance(other, str): | ||
| return self.name == other | ||
| return NotImplemented |
There was a problem hiding this comment.
If you do this, the method annotation is no longer correct.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Huh. So "this has happened before" (unsurprisingly) and there's a special case for it. I didn't know that.
There was a problem hiding this comment.
(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.
If "default" is part of the tool list (including common case of implicit inclusion when no tools are declared), the
default.pytool will be run. This calls a function to generate the default tool list, which as a side effect creates aToolinstance for each tool examined. The tool then loops through the returned list, creating a freshToolinstance for each tool. To alleviate this duplication, theToolinstances are returned in the default list rather than the tool name string. If an attempt is made to instantiate a Tool with thenameargument 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.Platformmodule provides for getting the default tool list (DefaultToolList). It does the same thing asdefault.generatedid directly, but this way "we're using the API" and not duplicating an existing routine.Contributor Checklist:
CHANGES.txtandRELEASE.txt(and read theREADME.rst).