Refine list of annotatable subscripts (#534)

This commit is contained in:
Charlie Marsh 2022-11-01 16:36:05 -04:00 committed by GitHub
parent e9a4c8ba13
commit 5123b38758
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 188 additions and 107 deletions

View File

@ -16,3 +16,15 @@ import typing
# F821 Undefined name `Model`
x = typing.cast("Model", x)
from typing import Pattern
# F821 Undefined name `Model`
x = Pattern["Model"]
from typing.re import Match
# F821 Undefined name `Model`
x = Match["Model"]

View File

@ -37,7 +37,14 @@ use crate::{
};
const GLOBAL_SCOPE_INDEX: usize = 0;
const TRACK_FROM_IMPORTS: [&str; 1] = ["typing"];
const TRACK_FROM_IMPORTS: [&str; 6] = [
"collections",
"collections.abc",
"contextlib",
"re",
"typing",
"typing.re",
];
pub struct Checker<'a> {
// Input data.
@ -1396,7 +1403,7 @@ where
}
}
ExprKind::Subscript { value, slice, ctx } => {
match typing::match_annotated_subscript(value, self.from_imports.get("typing")) {
match typing::match_annotated_subscript(value, &self.from_imports) {
Some(subscript) => match subscript {
// Ex) Optional[int]
SubscriptKind::AnnotatedSubscript => {

View File

@ -1,100 +1,132 @@
use std::collections::BTreeSet;
use std::collections::{BTreeMap, BTreeSet};
use once_cell::sync::Lazy;
use rustpython_ast::{Expr, ExprKind};
// TODO(charlie): Some of these are actually from `collections`.
// Review: https://peps.python.org/pep-0585/.
static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
BTreeSet::from([
"AbstractAsyncContextManager",
"AbstractContextManager",
"AbstractSet",
// "Annotated",
"AsyncContextManager",
"AsyncGenerator",
"AsyncIterable",
"AsyncIterator",
"Awaitable",
"BinaryIO",
"ByteString",
"Callable",
"ChainMap",
"ClassVar",
"Collection",
"Concatenate",
"Container",
"ContextManager",
"Coroutine",
"Counter",
"Counter",
"DbfilenameShelf",
"DefaultDict",
"Deque",
"Dict",
"Field",
"Final",
"FrozenSet",
"Generator",
"Iterator",
"Generic",
"IO",
"ItemsView",
"Iterable",
"Iterator",
"KeysView",
"LifoQueue",
"List",
"Mapping",
"MappingProxyType",
"MappingView",
"Match",
"MutableMapping",
"MutableSequence",
"MutableSet",
"Optional",
"OrderedDict",
"PathLike",
"Pattern",
"PriorityQueue",
"Protocol",
"Queue",
"Reversible",
"Sequence",
"Set",
"Shelf",
"SimpleQueue",
"TextIO",
"Tuple",
"Type",
"TypeGuard",
"Union",
"ValuesView",
"WeakKeyDictionary",
"WeakMethod",
"WeakSet",
"WeakValueDictionary",
"defaultdict",
"deque",
"dict",
"frozenset",
"list",
"set",
"tuple",
"type",
])
});
// See: https://docs.python.org/3/library/typing.html
static IMPORTED_SUBSCRIPTS: Lazy<BTreeMap<&'static str, BTreeSet<&'static str>>> =
Lazy::new(|| {
let mut import_map = BTreeMap::new();
for (name, module) in [
// `collections`
("ChainMap", "collections"),
("Counter", "collections"),
("OrderedDict", "collections"),
("defaultdict", "collections"),
("deque", "collections"),
// `collections.abc`
("AsyncGenerator", "collections.abc"),
("AsyncIterable", "collections.abc"),
("AsyncIterator", "collections.abc"),
("Awaitable", "collections.abc"),
("ByteString", "collections.abc"),
("Callable", "collections.abc"),
("Collection", "collections.abc"),
("Container", "collections.abc"),
("Coroutine", "collections.abc"),
("Generator", "collections.abc"),
("ItemsView", "collections.abc"),
("Iterable", "collections.abc"),
("Iterator", "collections.abc"),
("KeysView", "collections.abc"),
("Mapping", "collections.abc"),
("MappingView", "collections.abc"),
("MutableMapping", "collections.abc"),
("MutableSequence", "collections.abc"),
("MutableSet", "collections.abc"),
("Reversible", "collections.abc"),
("Sequence", "collections.abc"),
("Set", "collections.abc"),
("ValuesView", "collections.abc"),
// `contextlib`
("AbstractAsyncContextManager", "contextlib"),
("AbstractContextManager", "contextlib"),
// `re`
("Match", "re"),
("Pattern", "re"),
// `typing`
("AbstractSet", "typing"),
("Annotated", "typing"),
("AsyncContextManager", "typing"),
("AsyncContextManager", "typing"),
("AsyncGenerator", "typing"),
("AsyncIterable", "typing"),
("AsyncIterator", "typing"),
("Awaitable", "typing"),
("BinaryIO", "typing"),
("ByteString", "typing"),
("Callable", "typing"),
("ChainMap", "typing"),
("ClassVar", "typing"),
("Collection", "typing"),
("Concatenate", "typing"),
("Container", "typing"),
("ContextManager", "typing"),
("ContextManager", "typing"),
("Coroutine", "typing"),
("Counter", "typing"),
("DefaultDict", "typing"),
("Deque", "typing"),
("Dict", "typing"),
("Final", "typing"),
("FrozenSet", "typing"),
("Generator", "typing"),
("Generic", "typing"),
("IO", "typing"),
("ItemsView", "typing"),
("Iterable", "typing"),
("Iterator", "typing"),
("KeysView", "typing"),
("List", "typing"),
("Mapping", "typing"),
("Match", "typing"),
("MutableMapping", "typing"),
("MutableSequence", "typing"),
("MutableSet", "typing"),
("Optional", "typing"),
("OrderedDict", "typing"),
("Pattern", "typing"),
("Reversible", "typing"),
("Sequence", "typing"),
("Set", "typing"),
("TextIO", "typing"),
("Tuple", "typing"),
("Type", "typing"),
("Type", "typing"),
("TypeGuard", "typing"),
("Union", "typing"),
("Unpack", "typing"),
("ValuesView", "typing"),
// `typing.io`
("BinaryIO", "typing.io"),
("IO", "typing.io"),
("TextIO", "typing.io"),
// `typing.re`
("Match", "typing.re"),
("Pattern", "typing.re"),
// `weakref`
("WeakKeyDictionary", "weakref"),
("WeakSet", "weakref"),
("WeakValueDictionary", "weakref"),
] {
import_map
.entry(name)
.or_insert_with(BTreeSet::new)
.insert(module);
}
import_map
});
// These are all assumed to come from the `typing` module.
// See: https://peps.python.org/pep-0585/
static PEP_585_BUILTINS_ELIGIBLE: Lazy<BTreeSet<&'static str>> =
Lazy::new(|| BTreeSet::from(["Dict", "FrozenSet", "List", "Set", "Tuple", "Type"]));
// These are all assumed to come from the `typing` module.
// See: https://peps.python.org/pep-0585/
static PEP_585_BUILTINS: Lazy<BTreeSet<&'static str>> =
Lazy::new(|| BTreeSet::from(["dict", "frozenset", "list", "set", "tuple", "type"]));
fn is_annotated_subscript(name: &str) -> bool {
ANNOTATED_SUBSCRIPTS.contains(name)
}
fn is_pep593_annotated_subscript(name: &str) -> bool {
name == "Annotated"
}
@ -106,17 +138,23 @@ pub enum SubscriptKind {
pub fn match_annotated_subscript(
expr: &Expr,
imports: Option<&BTreeSet<&str>>,
imports: &BTreeMap<&str, BTreeSet<&str>>,
) -> Option<SubscriptKind> {
match &expr.node {
ExprKind::Attribute { attr, value, .. } => {
if let ExprKind::Name { id, .. } = &value.node {
if id == "typing" {
if is_pep593_annotated_subscript(attr) {
return Some(SubscriptKind::PEP593AnnotatedSubscript);
} else if is_annotated_subscript(attr) {
return Some(SubscriptKind::AnnotatedSubscript);
}
// If `id` is `typing` and `attr` is `Union`, verify that `typing.Union` is an
// annotated subscript.
if IMPORTED_SUBSCRIPTS
.get(&attr.as_str())
.map(|imports| imports.contains(&id.as_str()))
.unwrap_or_default()
{
return if is_pep593_annotated_subscript(attr) {
Some(SubscriptKind::PEP593AnnotatedSubscript)
} else {
Some(SubscriptKind::AnnotatedSubscript)
};
}
}
}
@ -126,14 +164,20 @@ pub fn match_annotated_subscript(
return Some(SubscriptKind::AnnotatedSubscript);
}
if imports
.map(|import| import.contains(&id.as_str()))
.unwrap_or_default()
{
if is_pep593_annotated_subscript(id) {
return Some(SubscriptKind::PEP593AnnotatedSubscript);
} else if is_annotated_subscript(id) {
return Some(SubscriptKind::AnnotatedSubscript);
// Verify that, e.g., `Union` is a reference to `typing.Union`.
if let Some(modules) = IMPORTED_SUBSCRIPTS.get(&id.as_str()) {
for module in modules {
if imports
.get(module)
.map(|imports| imports.contains(&id.as_str()))
.unwrap_or_default()
{
return if is_pep593_annotated_subscript(id) {
Some(SubscriptKind::PEP593AnnotatedSubscript)
} else {
Some(SubscriptKind::AnnotatedSubscript)
};
}
}
}
}
@ -142,8 +186,8 @@ pub fn match_annotated_subscript(
None
}
/// Returns `true` if `Expr` represents a reference to a typing object with a PEP585 built-in.
pub fn is_pep585_builtin(expr: &Expr, imports: Option<&BTreeSet<&str>>) -> bool {
/// Returns `true` if `Expr` represents a reference to a typing object with a PEP 585 built-in.
pub fn is_pep585_builtin(expr: &Expr, typing_imports: Option<&BTreeSet<&str>>) -> bool {
match &expr.node {
ExprKind::Attribute { attr, value, .. } => {
if let ExprKind::Name { id, .. } = &value.node {
@ -153,8 +197,8 @@ pub fn is_pep585_builtin(expr: &Expr, imports: Option<&BTreeSet<&str>>) -> bool
}
}
ExprKind::Name { id, .. } => {
imports
.map(|import| import.contains(&id.as_str()))
typing_imports
.map(|imports| imports.contains(&id.as_str()))
.unwrap_or_default()
&& PEP_585_BUILTINS_ELIGIBLE.contains(&id.as_str())
}

View File

@ -20,4 +20,22 @@ expression: checks
row: 18
column: 23
fix: ~
- kind:
UndefinedName: Model
location:
row: 24
column: 12
end_location:
row: 24
column: 19
fix: ~
- kind:
UndefinedName: Model
location:
row: 30
column: 10
end_location:
row: 30
column: 17
fix: ~