mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:34:57 +00:00
[ty
] Include NamedTupleFallback
members in NamedTuple
instance completions (#20356)
## Summary Fixes https://github.com/astral-sh/ty/issues/1161 Include `NamedTupleFallback` members in `NamedTuple` instance completions. - Augment instance attribute completions when completing on NamedTuple instances by merging members from `_typeshed._type_checker_internals.NamedTupleFallback` ## Test Plan Adds a minimal completion test `namedtuple_fallback_instance_methods` --------- Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
parent
02c58f1beb
commit
093fa72656
2 changed files with 80 additions and 4 deletions
|
@ -238,6 +238,58 @@ def _(t_person: type[Person]):
|
||||||
static_assert(has_member(t_person, "keys"))
|
static_assert(has_member(t_person, "keys"))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### NamedTuples
|
||||||
|
|
||||||
|
```py
|
||||||
|
from ty_extensions import has_member, static_assert
|
||||||
|
from typing import NamedTuple, Generic, TypeVar
|
||||||
|
|
||||||
|
class Person(NamedTuple):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
|
||||||
|
static_assert(has_member(Person, "id"))
|
||||||
|
static_assert(has_member(Person, "name"))
|
||||||
|
|
||||||
|
static_assert(has_member(Person, "_make"))
|
||||||
|
static_assert(has_member(Person, "_asdict"))
|
||||||
|
static_assert(has_member(Person, "_replace"))
|
||||||
|
|
||||||
|
def _(person: Person):
|
||||||
|
static_assert(has_member(person, "id"))
|
||||||
|
static_assert(has_member(person, "name"))
|
||||||
|
|
||||||
|
static_assert(has_member(person, "_make"))
|
||||||
|
static_assert(has_member(person, "_asdict"))
|
||||||
|
static_assert(has_member(person, "_replace"))
|
||||||
|
|
||||||
|
def _(t_person: type[Person]):
|
||||||
|
static_assert(has_member(t_person, "id"))
|
||||||
|
static_assert(has_member(t_person, "name"))
|
||||||
|
|
||||||
|
static_assert(has_member(t_person, "_make"))
|
||||||
|
static_assert(has_member(t_person, "_asdict"))
|
||||||
|
static_assert(has_member(t_person, "_replace"))
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
class Box(NamedTuple, Generic[T]):
|
||||||
|
item: T
|
||||||
|
|
||||||
|
static_assert(has_member(Box, "item"))
|
||||||
|
|
||||||
|
static_assert(has_member(Box, "_make"))
|
||||||
|
static_assert(has_member(Box, "_asdict"))
|
||||||
|
static_assert(has_member(Box, "_replace"))
|
||||||
|
|
||||||
|
def _(box: Box[int]):
|
||||||
|
static_assert(has_member(box, "item"))
|
||||||
|
|
||||||
|
static_assert(has_member(box, "_make"))
|
||||||
|
static_assert(has_member(box, "_asdict"))
|
||||||
|
static_assert(has_member(box, "_replace"))
|
||||||
|
```
|
||||||
|
|
||||||
### Unions
|
### Unions
|
||||||
|
|
||||||
For unions, `ide_support::all_members` only returns members that are available on all elements of
|
For unions, `ide_support::all_members` only returns members that are available on all elements of
|
||||||
|
|
|
@ -12,7 +12,10 @@ use crate::semantic_index::{
|
||||||
};
|
};
|
||||||
use crate::types::call::{CallArguments, MatchedArgument};
|
use crate::types::call::{CallArguments, MatchedArgument};
|
||||||
use crate::types::signatures::Signature;
|
use crate::types::signatures::Signature;
|
||||||
use crate::types::{ClassBase, ClassLiteral, DynamicType, KnownClass, KnownInstanceType, Type};
|
use crate::types::{
|
||||||
|
ClassBase, ClassLiteral, DynamicType, KnownClass, KnownInstanceType, Type,
|
||||||
|
class::CodeGeneratorKind,
|
||||||
|
};
|
||||||
use crate::{Db, HasType, NameKind, SemanticModel};
|
use crate::{Db, HasType, NameKind, SemanticModel};
|
||||||
use ruff_db::files::{File, FileRange};
|
use ruff_db::files::{File, FileRange};
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
|
@ -95,7 +98,13 @@ impl<'db> AllMembers<'db> {
|
||||||
),
|
),
|
||||||
|
|
||||||
Type::NominalInstance(instance) => {
|
Type::NominalInstance(instance) => {
|
||||||
self.extend_with_instance_members(db, ty, instance.class_literal(db));
|
let class_literal = instance.class_literal(db);
|
||||||
|
self.extend_with_instance_members(db, ty, class_literal);
|
||||||
|
|
||||||
|
// If this is a NamedTuple instance, include members from NamedTupleFallback
|
||||||
|
if CodeGeneratorKind::NamedTuple.matches(db, class_literal) {
|
||||||
|
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_class_literal(db));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::ClassLiteral(class_literal) if class_literal.is_typed_dict(db) => {
|
Type::ClassLiteral(class_literal) if class_literal.is_typed_dict(db) => {
|
||||||
|
@ -113,6 +122,10 @@ impl<'db> AllMembers<'db> {
|
||||||
Type::ClassLiteral(class_literal) => {
|
Type::ClassLiteral(class_literal) => {
|
||||||
self.extend_with_class_members(db, ty, class_literal);
|
self.extend_with_class_members(db, ty, class_literal);
|
||||||
|
|
||||||
|
if CodeGeneratorKind::NamedTuple.matches(db, class_literal) {
|
||||||
|
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_class_literal(db));
|
||||||
|
}
|
||||||
|
|
||||||
if let Type::ClassLiteral(meta_class_literal) = ty.to_meta_type(db) {
|
if let Type::ClassLiteral(meta_class_literal) = ty.to_meta_type(db) {
|
||||||
self.extend_with_class_members(db, ty, meta_class_literal);
|
self.extend_with_class_members(db, ty, meta_class_literal);
|
||||||
}
|
}
|
||||||
|
@ -120,12 +133,23 @@ impl<'db> AllMembers<'db> {
|
||||||
|
|
||||||
Type::GenericAlias(generic_alias) => {
|
Type::GenericAlias(generic_alias) => {
|
||||||
let class_literal = generic_alias.origin(db);
|
let class_literal = generic_alias.origin(db);
|
||||||
|
if CodeGeneratorKind::NamedTuple.matches(db, class_literal) {
|
||||||
|
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_class_literal(db));
|
||||||
|
}
|
||||||
self.extend_with_class_members(db, ty, class_literal);
|
self.extend_with_class_members(db, ty, class_literal);
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::SubclassOf(subclass_of_type) => {
|
Type::SubclassOf(subclass_of_type) => {
|
||||||
if let Some(class_literal) = subclass_of_type.subclass_of().into_class() {
|
if let Some(class_type) = subclass_of_type.subclass_of().into_class() {
|
||||||
self.extend_with_class_members(db, ty, class_literal.class_literal(db).0);
|
let class_literal = class_type.class_literal(db).0;
|
||||||
|
self.extend_with_class_members(db, ty, class_literal);
|
||||||
|
|
||||||
|
if CodeGeneratorKind::NamedTuple.matches(db, class_literal) {
|
||||||
|
self.extend_with_type(
|
||||||
|
db,
|
||||||
|
KnownClass::NamedTupleFallback.to_class_literal(db),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue