Skip to content

Commit ec149da

Browse files
authored
[mypyc] Fix glue methods with native int types (#13939)
This fixes handling of bitmap arguments used to track default values of arguments with native int types in method overrides. The main use case is a method override that adds a native int argument with a default value to the base class signature. Also generate an error if we don't support generating a glue method for an override. This can happen if the positions of bits in the bitmap would change in the subclass. We also don't support switching between error values and default value bitmaps in glue methods. These could be supported, but it would be complicated and doesn't seem worth it. Work on mypyc/mypyc#837.
1 parent cf705d7 commit ec149da

File tree

7 files changed

+266
-13
lines changed

7 files changed

+266
-13
lines changed

mypyc/ir/func_ir.py

+8
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ class FuncSignature:
7070
def __init__(self, args: Sequence[RuntimeArg], ret_type: RType) -> None:
7171
self.args = tuple(args)
7272
self.ret_type = ret_type
73+
# Bitmap arguments are use to mark default values for arguments that
74+
# have types with overlapping error values.
7375
self.num_bitmap_args = num_bitmap_args(self.args)
7476
if self.num_bitmap_args:
7577
extra = [
@@ -78,6 +80,12 @@ def __init__(self, args: Sequence[RuntimeArg], ret_type: RType) -> None:
7880
]
7981
self.args = self.args + tuple(reversed(extra))
8082

83+
def real_args(self) -> tuple[RuntimeArg, ...]:
84+
"""Return arguments without any synthetic bitmap arguments."""
85+
if self.num_bitmap_args:
86+
return self.args[: -self.num_bitmap_args]
87+
return self.args
88+
8189
def bound_sig(self) -> "FuncSignature":
8290
if self.num_bitmap_args:
8391
return FuncSignature(self.args[1 : -self.num_bitmap_args], self.ret_type)

mypyc/irbuild/function.py

+52-10
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
from mypyc.primitives.generic_ops import py_setattr_op
9090
from mypyc.primitives.misc_ops import register_function
9191
from mypyc.primitives.registry import builtin_names
92-
from mypyc.sametype import is_same_method_signature
92+
from mypyc.sametype import is_same_method_signature, is_same_type
9393

9494
# Top-level transform functions
9595

@@ -548,7 +548,7 @@ def is_decorated(builder: IRBuilder, fdef: FuncDef) -> bool:
548548

549549
def gen_glue(
550550
builder: IRBuilder,
551-
sig: FuncSignature,
551+
base_sig: FuncSignature,
552552
target: FuncIR,
553553
cls: ClassIR,
554554
base: ClassIR,
@@ -566,9 +566,9 @@ def gen_glue(
566566
"shadow" glue methods that work with interpreted subclasses.
567567
"""
568568
if fdef.is_property:
569-
return gen_glue_property(builder, sig, target, cls, base, fdef.line, do_py_ops)
569+
return gen_glue_property(builder, base_sig, target, cls, base, fdef.line, do_py_ops)
570570
else:
571-
return gen_glue_method(builder, sig, target, cls, base, fdef.line, do_py_ops)
571+
return gen_glue_method(builder, base_sig, target, cls, base, fdef.line, do_py_ops)
572572

573573

574574
class ArgInfo(NamedTuple):
@@ -594,7 +594,7 @@ def get_args(builder: IRBuilder, rt_args: Sequence[RuntimeArg], line: int) -> Ar
594594

595595
def gen_glue_method(
596596
builder: IRBuilder,
597-
sig: FuncSignature,
597+
base_sig: FuncSignature,
598598
target: FuncIR,
599599
cls: ClassIR,
600600
base: ClassIR,
@@ -626,16 +626,25 @@ def f(builder: IRBuilder, x: object) -> int: ...
626626
If do_pycall is True, then make the call using the C API
627627
instead of a native call.
628628
"""
629+
check_native_override(builder, base_sig, target.decl.sig, line)
630+
629631
builder.enter()
630-
builder.ret_types[-1] = sig.ret_type
632+
builder.ret_types[-1] = base_sig.ret_type
631633

632-
rt_args = list(sig.args)
634+
rt_args = list(base_sig.args)
633635
if target.decl.kind == FUNC_NORMAL:
634-
rt_args[0] = RuntimeArg(sig.args[0].name, RInstance(cls))
636+
rt_args[0] = RuntimeArg(base_sig.args[0].name, RInstance(cls))
635637

636638
arg_info = get_args(builder, rt_args, line)
637639
args, arg_kinds, arg_names = arg_info.args, arg_info.arg_kinds, arg_info.arg_names
638640

641+
bitmap_args = None
642+
if base_sig.num_bitmap_args:
643+
args = args[: -base_sig.num_bitmap_args]
644+
arg_kinds = arg_kinds[: -base_sig.num_bitmap_args]
645+
arg_names = arg_names[: -base_sig.num_bitmap_args]
646+
bitmap_args = builder.builder.args[-base_sig.num_bitmap_args :]
647+
639648
# We can do a passthrough *args/**kwargs with a native call, but if the
640649
# args need to get distributed out to arguments, we just let python handle it
641650
if any(kind.is_star() for kind in arg_kinds) and any(
@@ -655,11 +664,15 @@ def f(builder: IRBuilder, x: object) -> int: ...
655664
first, target.name, args[st:], line, arg_kinds[st:], arg_names[st:]
656665
)
657666
else:
658-
retval = builder.builder.call(target.decl, args, arg_kinds, arg_names, line)
659-
retval = builder.coerce(retval, sig.ret_type, line)
667+
retval = builder.builder.call(
668+
target.decl, args, arg_kinds, arg_names, line, bitmap_args=bitmap_args
669+
)
670+
retval = builder.coerce(retval, base_sig.ret_type, line)
660671
builder.add(Return(retval))
661672

662673
arg_regs, _, blocks, ret_type, _ = builder.leave()
674+
if base_sig.num_bitmap_args:
675+
rt_args = rt_args[: -base_sig.num_bitmap_args]
663676
return FuncIR(
664677
FuncDecl(
665678
target.name + "__" + base.name + "_glue",
@@ -673,6 +686,35 @@ def f(builder: IRBuilder, x: object) -> int: ...
673686
)
674687

675688

689+
def check_native_override(
690+
builder: IRBuilder, base_sig: FuncSignature, sub_sig: FuncSignature, line: int
691+
) -> None:
692+
"""Report an error if an override changes signature in unsupported ways.
693+
694+
Glue methods can work around many signature changes but not all of them.
695+
"""
696+
for base_arg, sub_arg in zip(base_sig.real_args(), sub_sig.real_args()):
697+
if base_arg.type.error_overlap:
698+
if not base_arg.optional and sub_arg.optional and base_sig.num_bitmap_args:
699+
# This would change the meanings of bits in the argument defaults
700+
# bitmap, which we don't support. We'd need to do tricky bit
701+
# manipulations to support this generally.
702+
builder.error(
703+
"An argument with type "
704+
+ f'"{base_arg.type}" cannot be given a default value in a method override',
705+
line,
706+
)
707+
if base_arg.type.error_overlap or sub_arg.type.error_overlap:
708+
if not is_same_type(base_arg.type, sub_arg.type):
709+
# This would change from signaling a default via an error value to
710+
# signaling a default via bitmap, which we don't support.
711+
builder.error(
712+
"Incompatible argument type "
713+
+ f'"{sub_arg.type}" (base class has type "{base_arg.type}")',
714+
line,
715+
)
716+
717+
676718
def gen_glue_property(
677719
builder: IRBuilder,
678720
sig: FuncSignature,

mypyc/irbuild/ll_builder.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -935,10 +935,19 @@ def call(
935935
arg_kinds: list[ArgKind],
936936
arg_names: Sequence[str | None],
937937
line: int,
938+
*,
939+
bitmap_args: list[Register] | None = None,
938940
) -> Value:
939-
"""Call a native function."""
941+
"""Call a native function.
942+
943+
If bitmap_args is given, they override the values of (some) of the bitmap
944+
arguments used to track the presence of values for certain arguments. By
945+
default, the values of the bitmap arguments are inferred from args.
946+
"""
940947
# Normalize args to positionals.
941-
args = self.native_args_to_positional(args, arg_kinds, arg_names, decl.sig, line)
948+
args = self.native_args_to_positional(
949+
args, arg_kinds, arg_names, decl.sig, line, bitmap_args=bitmap_args
950+
)
942951
return self.add(Call(decl, args, line))
943952

944953
def native_args_to_positional(
@@ -948,6 +957,8 @@ def native_args_to_positional(
948957
arg_names: Sequence[str | None],
949958
sig: FuncSignature,
950959
line: int,
960+
*,
961+
bitmap_args: list[Register] | None = None,
951962
) -> list[Value]:
952963
"""Prepare arguments for a native call.
953964
@@ -1015,6 +1026,11 @@ def native_args_to_positional(
10151026
output_args.append(output_arg)
10161027

10171028
for i in reversed(range(n)):
1029+
if bitmap_args and i < len(bitmap_args):
1030+
# Use override provided by caller
1031+
output_args.append(bitmap_args[i])
1032+
continue
1033+
# Infer values of bitmap args
10181034
bitmap = 0
10191035
c = 0
10201036
for lst, arg in zip(formal_to_actual, sig_args):

mypyc/sametype.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def is_same_method_signature(a: FuncSignature, b: FuncSignature) -> bool:
3535
len(a.args) == len(b.args)
3636
and is_same_type(a.ret_type, b.ret_type)
3737
and all(
38-
is_same_type(t1.type, t2.type) and t1.name == t2.name
38+
is_same_type(t1.type, t2.type) and t1.name == t2.name and t1.optional == t2.optional
3939
for t1, t2 in zip(a.args[1:], b.args[1:])
4040
)
4141
)

mypyc/test-data/irbuild-glue-methods.test

+108
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,111 @@ L0:
327327
r0 = __mypyc_self__.boxed
328328
r1 = box(int, r0)
329329
return r1
330+
331+
[case testI64GlueWithExtraDefaultArg]
332+
from mypy_extensions import i64
333+
334+
class C:
335+
def f(self) -> None: pass
336+
337+
class D(C):
338+
def f(self, x: i64 = 44) -> None: pass
339+
[out]
340+
def C.f(self):
341+
self :: __main__.C
342+
L0:
343+
return 1
344+
def D.f(self, x, __bitmap):
345+
self :: __main__.D
346+
x :: int64
347+
__bitmap, r0 :: uint32
348+
r1 :: bit
349+
L0:
350+
r0 = __bitmap & 1
351+
r1 = r0 == 0
352+
if r1 goto L1 else goto L2 :: bool
353+
L1:
354+
x = 44
355+
L2:
356+
return 1
357+
def D.f__C_glue(self):
358+
self :: __main__.D
359+
r0 :: None
360+
L0:
361+
r0 = D.f(self, 0, 0)
362+
return r0
363+
364+
[case testI64GlueWithSecondDefaultArg]
365+
from mypy_extensions import i64
366+
367+
class C:
368+
def f(self, x: i64 = 11) -> None: pass
369+
class D(C):
370+
def f(self, x: i64 = 12, y: i64 = 13) -> None: pass
371+
[out]
372+
def C.f(self, x, __bitmap):
373+
self :: __main__.C
374+
x :: int64
375+
__bitmap, r0 :: uint32
376+
r1 :: bit
377+
L0:
378+
r0 = __bitmap & 1
379+
r1 = r0 == 0
380+
if r1 goto L1 else goto L2 :: bool
381+
L1:
382+
x = 11
383+
L2:
384+
return 1
385+
def D.f(self, x, y, __bitmap):
386+
self :: __main__.D
387+
x, y :: int64
388+
__bitmap, r0 :: uint32
389+
r1 :: bit
390+
r2 :: uint32
391+
r3 :: bit
392+
L0:
393+
r0 = __bitmap & 1
394+
r1 = r0 == 0
395+
if r1 goto L1 else goto L2 :: bool
396+
L1:
397+
x = 12
398+
L2:
399+
r2 = __bitmap & 2
400+
r3 = r2 == 0
401+
if r3 goto L3 else goto L4 :: bool
402+
L3:
403+
y = 13
404+
L4:
405+
return 1
406+
def D.f__C_glue(self, x, __bitmap):
407+
self :: __main__.D
408+
x :: int64
409+
__bitmap :: uint32
410+
r0 :: None
411+
L0:
412+
r0 = D.f(self, x, 0, __bitmap)
413+
return r0
414+
415+
[case testI64GlueWithInvalidOverride]
416+
from mypy_extensions import i64
417+
418+
class C:
419+
def f(self, x: i64, y: i64 = 5) -> None: pass
420+
def ff(self, x: int) -> None: pass
421+
class CC(C):
422+
def f(self, x: i64 = 12, y: i64 = 5) -> None: pass # Line 7
423+
def ff(self, x: int = 12) -> None: pass
424+
425+
class D:
426+
def f(self, x: int) -> None: pass
427+
class DD(D):
428+
def f(self, x: i64) -> None: pass # Line 13
429+
430+
class E:
431+
def f(self, x: i64) -> None: pass
432+
class EE(E):
433+
def f(self, x: int) -> None: pass # Line 18
434+
[out]
435+
main:7: error: An argument with type "int64" cannot be given a default value in a method override
436+
main:13: error: Incompatible argument type "int64" (base class has type "int")
437+
main:18: error: Incompatible argument type "int" (base class has type "int64")

mypyc/test-data/irbuild-i64.test

+35
Original file line numberDiff line numberDiff line change
@@ -1628,3 +1628,38 @@ L3:
16281628
goto L1
16291629
L4:
16301630
return 1
1631+
1632+
[case testI64MethodDefaultValueOverride]
1633+
from mypy_extensions import i64
1634+
1635+
class C:
1636+
def f(self, x: i64 = 11) -> None: pass
1637+
class D(C):
1638+
def f(self, x: i64 = 12) -> None: pass
1639+
[out]
1640+
def C.f(self, x, __bitmap):
1641+
self :: __main__.C
1642+
x :: int64
1643+
__bitmap, r0 :: uint32
1644+
r1 :: bit
1645+
L0:
1646+
r0 = __bitmap & 1
1647+
r1 = r0 == 0
1648+
if r1 goto L1 else goto L2 :: bool
1649+
L1:
1650+
x = 11
1651+
L2:
1652+
return 1
1653+
def D.f(self, x, __bitmap):
1654+
self :: __main__.D
1655+
x :: int64
1656+
__bitmap, r0 :: uint32
1657+
r1 :: bit
1658+
L0:
1659+
r0 = __bitmap & 1
1660+
r1 = r0 == 0
1661+
if r1 goto L1 else goto L2 :: bool
1662+
L1:
1663+
x = 12
1664+
L2:
1665+
return 1

mypyc/test-data/run-i64.test

+44
Original file line numberDiff line numberDiff line change
@@ -1126,3 +1126,47 @@ def test_many_locals() -> None:
11261126
assert a31 == 10
11271127
assert a32 == 55
11281128
assert a33 == 20
1129+
1130+
[case testI64GlueMethods]
1131+
from typing_extensions import Final
1132+
1133+
MYPY = False
1134+
if MYPY:
1135+
from mypy_extensions import i64
1136+
1137+
MAGIC: Final = -113
1138+
1139+
class Base:
1140+
def foo(self) -> i64:
1141+
return 5
1142+
1143+
def bar(self, x: i64 = 2) -> i64:
1144+
return x + 1
1145+
1146+
def hoho(self, x: i64) -> i64:
1147+
return x - 1
1148+
1149+
class Derived(Base):
1150+
def foo(self, x: i64 = 5) -> i64:
1151+
return x + 10
1152+
1153+
def bar(self, x: i64 = 3, y: i64 = 20) -> i64:
1154+
return x + y + 2
1155+
1156+
def hoho(self, x: i64 = 7) -> i64:
1157+
return x - 2
1158+
1159+
def test_derived_adds_bitmap() -> None:
1160+
b: Base = Derived()
1161+
assert b.foo() == 15
1162+
1163+
def test_derived_adds_another_default_arg() -> None:
1164+
b: Base = Derived()
1165+
assert b.bar() == 25
1166+
assert b.bar(1) == 23
1167+
assert b.bar(MAGIC) == MAGIC + 22
1168+
1169+
def test_derived_switches_arg_to_have_default() -> None:
1170+
b: Base = Derived()
1171+
assert b.hoho(5) == 3
1172+
assert b.hoho(MAGIC) == MAGIC - 2

0 commit comments

Comments
 (0)