Skip to content

Commit d953036

Browse files
committed
Move functions to _apply_generic.
1 parent a56a877 commit d953036

2 files changed

Lines changed: 104 additions & 105 deletions

File tree

typemap/type_eval/_apply_generic.py

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,6 @@ def get_local_defns(
316316
],
317317
]:
318318
from typemap.typing import GenericCallable, Overloaded
319-
from ._eval_operators import _function_type, _function_type_from_sig
320319

321320
annos: dict[str, Any] = {}
322321
dct: dict[str, Any] = {}
@@ -357,8 +356,6 @@ def get_local_defns(
357356
definition_cls = boxed.canonical_cls
358357

359358
def _make_lambda(fn, o, sa, tp, recv_cls, def_cls):
360-
from ._eval_operators import _function_type_from_sig
361-
362359
def lam(*vs):
363360
args = dict(sa)
364361
args.update(
@@ -416,6 +413,110 @@ def lam(*vs):
416413
return annos, dct
417414

418415

416+
def _function_type_from_sig(sig, func_type, *, receiver_type):
417+
from typemap.typing import Param
418+
419+
empty = inspect.Parameter.empty
420+
421+
def _ann(x):
422+
return typing.Any if x is empty else None if x is type(None) else x
423+
424+
specified_receiver = receiver_type
425+
426+
params = []
427+
for i, p in enumerate(sig.parameters.values()):
428+
ann = p.annotation
429+
# Special handling for first argument on methods.
430+
if i == 0 and receiver_type and func_type is not staticmethod:
431+
if ann is empty:
432+
ann = receiver_type
433+
else:
434+
if (
435+
func_type is classmethod
436+
and typing.get_origin(ann) is type
437+
and (receiver_args := typing.get_args(ann))
438+
):
439+
# The annotation for cls in a classmethod should be type[C]
440+
specified_receiver = receiver_args[0]
441+
else:
442+
specified_receiver = ann
443+
444+
quals = []
445+
if p.kind == inspect.Parameter.VAR_POSITIONAL:
446+
quals.append("*")
447+
if p.kind == inspect.Parameter.VAR_KEYWORD:
448+
quals.append("**")
449+
if p.kind == inspect.Parameter.KEYWORD_ONLY:
450+
quals.append("keyword")
451+
if p.kind == inspect.Parameter.POSITIONAL_ONLY:
452+
quals.append("positional")
453+
if p.default is not empty:
454+
quals.append("default")
455+
params.append(
456+
Param[
457+
typing.Literal[p.name],
458+
_ann(ann),
459+
typing.Literal[*quals] if quals else typing.Never,
460+
]
461+
)
462+
463+
ret = _ann(sig.return_annotation)
464+
465+
# TODO: Is doing the tuple for staticmethod/classmethod legit?
466+
# Putting a list in makes it unhashable...
467+
f: typing.Any # type: ignore[annotation-unchecked]
468+
if func_type is staticmethod:
469+
f = staticmethod[tuple[*params], ret]
470+
elif func_type is classmethod:
471+
f = classmethod[specified_receiver, tuple[*params[1:]], ret]
472+
else:
473+
f = typing.Callable[params, ret]
474+
475+
return f
476+
477+
478+
def _function_type(
479+
func, *, receiver_type
480+
) -> type[typing.Callable | classmethod | staticmethod | GenericCallable]:
481+
from typemap.typing import GenericCallable
482+
483+
root = inspect.unwrap(func)
484+
sig = inspect.signature(root)
485+
f = _function_type_from_sig(sig, type(func), receiver_type=receiver_type)
486+
487+
if root.__type_params__:
488+
# Must store a lambda that performs type variable substitution
489+
type_params = root.__type_params__
490+
callable_lambda = _create_generic_callable_lambda(f, type_params)
491+
f = GenericCallable[tuple[*type_params], callable_lambda] # type: ignore[misc,valid-type]
492+
return f
493+
494+
495+
def _create_generic_callable_lambda(
496+
f: typing.Callable | classmethod | staticmethod,
497+
type_params: tuple[typing.TypeVar, ...],
498+
):
499+
if typing.get_origin(f) in (staticmethod, classmethod):
500+
return lambda *vs: substitute(
501+
f, dict(zip(type_params, vs, strict=True))
502+
)
503+
504+
else:
505+
# Callable params are stored as a list
506+
params, ret = typing.get_args(f)
507+
508+
return lambda *vs: typing.Callable[
509+
[
510+
substitute(
511+
p,
512+
dict(zip(type_params, vs, strict=True)),
513+
)
514+
for p in params
515+
],
516+
substitute(ret, dict(zip(type_params, vs, strict=True))),
517+
]
518+
519+
419520
def flatten_class_new_proto(cls: type) -> type:
420521
# This is a hacky version of flatten_class that works by using
421522
# NewProtocol on Members!

typemap/type_eval/_eval_operators.py

Lines changed: 0 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -679,108 +679,6 @@ def _callable_type_to_method(name, typ, ctx):
679679
return head(func)
680680

681681

682-
def _function_type_from_sig(sig, func_type, *, receiver_type):
683-
empty = inspect.Parameter.empty
684-
685-
def _ann(x):
686-
return typing.Any if x is empty else None if x is type(None) else x
687-
688-
specified_receiver = receiver_type
689-
690-
params = []
691-
for i, p in enumerate(sig.parameters.values()):
692-
ann = p.annotation
693-
# Special handling for first argument on methods.
694-
if i == 0 and receiver_type and func_type is not staticmethod:
695-
if ann is empty:
696-
ann = receiver_type
697-
else:
698-
if (
699-
func_type is classmethod
700-
and typing.get_origin(ann) is type
701-
and (receiver_args := typing.get_args(ann))
702-
):
703-
# The annotation for cls in a classmethod should be type[C]
704-
specified_receiver = receiver_args[0]
705-
else:
706-
specified_receiver = ann
707-
708-
quals = []
709-
if p.kind == inspect.Parameter.VAR_POSITIONAL:
710-
quals.append("*")
711-
if p.kind == inspect.Parameter.VAR_KEYWORD:
712-
quals.append("**")
713-
if p.kind == inspect.Parameter.KEYWORD_ONLY:
714-
quals.append("keyword")
715-
if p.kind == inspect.Parameter.POSITIONAL_ONLY:
716-
quals.append("positional")
717-
if p.default is not empty:
718-
quals.append("default")
719-
params.append(
720-
Param[
721-
typing.Literal[p.name],
722-
_ann(ann),
723-
typing.Literal[*quals] if quals else typing.Never,
724-
]
725-
)
726-
727-
ret = _ann(sig.return_annotation)
728-
729-
# TODO: Is doing the tuple for staticmethod/classmethod legit?
730-
# Putting a list in makes it unhashable...
731-
f: typing.Any # type: ignore[annotation-unchecked]
732-
if func_type is staticmethod:
733-
f = staticmethod[tuple[*params], ret]
734-
elif func_type is classmethod:
735-
f = classmethod[specified_receiver, tuple[*params[1:]], ret]
736-
else:
737-
f = typing.Callable[params, ret]
738-
739-
return f
740-
741-
742-
def _function_type(
743-
func, *, receiver_type
744-
) -> type[typing.Callable | classmethod | staticmethod | GenericCallable]:
745-
root = inspect.unwrap(func)
746-
sig = inspect.signature(root)
747-
f = _function_type_from_sig(sig, type(func), receiver_type=receiver_type)
748-
749-
if root.__type_params__:
750-
# Must store a lambda that performs type variable substitution
751-
type_params = root.__type_params__
752-
callable_lambda = _create_generic_callable_lambda(f, type_params)
753-
f = GenericCallable[tuple[*type_params], callable_lambda] # type: ignore[misc,valid-type]
754-
return f
755-
756-
757-
def _create_generic_callable_lambda(
758-
f: typing.Callable | classmethod | staticmethod,
759-
type_params: tuple[typing.TypeVar, ...],
760-
):
761-
if typing.get_origin(f) in (staticmethod, classmethod):
762-
return lambda *vs: _apply_generic.substitute(
763-
f, dict(zip(type_params, vs, strict=True))
764-
)
765-
766-
else:
767-
# Callable params are stored as a list
768-
params, ret = typing.get_args(f)
769-
770-
return lambda *vs: typing.Callable[
771-
[
772-
_apply_generic.substitute(
773-
p,
774-
dict(zip(type_params, vs, strict=True)),
775-
)
776-
for p in params
777-
],
778-
_apply_generic.substitute(
779-
ret, dict(zip(type_params, vs, strict=True))
780-
),
781-
]
782-
783-
784682
def _hint_to_member(n, t, qs, init, d, *, ctx):
785683
return Member[
786684
typing.Literal[n],

0 commit comments

Comments
 (0)