Repository navigation
Attributes named like a builtin class (type, filter, …) are indexed as the builtin #230
Description
Version: scip-python 0.6.6
A class member whose name is also a builtin class (type, filter, list, dict, map, range, zip, slice, …) is indexed as a reference to that builtin class. For attributes this happens everywhere, including the line that defines them; for methods it happens when the class is imported from another module. A dataclass field called type is enough to trigger it:
demo/models.py
from dataclasses import dataclass
@dataclass
class Event:
type: str
id: intdemo/use.py
from demo.models import Event
def describe(event: Event) -> str:
return event.type + str(event.id)scip-python snapshot-dir output:
# demo/models.py
type: str
# ^^^^ reference python-stdlib 3.10 builtins/type#
id: int
# ^^ definition demo 0.1.0 `demo.models`/Event#id.
# demo/use.py
return event.type + str(event.id)
# ^^^^ reference python-stdlib 3.10 builtins/type#
# ^^ reference demo 0.1.0 `demo.models`/Event#id.
Expected: event.type should be demo 0.1.0 `demo.models`/Event#type., just as event.id is, and the field line should be the definition of Event#type.. Python looks up an attribute on the instance's class and its bases, never in builtins.
The same happens when Event and describe are in one file. Methods are affected when their class is imported: with class Box: def filter(self) from another module, box.filter() becomes builtins/filter#. Names of builtin functions (id, max) resolve correctly.
Cause: emitDeclaration checks the bare name against builtins (getBuiltInType(node, node.value)), and isStdlib accepts any builtin class without looking at the declaration. The Function branch already checks decl.moduleName; the Class branch does not. A method of a class in the same file only escapes because visitFunction has already cached its symbol. An attribute's own definition goes down the builtin path, so it never gets cached.
Possible fix (packages/pyright-scip/src/treeVisitor.ts, isStdlib): decide from the declaration the name resolves to.
- case TypeCategory.Class:
- return ClassType.isBuiltIn(builtinType) || ClassType.isSpecialBuiltIn(builtinType);
+ case TypeCategory.Class: {
+ const resolved = this.evaluator.resolveAliasDeclaration(decl, /* resolveLocalNames */ true) ?? decl;
+ return (
+ (isBuiltinModuleName(resolved.moduleName) || resolved.type === DeclarationType.Intrinsic) &&
+ (ClassType.isBuiltIn(builtinType) || ClassType.isSpecialBuiltIn(builtinType))
+ );
+ }Resolving the alias first keeps names that reach a builtin through an import, such as Callable from collections.abc, on the builtin path. The Intrinsic case does the same for __name__ and similar names (the dunder_vars snapshot). With this change, event.type, box.filter and box.type resolve to the project symbols, and the existing snapshot outputs are unchanged.
I also tried it on numpy 2.5.4's sources. All Callable references stay as they were, and the changes are the ones this issue describes: 371 uses of numpy's own bool class no longer point to builtins/bool#, 57 dtype.type accesses no longer point to builtins/type#, and a with … as bytes: variable is a local instead of builtins/bytes#. I can open a PR with a snapshot test.
Activity
linear commentedon Oct 21, 2026
OSS-24226