Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a91c1b6

Browse files
Do not use raw strings as class keys (#472)
1 parent d475fc0 commit a91c1b6

28 files changed

Lines changed: 800 additions & 618 deletions

pycroscope/annotations.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@
104104
CallableValue,
105105
CanAssignContext,
106106
CanAssignError,
107+
ClassKey,
108+
ClassOwner,
107109
CustomCheckExtension,
108110
DictIncompleteValue,
109111
Extension,
@@ -150,6 +152,7 @@
150152
Variance,
151153
annotate_value,
152154
bound_self_type_from_class_key,
155+
class_owner_from_key,
153156
get_single_typevartuple_param,
154157
get_typevar_variance,
155158
iter_type_params_in_value,
@@ -202,7 +205,7 @@ def resolve_name(
202205
def invalid_self_annotation_message(self, annotation: ast.AST) -> str | None:
203206
raise NotImplementedError
204207

205-
def get_self_key(self) -> type | str | None:
208+
def get_self_key(self) -> ClassKey | None:
206209
raise NotImplementedError
207210

208211
def get_type_alias_cache(self) -> dict[object, TypeAlias]:
@@ -217,7 +220,7 @@ class Context:
217220
218221
"""
219222

220-
self_key: type | str | None = field(kw_only=True)
223+
self_key: ClassKey | None = field(kw_only=True)
221224
should_suppress_errors: bool = field(default=False, init=False)
222225
"""While this is True, no annotation errors are emitted."""
223226
should_allow_undefined_names: bool = field(default=False, init=False)
@@ -2270,7 +2273,7 @@ def _type_from_subscripted_value(
22702273
synthetic_typ, _canonicalize_generic_args_for_value(typed_members)
22712274
)
22722275

2273-
if isinstance(root, TypedValue) and isinstance(root.typ, str):
2276+
if isinstance(root, TypedValue) and isinstance(root.typ, ClassOwner):
22742277
synthetic_typ = root.typ
22752278
can_assign_ctx = _get_can_assign_context(ctx)
22762279
synthetic_type_params_for_str: Sequence[TypeParam] = ()
@@ -2589,13 +2592,13 @@ def _evaluate_runtime_generic_alias_type_params(
25892592
return AnyValue(AnySource.error)
25902593

25912594

2592-
def _maybe_get_extra(origin: type) -> type | str:
2595+
def _maybe_get_extra(origin: type) -> ClassKey:
25932596
# ContextManager is defined oddly and we lose the Protocol if we don't use
25942597
# synthetic types.
25952598
if any(origin is cls for cls in CONTEXT_MANAGER_TYPES):
2596-
return "contextlib.AbstractContextManager"
2599+
return class_owner_from_key("contextlib.AbstractContextManager")
25972600
elif any(origin is cls for cls in ASYNC_CONTEXT_MANAGER_TYPES):
2598-
return "contextlib.AbstractAsyncContextManager"
2601+
return class_owner_from_key("contextlib.AbstractAsyncContextManager")
25992602
else:
26002603
return origin
26012604

@@ -2627,7 +2630,7 @@ def __init__(
26272630
*,
26282631
visitor: CanAssignContext | None,
26292632
node: ast.AST | None,
2630-
self_key: type | str | None = None,
2633+
self_key: ClassKey | None = None,
26312634
globals: Mapping[str, object] | None = None,
26322635
use_name_node_for_error: bool = False,
26332636
) -> None:
@@ -3241,7 +3244,7 @@ def _value_of_origin_args(
32413244
return AnyValue(AnySource.error)
32423245

32433246

3244-
def _maybe_typed_value(val: type | str) -> Value:
3247+
def _maybe_typed_value(val: ClassKey) -> Value:
32453248
if val is type(None):
32463249
return KnownValue(None)
32473250
elif val is Hashable:
@@ -3448,5 +3451,7 @@ def _make_annotated(origin: Value, metadata: Sequence[Value], ctx: Context) -> V
34483451
}
34493452

34503453

3451-
def is_context_manager_type(typ: str | type) -> bool:
3454+
def is_context_manager_type(typ: ClassKey) -> bool:
3455+
if isinstance(typ, ClassOwner):
3456+
return str(typ) in _CONTEXT_MANAGER_TYPES
34523457
return typ in _CONTEXT_MANAGER_TYPES

pycroscope/arg_spec.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@
7474
AnyValue,
7575
CanAssignContext,
7676
CanAssignError,
77+
ClassKey,
78+
ClassOwner,
7779
Extension,
7880
GenericBases,
7981
GenericValue,
@@ -97,6 +99,7 @@
9799
TypeVarTupleValue,
98100
TypeVarValue,
99101
Value,
102+
class_owner_from_key,
100103
get_namedtuple_field_annotation,
101104
get_self_param,
102105
is_async_iterable,
@@ -108,6 +111,7 @@
108111

109112
_GET_OVERLOADS = []
110113

114+
111115
try:
112116
from typing_extensions import get_overloads
113117
except ImportError:
@@ -615,15 +619,19 @@ def _infer_contextmanager_wrapper_return(
615619
return None
616620
return (
617621
GenericValue(
618-
"contextlib._AsyncGeneratorContextManager", [maybe_iterable]
622+
class_owner_from_key("contextlib._AsyncGeneratorContextManager"),
623+
[maybe_iterable],
619624
),
620625
True,
621626
)
622627
maybe_iterable = is_iterable(wrapped_return, self.ctx)
623628
if isinstance(maybe_iterable, CanAssignError):
624629
return None
625630
return (
626-
GenericValue("contextlib._GeneratorContextManager", [maybe_iterable]),
631+
GenericValue(
632+
class_owner_from_key("contextlib._GeneratorContextManager"),
633+
[maybe_iterable],
634+
),
627635
True,
628636
)
629637

@@ -962,7 +970,7 @@ def _uncached_get_argspec(
962970
FunctionsSafeToCall.contains(obj, self.options)
963971
or (safe_isinstance(obj, type) and safe_issubclass(obj, self.safe_bases))
964972
)
965-
if safe_isinstance(obj, (type, str)):
973+
if safe_isinstance(obj, (type, ClassOwner)):
966974
type_params = self.get_type_parameters(obj)
967975
else:
968976
type_params = []
@@ -1331,7 +1339,7 @@ def _safe_get_signature(self, obj: Any) -> inspect.Signature | None:
13311339
# Python 2.
13321340
return None
13331341

1334-
def get_type_parameters(self, typ: type | str) -> list[TypeParam]:
1342+
def get_type_parameters(self, typ: ClassKey) -> list[TypeParam]:
13351343
try:
13361344
cached = self.type_params_cache[typ]
13371345
except Exception:
@@ -1345,7 +1353,7 @@ def get_type_parameters(self, typ: type | str) -> list[TypeParam]:
13451353
cached = None
13461354
if cached is not None:
13471355
return list(cached)
1348-
if isinstance(typ, str):
1356+
if isinstance(typ, ClassOwner):
13491357
return []
13501358
runtime_type_params = safe_getattr(typ, "__type_params__", ())
13511359
if not runtime_type_params:
@@ -1367,7 +1375,7 @@ def get_type_parameters(self, typ: type | str) -> list[TypeParam]:
13671375

13681376
def get_generic_bases(
13691377
self,
1370-
typ: type | str,
1378+
typ: ClassKey,
13711379
generic_args: Sequence[Value] = (),
13721380
*,
13731381
substitute_typevars: bool = True,
@@ -1597,20 +1605,17 @@ def _default_type_argument_for_param(
15971605
)
15981606
return AnyValue(AnySource.generic_argument)
15991607

1600-
def _get_generic_bases_cached(self, typ: type | str) -> GenericBases:
1608+
def _get_generic_bases_cached(self, typ: ClassKey) -> GenericBases:
16011609
try:
16021610
return self.generic_bases_cache[typ]
16031611
except KeyError:
16041612
pass
16051613
except Exception:
16061614
return {} # We don't support unhashable types.
1607-
if isinstance(typ, str):
1608-
bases = self.ts_finder.get_bases_for_fq_name(typ)
1609-
else:
1610-
bases = self.ts_finder.get_bases(typ)
1615+
bases = self.ts_finder.get_bases(typ)
16111616
generic_bases = self._extract_bases(typ, bases)
16121617
if generic_bases is None:
1613-
if isinstance(typ, str):
1618+
if isinstance(typ, ClassOwner):
16141619
# Synthetic classes may not have typeshed entries.
16151620
generic_bases = {}
16161621
self.generic_bases_cache[typ] = generic_bases
@@ -1637,7 +1642,7 @@ def _type_from_base(self, base: object, owner: type) -> Value:
16371642
return type_from_runtime(base, ctx=AnnotationsContext(self_key=owner))
16381643

16391644
def _extract_bases(
1640-
self, typ: type | str, bases: Sequence[Value] | None
1645+
self, typ: ClassKey, bases: Sequence[Value] | None
16411646
) -> GenericBases | None:
16421647
if bases is None:
16431648
return None
@@ -1661,7 +1666,7 @@ def _extract_bases(
16611666
generic_bases[typ] = self_typevars
16621667
for base in bases:
16631668
if isinstance(base, TypedValue):
1664-
if isinstance(base.typ, str):
1669+
if isinstance(base.typ, ClassOwner):
16651670
assert base.typ != typ, base
16661671
else:
16671672
assert base.typ is not typ, base

pycroscope/attributes.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
AnyValue,
5656
CallableValue,
5757
CanAssignContext,
58+
ClassKey,
59+
ClassOwner,
5860
ClassSymbol,
5961
CustomCheckExtension,
6062
GenericBases,
@@ -153,11 +155,11 @@ def get_can_assign_context(self) -> CanAssignContext:
153155
raise NotImplementedError
154156

155157
def get_generic_bases(
156-
self, typ: type | str, generic_args: Sequence[Value]
158+
self, typ: ClassKey, generic_args: Sequence[Value]
157159
) -> GenericBases:
158160
raise NotImplementedError
159161

160-
def get_synthetic_class(self, typ: type | str) -> SyntheticClassObjectValue | None:
162+
def get_synthetic_class(self, typ: ClassKey) -> SyntheticClassObjectValue | None:
161163
raise NotImplementedError
162164

163165
def clone_for_attribute_lookup(
@@ -278,14 +280,14 @@ def get_attribute(ctx: AttrContext) -> Value:
278280
args = root_value.args
279281
else:
280282
args = ()
281-
if isinstance(root_value.typ, str):
283+
if isinstance(root_value.typ, ClassOwner):
282284
attribute_value = _get_attribute_from_synthetic_typed_value(root_value, ctx)
283285
else:
284286
attribute_value = _get_attribute_from_typed(root_value.typ, args, ctx)
285287
elif isinstance(root_value, SubclassValue):
286-
synthetic_name: str | None = None
288+
synthetic_name: ClassKey | None = None
287289
if isinstance(root_value.typ, TypedValue):
288-
if isinstance(root_value.typ.typ, str):
290+
if isinstance(root_value.typ.typ, ClassOwner):
289291
synthetic_name = root_value.typ.typ
290292
else:
291293
attribute_value = _get_attribute_from_subclass(
@@ -294,7 +296,7 @@ def get_attribute(ctx: AttrContext) -> Value:
294296
elif isinstance(root_value.typ, TypeVarValue):
295297
if root_value.typ.typevar_param.bound is not None:
296298
bound = replace_fallback(root_value.typ.typevar_param.bound)
297-
if isinstance(bound, TypedValue) and isinstance(bound.typ, str):
299+
if isinstance(bound, TypedValue) and isinstance(bound.typ, ClassOwner):
298300
synthetic_name = bound.typ
299301
else:
300302
assert_never(root_value.typ)
@@ -358,7 +360,7 @@ def _maybe_specialize_class_partial_root(root_value: Value, ctx: AttrContext) ->
358360
return root_value
359361
root = replace_fallback(root_value.root)
360362
can_assign_ctx = ctx.get_can_assign_context()
361-
class_key: type | str | None = None
363+
class_key: ClassKey | None = None
362364
if isinstance(root, SyntheticClassObjectValue) and isinstance(
363365
root.class_type, TypedValue
364366
):
@@ -405,7 +407,7 @@ def _specialized_class_partial_member_to_type(
405407
def _get_namedtuple_member_from_sequence_value(
406408
root_value: SequenceValue, ctx: AttrContext
407409
) -> Value | None:
408-
if not isinstance(root_value.typ, str):
410+
if not isinstance(root_value.typ, ClassOwner):
409411
return None
410412
type_object = ctx.get_can_assign_context().make_type_object(root_value.typ)
411413
if not type_object.is_direct_namedtuple():
@@ -444,7 +446,7 @@ def _super_receiver_type_value(value: Value) -> tuple[TypedValue | None, bool]:
444446
return None, False
445447

446448

447-
def _super_thisclass_key(value: Value) -> type | str | None:
449+
def _super_thisclass_key(value: Value) -> ClassKey | None:
448450
value = replace_fallback(value)
449451
if isinstance(value, KnownValue) and isinstance(value.val, type):
450452
return value.val
@@ -651,7 +653,7 @@ def _get_attribute_from_synthetic_typed_value(
651653

652654

653655
def _get_attribute_from_synthetic_class(
654-
class_key: type | str, self_value: Value, ctx: AttrContext
656+
class_key: ClassKey, self_value: Value, ctx: AttrContext
655657
) -> Value:
656658
assert isinstance(self_value, SyntheticClassObjectValue)
657659
can_assign_ctx = ctx.get_can_assign_context()
@@ -914,22 +916,21 @@ def _get_runtime_class_attribute_from_synthetic_class(
914916

915917

916918
def _substitute_typevars(
917-
typ: type | str,
919+
typ: ClassKey,
918920
generic_args: Sequence[Value],
919921
result: Value,
920922
provider: object,
921923
ctx: AttrContext,
922924
) -> Value:
923925
generic_bases = ctx.get_generic_bases(typ, generic_args)
924-
provider_key: type | str | None
925-
if isinstance(provider, (type, str)) and provider in generic_bases:
926-
provider_key = provider
927-
else:
926+
provider_key: ClassKey | None = _normalize_class_key(provider)
927+
if provider_key not in generic_bases:
928928
provider_key = None
929-
if provider_key is None and not isinstance(provider, str):
929+
if provider_key is None and not isinstance(provider, ClassOwner):
930930
origin = get_origin(provider)
931-
if isinstance(origin, (type, str)) and origin in generic_bases:
932-
provider_key = origin
931+
provider_key = _normalize_class_key(origin)
932+
if provider_key not in generic_bases:
933+
provider_key = None
933934
if provider_key is not None:
934935
provider_typevars = generic_bases[provider_key]
935936
substituted_typevars = _typevar_map_from_varlike_pairs(
@@ -954,6 +955,12 @@ def _substitute_typevars(
954955
return result
955956

956957

958+
def _normalize_class_key(value: object) -> ClassKey | None:
959+
if isinstance(value, (type, ClassOwner)):
960+
return value
961+
return None
962+
963+
957964
def _unwrap_value_from_typed(result: Value, typ: type, ctx: AttrContext) -> Value:
958965
if not isinstance(result, KnownValue):
959966
return result

pycroscope/boolability.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
KNOWN_MUTABLE_TYPES,
2020
AnyValue,
2121
BasicType,
22+
ClassOwner,
2223
DictIncompleteValue,
2324
IntersectionValue,
2425
KnownValue,
@@ -193,7 +194,7 @@ def _get_boolability_no_mvv(value: SimpleType) -> Boolability:
193194
f" {value!r}"
194195
)
195196
elif isinstance(value, TypedValue):
196-
if isinstance(value.typ, str):
197+
if isinstance(value.typ, ClassOwner):
197198
return Boolability.boolable # TODO deal with synthetic types
198199
return _get_type_boolability(value.typ)
199200
elif isinstance(value, (TypeFormValue, PredicateValue)):

0 commit comments

Comments
 (0)