[ty] Todo-types for os.fdopen, NamedTemporaryFile, and Path.open (#20549)

## Summary

This applies the trick that we use for `builtins.open` to similar
functions that have the same problem. The reason is that the problem
would otherwise become even more pronounced once we add understanding of
the implicit type of `self` parameters, because then something like
`(base_path / "test.bin").open("rb")` also leads to a wrong return type
and can result in false positives.

## Test Plan

New Markdown tests
This commit is contained in:
David Peter 2025-09-24 15:43:58 +02:00 committed by GitHub
parent eea87e24e3
commit fcc76bb7b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 240 additions and 100 deletions

View file

@ -162,22 +162,3 @@ def _(x: A | B, y: list[int]):
reveal_type(x) # revealed: B & ~A reveal_type(x) # revealed: B & ~A
reveal_type(isinstance(x, B)) # revealed: Literal[True] reveal_type(isinstance(x, B)) # revealed: Literal[True]
``` ```
## Calls to `open()`
We do not fully understand typeshed's overloads for `open()` yet, due to missing support for PEP-613
type aliases. However, we also do not emit false-positive diagnostics on common calls to `open()`:
```py
import pickle
reveal_type(open("")) # revealed: TextIOWrapper[_WrappedBuffer]
reveal_type(open("", "r")) # revealed: TextIOWrapper[_WrappedBuffer]
reveal_type(open("", "rb")) # revealed: @Todo(`builtins.open` return type)
with open("foo.pickle", "rb") as f:
x = pickle.load(f) # fine
def _(mode: str):
reveal_type(open("", mode)) # revealed: @Todo(`builtins.open` return type)
```

View file

@ -0,0 +1,68 @@
# Calls to `open()`
## `builtins.open`
We do not fully understand typeshed's overloads for `open()` yet, due to missing support for PEP-613
type aliases. However, we also do not emit false-positive diagnostics on common calls to `open()`:
```py
import pickle
reveal_type(open("")) # revealed: TextIOWrapper[_WrappedBuffer]
reveal_type(open("", "r")) # revealed: TextIOWrapper[_WrappedBuffer]
reveal_type(open("", "rb")) # revealed: @Todo(`builtins.open` return type)
with open("foo.pickle", "rb") as f:
x = pickle.load(f) # fine
def _(mode: str):
reveal_type(open("", mode)) # revealed: @Todo(`builtins.open` return type)
```
## `os.fdopen`
The same is true for `os.fdopen()`:
```py
import pickle
import os
reveal_type(os.fdopen(0)) # revealed: TextIOWrapper[_WrappedBuffer]
reveal_type(os.fdopen(0, "r")) # revealed: TextIOWrapper[_WrappedBuffer]
reveal_type(os.fdopen(0, "rb")) # revealed: @Todo(`os.fdopen` return type)
with os.fdopen(0, "rb") as f:
x = pickle.load(f) # fine
```
## `Path.open`
And similarly for `Path.open()`:
```py
from pathlib import Path
import pickle
reveal_type(Path("").open()) # revealed: @Todo(`Path.open` return type)
reveal_type(Path("").open("r")) # revealed: @Todo(`Path.open` return type)
reveal_type(Path("").open("rb")) # revealed: @Todo(`Path.open` return type)
with Path("foo.pickle").open("rb") as f:
x = pickle.load(f) # fine
```
## `NamedTemporaryFile`
And similarly for `tempfile.NamedTemporaryFile()`:
```py
from tempfile import NamedTemporaryFile
import pickle
reveal_type(NamedTemporaryFile()) # revealed: _TemporaryFileWrapper[bytes]
reveal_type(NamedTemporaryFile("r")) # revealed: _TemporaryFileWrapper[str]
reveal_type(NamedTemporaryFile("rb")) # revealed: @Todo(`tempfile.NamedTemporaryFile` return type)
with NamedTemporaryFile("rb") as f:
x = pickle.load(f) # fine
```

View file

@ -318,6 +318,9 @@ pub enum KnownModule {
TypingExtensions, TypingExtensions,
Typing, Typing,
Sys, Sys,
Os,
Tempfile,
Pathlib,
Abc, Abc,
Dataclasses, Dataclasses,
Collections, Collections,
@ -347,6 +350,9 @@ impl KnownModule {
Self::Typeshed => "_typeshed", Self::Typeshed => "_typeshed",
Self::TypingExtensions => "typing_extensions", Self::TypingExtensions => "typing_extensions",
Self::Sys => "sys", Self::Sys => "sys",
Self::Os => "os",
Self::Tempfile => "tempfile",
Self::Pathlib => "pathlib",
Self::Abc => "abc", Self::Abc => "abc",
Self::Dataclasses => "dataclasses", Self::Dataclasses => "dataclasses",
Self::Collections => "collections", Self::Collections => "collections",

View file

@ -3447,6 +3447,11 @@ impl<'db> Type<'db> {
Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(literal)), Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(literal)),
) )
.into(), .into(),
Type::NominalInstance(instance)
if instance.has_known_class(db, KnownClass::Path) && name == "open" =>
{
Place::bound(Type::KnownBoundMethod(KnownBoundMethodType::PathOpen)).into()
}
Type::ClassLiteral(class) Type::ClassLiteral(class)
if name == "__get__" && class.is_known(db, KnownClass::FunctionType) => if name == "__get__" && class.is_known(db, KnownClass::FunctionType) =>
@ -6209,7 +6214,7 @@ impl<'db> Type<'db> {
| Type::AlwaysTruthy | Type::AlwaysTruthy
| Type::AlwaysFalsy | Type::AlwaysFalsy
| Type::WrapperDescriptor(_) | Type::WrapperDescriptor(_)
| Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(_)) | Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(_) | KnownBoundMethodType::PathOpen)
| Type::DataclassDecorator(_) | Type::DataclassDecorator(_)
| Type::DataclassTransformer(_) | Type::DataclassTransformer(_)
// A non-generic class never needs to be specialized. A generic class is specialized // A non-generic class never needs to be specialized. A generic class is specialized
@ -6354,7 +6359,9 @@ impl<'db> Type<'db> {
| Type::AlwaysTruthy | Type::AlwaysTruthy
| Type::AlwaysFalsy | Type::AlwaysFalsy
| Type::WrapperDescriptor(_) | Type::WrapperDescriptor(_)
| Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(_)) | Type::KnownBoundMethod(
KnownBoundMethodType::StrStartswith(_) | KnownBoundMethodType::PathOpen,
)
| Type::DataclassDecorator(_) | Type::DataclassDecorator(_)
| Type::DataclassTransformer(_) | Type::DataclassTransformer(_)
| Type::ModuleLiteral(_) | Type::ModuleLiteral(_)
@ -9435,6 +9442,8 @@ pub enum KnownBoundMethodType<'db> {
/// this allows us to understand statically known branches for common tests such as /// this allows us to understand statically known branches for common tests such as
/// `if sys.platform.startswith("freebsd")`. /// `if sys.platform.startswith("freebsd")`.
StrStartswith(StringLiteralType<'db>), StrStartswith(StringLiteralType<'db>),
/// Method wrapper for `Path.open`,
PathOpen,
} }
pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@ -9458,6 +9467,7 @@ pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Size
KnownBoundMethodType::StrStartswith(string_literal) => { KnownBoundMethodType::StrStartswith(string_literal) => {
visitor.visit_type(db, Type::StringLiteral(string_literal)); visitor.visit_type(db, Type::StringLiteral(string_literal));
} }
KnownBoundMethodType::PathOpen => {}
} }
} }
@ -9493,17 +9503,23 @@ impl<'db> KnownBoundMethodType<'db> {
ConstraintSet::from(self == other) ConstraintSet::from(self == other)
} }
(KnownBoundMethodType::PathOpen, KnownBoundMethodType::PathOpen) => {
ConstraintSet::from(true)
}
( (
KnownBoundMethodType::FunctionTypeDunderGet(_) KnownBoundMethodType::FunctionTypeDunderGet(_)
| KnownBoundMethodType::FunctionTypeDunderCall(_) | KnownBoundMethodType::FunctionTypeDunderCall(_)
| KnownBoundMethodType::PropertyDunderGet(_) | KnownBoundMethodType::PropertyDunderGet(_)
| KnownBoundMethodType::PropertyDunderSet(_) | KnownBoundMethodType::PropertyDunderSet(_)
| KnownBoundMethodType::StrStartswith(_), | KnownBoundMethodType::StrStartswith(_)
| KnownBoundMethodType::PathOpen,
KnownBoundMethodType::FunctionTypeDunderGet(_) KnownBoundMethodType::FunctionTypeDunderGet(_)
| KnownBoundMethodType::FunctionTypeDunderCall(_) | KnownBoundMethodType::FunctionTypeDunderCall(_)
| KnownBoundMethodType::PropertyDunderGet(_) | KnownBoundMethodType::PropertyDunderGet(_)
| KnownBoundMethodType::PropertyDunderSet(_) | KnownBoundMethodType::PropertyDunderSet(_)
| KnownBoundMethodType::StrStartswith(_), | KnownBoundMethodType::StrStartswith(_)
| KnownBoundMethodType::PathOpen,
) => ConstraintSet::from(false), ) => ConstraintSet::from(false),
} }
} }
@ -9538,17 +9554,23 @@ impl<'db> KnownBoundMethodType<'db> {
ConstraintSet::from(self == other) ConstraintSet::from(self == other)
} }
(KnownBoundMethodType::PathOpen, KnownBoundMethodType::PathOpen) => {
ConstraintSet::from(true)
}
( (
KnownBoundMethodType::FunctionTypeDunderGet(_) KnownBoundMethodType::FunctionTypeDunderGet(_)
| KnownBoundMethodType::FunctionTypeDunderCall(_) | KnownBoundMethodType::FunctionTypeDunderCall(_)
| KnownBoundMethodType::PropertyDunderGet(_) | KnownBoundMethodType::PropertyDunderGet(_)
| KnownBoundMethodType::PropertyDunderSet(_) | KnownBoundMethodType::PropertyDunderSet(_)
| KnownBoundMethodType::StrStartswith(_), | KnownBoundMethodType::StrStartswith(_)
| KnownBoundMethodType::PathOpen,
KnownBoundMethodType::FunctionTypeDunderGet(_) KnownBoundMethodType::FunctionTypeDunderGet(_)
| KnownBoundMethodType::FunctionTypeDunderCall(_) | KnownBoundMethodType::FunctionTypeDunderCall(_)
| KnownBoundMethodType::PropertyDunderGet(_) | KnownBoundMethodType::PropertyDunderGet(_)
| KnownBoundMethodType::PropertyDunderSet(_) | KnownBoundMethodType::PropertyDunderSet(_)
| KnownBoundMethodType::StrStartswith(_), | KnownBoundMethodType::StrStartswith(_)
| KnownBoundMethodType::PathOpen,
) => ConstraintSet::from(false), ) => ConstraintSet::from(false),
} }
} }
@ -9567,7 +9589,7 @@ impl<'db> KnownBoundMethodType<'db> {
KnownBoundMethodType::PropertyDunderSet(property) => { KnownBoundMethodType::PropertyDunderSet(property) => {
KnownBoundMethodType::PropertyDunderSet(property.normalized_impl(db, visitor)) KnownBoundMethodType::PropertyDunderSet(property.normalized_impl(db, visitor))
} }
KnownBoundMethodType::StrStartswith(_) => self, KnownBoundMethodType::StrStartswith(_) | KnownBoundMethodType::PathOpen => self,
} }
} }
@ -9579,6 +9601,7 @@ impl<'db> KnownBoundMethodType<'db> {
| KnownBoundMethodType::PropertyDunderGet(_) | KnownBoundMethodType::PropertyDunderGet(_)
| KnownBoundMethodType::PropertyDunderSet(_) => KnownClass::MethodWrapperType, | KnownBoundMethodType::PropertyDunderSet(_) => KnownClass::MethodWrapperType,
KnownBoundMethodType::StrStartswith(_) => KnownClass::BuiltinFunctionType, KnownBoundMethodType::StrStartswith(_) => KnownClass::BuiltinFunctionType,
KnownBoundMethodType::PathOpen => KnownClass::MethodType,
} }
} }
@ -9675,6 +9698,9 @@ impl<'db> KnownBoundMethodType<'db> {
Some(KnownClass::Bool.to_instance(db)), Some(KnownClass::Bool.to_instance(db)),
))) )))
} }
KnownBoundMethodType::PathOpen => {
Either::Right(std::iter::once(Signature::todo("`Path.open` return type")))
}
} }
} }
} }

View file

@ -3721,6 +3721,8 @@ pub enum KnownClass {
TypedDictFallback, TypedDictFallback,
// string.templatelib // string.templatelib
Template, Template,
// pathlib
Path,
// ty_extensions // ty_extensions
ConstraintSet, ConstraintSet,
} }
@ -3767,7 +3769,8 @@ impl KnownClass {
| Self::MethodWrapperType | Self::MethodWrapperType
| Self::CoroutineType | Self::CoroutineType
| Self::BuiltinFunctionType | Self::BuiltinFunctionType
| Self::Template => Some(Truthiness::AlwaysTrue), | Self::Template
| Self::Path => Some(Truthiness::AlwaysTrue),
Self::NoneType => Some(Truthiness::AlwaysFalse), Self::NoneType => Some(Truthiness::AlwaysFalse),
@ -3909,7 +3912,8 @@ impl KnownClass {
| KnownClass::TypedDictFallback | KnownClass::TypedDictFallback
| KnownClass::BuiltinFunctionType | KnownClass::BuiltinFunctionType
| KnownClass::ProtocolMeta | KnownClass::ProtocolMeta
| KnownClass::Template => false, | KnownClass::Template
| KnownClass::Path => false,
} }
} }
@ -3990,7 +3994,8 @@ impl KnownClass {
| KnownClass::TypedDictFallback | KnownClass::TypedDictFallback
| KnownClass::BuiltinFunctionType | KnownClass::BuiltinFunctionType
| KnownClass::ProtocolMeta | KnownClass::ProtocolMeta
| KnownClass::Template => false, | KnownClass::Template
| KnownClass::Path => false,
} }
} }
@ -4070,7 +4075,8 @@ impl KnownClass {
| KnownClass::ConstraintSet | KnownClass::ConstraintSet
| KnownClass::BuiltinFunctionType | KnownClass::BuiltinFunctionType
| KnownClass::ProtocolMeta | KnownClass::ProtocolMeta
| KnownClass::Template => false, | KnownClass::Template
| KnownClass::Path => false,
} }
} }
@ -4163,7 +4169,8 @@ impl KnownClass {
| Self::TypedDictFallback | Self::TypedDictFallback
| Self::BuiltinFunctionType | Self::BuiltinFunctionType
| Self::ProtocolMeta | Self::ProtocolMeta
| Self::Template => false, | Self::Template
| KnownClass::Path => false,
} }
} }
@ -4263,6 +4270,7 @@ impl KnownClass {
Self::ConstraintSet => "ConstraintSet", Self::ConstraintSet => "ConstraintSet",
Self::TypedDictFallback => "TypedDictFallback", Self::TypedDictFallback => "TypedDictFallback",
Self::Template => "Template", Self::Template => "Template",
Self::Path => "Path",
Self::ProtocolMeta => "_ProtocolMeta", Self::ProtocolMeta => "_ProtocolMeta",
} }
} }
@ -4534,6 +4542,7 @@ impl KnownClass {
Self::NamedTupleFallback | Self::TypedDictFallback => KnownModule::TypeCheckerInternals, Self::NamedTupleFallback | Self::TypedDictFallback => KnownModule::TypeCheckerInternals,
Self::NamedTupleLike | Self::ConstraintSet => KnownModule::TyExtensions, Self::NamedTupleLike | Self::ConstraintSet => KnownModule::TyExtensions,
Self::Template => KnownModule::Templatelib, Self::Template => KnownModule::Templatelib,
Self::Path => KnownModule::Pathlib,
} }
} }
@ -4616,7 +4625,8 @@ impl KnownClass {
| Self::TypedDictFallback | Self::TypedDictFallback
| Self::BuiltinFunctionType | Self::BuiltinFunctionType
| Self::ProtocolMeta | Self::ProtocolMeta
| Self::Template => Some(false), | Self::Template
| Self::Path => Some(false),
Self::Tuple => None, Self::Tuple => None,
} }
@ -4702,7 +4712,8 @@ impl KnownClass {
| Self::TypedDictFallback | Self::TypedDictFallback
| Self::BuiltinFunctionType | Self::BuiltinFunctionType
| Self::ProtocolMeta | Self::ProtocolMeta
| Self::Template => false, | Self::Template
| Self::Path => false,
} }
} }
@ -4798,6 +4809,7 @@ impl KnownClass {
"ConstraintSet" => Self::ConstraintSet, "ConstraintSet" => Self::ConstraintSet,
"TypedDictFallback" => Self::TypedDictFallback, "TypedDictFallback" => Self::TypedDictFallback,
"Template" => Self::Template, "Template" => Self::Template,
"Path" => Self::Path,
"_ProtocolMeta" => Self::ProtocolMeta, "_ProtocolMeta" => Self::ProtocolMeta,
_ => return None, _ => return None,
}; };
@ -4869,7 +4881,8 @@ impl KnownClass {
| Self::ConstraintSet | Self::ConstraintSet
| Self::Awaitable | Self::Awaitable
| Self::Generator | Self::Generator
| Self::Template => module == self.canonical_module(db), | Self::Template
| Self::Path => module == self.canonical_module(db),
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types), Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
Self::SpecialForm Self::SpecialForm
| Self::TypeVar | Self::TypeVar

View file

@ -387,6 +387,9 @@ impl Display for DisplayRepresentation<'_> {
Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(_)) => { Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(_)) => {
f.write_str("<method-wrapper `startswith` of `str` object>") f.write_str("<method-wrapper `startswith` of `str` object>")
} }
Type::KnownBoundMethod(KnownBoundMethodType::PathOpen) => {
f.write_str("bound method `Path.open`")
}
Type::WrapperDescriptor(kind) => { Type::WrapperDescriptor(kind) => {
let (method, object) = match kind { let (method, object) = match kind {
WrapperDescriptorKind::FunctionTypeDunderGet => ("__get__", "function"), WrapperDescriptorKind::FunctionTypeDunderGet => ("__get__", "function"),

View file

@ -1120,6 +1120,70 @@ fn is_instance_truthiness<'db>(
} }
} }
/// Return true, if the type passed as `mode` would require us to pick a non-trivial overload of
/// `builtins.open` / `os.fdopen` / `Path.open`.
fn is_mode_with_nontrivial_return_type<'db>(db: &'db dyn Db, mode: Type<'db>) -> bool {
// Return true for any mode that doesn't match typeshed's
// `OpenTextMode` type alias (<https://github.com/python/typeshed/blob/6937a9b193bfc2f0696452d58aad96d7627aa29a/stdlib/_typeshed/__init__.pyi#L220>).
mode.into_string_literal().is_none_or(|mode| {
!matches!(
mode.value(db),
"r+" | "+r"
| "rt+"
| "r+t"
| "+rt"
| "tr+"
| "t+r"
| "+tr"
| "w+"
| "+w"
| "wt+"
| "w+t"
| "+wt"
| "tw+"
| "t+w"
| "+tw"
| "a+"
| "+a"
| "at+"
| "a+t"
| "+at"
| "ta+"
| "t+a"
| "+ta"
| "x+"
| "+x"
| "xt+"
| "x+t"
| "+xt"
| "tx+"
| "t+x"
| "+tx"
| "w"
| "wt"
| "tw"
| "a"
| "at"
| "ta"
| "x"
| "xt"
| "tx"
| "r"
| "rt"
| "tr"
| "U"
| "rU"
| "Ur"
| "rtU"
| "rUt"
| "Urt"
| "trU"
| "tUr"
| "Utr"
)
})
}
fn signature_cycle_recover<'db>( fn signature_cycle_recover<'db>(
_db: &'db dyn Db, _db: &'db dyn Db,
_value: &CallableSignature<'db>, _value: &CallableSignature<'db>,
@ -1188,8 +1252,16 @@ pub enum KnownFunction {
DunderImport, DunderImport,
/// `importlib.import_module`, which returns the submodule. /// `importlib.import_module`, which returns the submodule.
ImportModule, ImportModule,
/// `builtins.open`
Open, Open,
/// `os.fdopen`
Fdopen,
/// `tempfile.NamedTemporaryFile`
#[strum(serialize = "NamedTemporaryFile")]
NamedTemporaryFile,
/// `typing(_extensions).final` /// `typing(_extensions).final`
Final, Final,
/// `typing(_extensions).disjoint_base` /// `typing(_extensions).disjoint_base`
@ -1308,6 +1380,12 @@ impl KnownFunction {
Self::AbstractMethod => { Self::AbstractMethod => {
matches!(module, KnownModule::Abc) matches!(module, KnownModule::Abc)
} }
Self::Fdopen => {
matches!(module, KnownModule::Os)
}
Self::NamedTemporaryFile => {
matches!(module, KnownModule::Tempfile)
}
Self::Dataclass | Self::Field => { Self::Dataclass | Self::Field => {
matches!(module, KnownModule::Dataclasses) matches!(module, KnownModule::Dataclasses)
} }
@ -1656,72 +1734,33 @@ impl KnownFunction {
} }
KnownFunction::Open => { KnownFunction::Open => {
// Temporary special-casing for `builtins.open` to avoid an excessive number of false positives // TODO: Temporary special-casing for `builtins.open` to avoid an excessive number of
// in lieu of proper support for PEP-614 type aliases. // false positives in lieu of proper support for PEP-613 type aliases.
if let [_, Some(mode), ..] = parameter_types { if let [_, Some(mode), ..] = parameter_types
// Infer `Todo` for any argument that doesn't match typeshed's && is_mode_with_nontrivial_return_type(db, *mode)
// `OpenTextMode` type alias (<https://github.com/python/typeshed/blob/6937a9b193bfc2f0696452d58aad96d7627aa29a/stdlib/_typeshed/__init__.pyi#L220>). {
// Without this special-casing, we'd just always select the first overload in our current state, overload.set_return_type(todo_type!("`builtins.open` return type"));
// which leads to lots of false positives. }
if mode.into_string_literal().is_none_or(|mode| { }
!matches!(
mode.value(db), KnownFunction::Fdopen => {
"r+" | "+r" // TODO: Temporary special-casing for `os.fdopen` to avoid an excessive number of
| "rt+" // false positives in lieu of proper support for PEP-613 type aliases.
| "r+t" if let [_, Some(mode), ..] = parameter_types
| "+rt" && is_mode_with_nontrivial_return_type(db, *mode)
| "tr+" {
| "t+r" overload.set_return_type(todo_type!("`os.fdopen` return type"));
| "+tr" }
| "w+" }
| "+w"
| "wt+" KnownFunction::NamedTemporaryFile => {
| "w+t" // TODO: Temporary special-casing for `tempfile.NamedTemporaryFile` to avoid an excessive number of
| "+wt" // false positives in lieu of proper support for PEP-613 type aliases.
| "tw+" if let [Some(mode), ..] = parameter_types
| "t+w" && is_mode_with_nontrivial_return_type(db, *mode)
| "+tw" {
| "a+" overload
| "+a" .set_return_type(todo_type!("`tempfile.NamedTemporaryFile` return type"));
| "at+"
| "a+t"
| "+at"
| "ta+"
| "t+a"
| "+ta"
| "x+"
| "+x"
| "xt+"
| "x+t"
| "+xt"
| "tx+"
| "t+x"
| "+tx"
| "w"
| "wt"
| "tw"
| "a"
| "at"
| "ta"
| "x"
| "xt"
| "tx"
| "r"
| "rt"
| "tr"
| "U"
| "rU"
| "Ur"
| "rtU"
| "rUt"
| "Urt"
| "trU"
| "tUr"
| "Utr"
)
}) {
overload.set_return_type(todo_type!("`builtins.open` return type"));
}
} }
} }
@ -1756,6 +1795,10 @@ pub(crate) mod tests {
KnownFunction::AbstractMethod => KnownModule::Abc, KnownFunction::AbstractMethod => KnownModule::Abc,
KnownFunction::Fdopen => KnownModule::Os,
KnownFunction::NamedTemporaryFile => KnownModule::Tempfile,
KnownFunction::Dataclass | KnownFunction::Field => KnownModule::Dataclasses, KnownFunction::Dataclass | KnownFunction::Field => KnownModule::Dataclasses,
KnownFunction::GetattrStatic => KnownModule::Inspect, KnownFunction::GetattrStatic => KnownModule::Inspect,