Skip to content

Commit 56ed494

Browse files
authored
Implement real support for default arguments (mypyc/mypyc#680)
Compute the defaults at function creation time and store them in globals (for toplevel functions) or in the function object (for nested functions). Fixes mypyc/mypyc#336. No tests for lambdas yet because they are broken due to a mypy bug I've sent a fix up for (python#7306). (Also fix a silly lambda bug where defaults weren't supported I came across while testing.)
1 parent 61d7065 commit 56ed494

File tree

3 files changed

+78
-16
lines changed

3 files changed

+78
-16
lines changed

mypyc/genops.py

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,7 +1176,7 @@ def visit_method(self, cdef: ClassDef, fdef: FuncDef) -> None:
11761176
else:
11771177
self.handle_non_ext_method(cdef, fdef)
11781178

1179-
def is_approximately_constant(self, e: Expression) -> bool:
1179+
def is_constant(self, e: Expression) -> bool:
11801180
"""Check whether we allow an expression to appear as a default value.
11811181
11821182
We don't currently properly support storing the evaluated
@@ -1189,7 +1189,7 @@ def is_approximately_constant(self, e: Expression) -> bool:
11891189
or (isinstance(e, UnaryExpr) and e.op == '-'
11901190
and isinstance(e.expr, (IntExpr, FloatExpr)))
11911191
or (isinstance(e, TupleExpr)
1192-
and all(self.is_approximately_constant(e) for e in e.items))
1192+
and all(self.is_constant(e) for e in e.items))
11931193
or (isinstance(e, RefExpr) and e.kind == GDEF
11941194
and (e.fullname in ('builtins.True', 'builtins.False', 'builtins.None')
11951195
or (isinstance(e.node, Var) and e.node.is_final))))
@@ -1228,7 +1228,7 @@ def generate_attr_defaults(self, cdef: ClassDef) -> None:
12281228
for stmt in default_assignments:
12291229
lvalue = stmt.lvalues[0]
12301230
assert isinstance(lvalue, NameExpr)
1231-
if not stmt.is_final_def and not self.is_approximately_constant(stmt.rvalue):
1231+
if not stmt.is_final_def and not self.is_constant(stmt.rvalue):
12321232
self.warning('Unsupported default attribute value', stmt.rvalue.line)
12331233

12341234
# If the attribute is initialized to None and type isn't optional,
@@ -1693,20 +1693,61 @@ def create_ne_from_eq(self, cdef: ClassDef) -> None:
16931693
cls.methods['__ne__'] = f
16941694
self.functions.append(f)
16951695

1696-
def gen_arg_default(self) -> None:
1696+
def calculate_arg_defaults(self,
1697+
fn_info: FuncInfo,
1698+
env: Environment,
1699+
func_reg: Optional[Value]) -> None:
1700+
"""Calculate default argument values and store them.
1701+
1702+
They are stored in statics for top level functions and in
1703+
the function objects for nested functions (while constants are
1704+
still stored computed on demand).
1705+
"""
1706+
fitem = fn_info.fitem
1707+
for arg in fitem.arguments:
1708+
# Constant values don't get stored but just recomputed
1709+
if arg.initializer and not self.is_constant(arg.initializer):
1710+
value = self.coerce(self.accept(arg.initializer),
1711+
env.lookup(arg.variable).type, arg.line)
1712+
if not fn_info.is_nested:
1713+
name = fitem.fullname() + '.' + arg.variable.name()
1714+
self.add(InitStatic(value, name, 'final'))
1715+
else:
1716+
assert func_reg is not None
1717+
self.add(SetAttr(func_reg, arg.variable.name(), value, arg.line))
1718+
1719+
def gen_arg_defaults(self) -> None:
16971720
"""Generate blocks for arguments that have default values.
16981721
1699-
If the passed value is an error value, then assign the default value to the argument.
1722+
If the passed value is an error value, then assign the default
1723+
value to the argument.
17001724
"""
17011725
fitem = self.fn_info.fitem
17021726
for arg in fitem.arguments:
17031727
if arg.initializer:
1704-
if not self.is_approximately_constant(arg.initializer):
1705-
self.warning('Unsupported default argument value', arg.initializer.line)
17061728
target = self.environment.lookup(arg.variable)
1729+
1730+
def get_default() -> Value:
1731+
assert arg.initializer is not None
1732+
1733+
# If it is constant, don't bother storing it
1734+
if self.is_constant(arg.initializer):
1735+
return self.accept(arg.initializer)
1736+
1737+
# Because gen_arg_defaults runs before calculate_arg_defaults, we
1738+
# add the static/attribute to final_names/the class here.
1739+
elif not self.fn_info.is_nested:
1740+
name = fitem.fullname() + '.' + arg.variable.name()
1741+
self.final_names.append((name, target.type))
1742+
return self.add(LoadStatic(target.type, name, 'final'))
1743+
else:
1744+
name = arg.variable.name()
1745+
self.fn_info.callable_class.ir.attributes[name] = target.type
1746+
return self.add(
1747+
GetAttr(self.fn_info.callable_class.self_reg, name, arg.line))
17071748
assert isinstance(target, AssignmentTargetRegister)
17081749
self.assign_if_null(target,
1709-
lambda: self.accept(cast(Expression, arg.initializer)),
1750+
get_default,
17101751
arg.initializer.line)
17111752

17121753
def gen_func_item(self,
@@ -1793,7 +1834,7 @@ def c() -> None:
17931834
self.add_raise_exception_blocks_to_generator_class(fitem.line)
17941835
else:
17951836
self.load_env_registers()
1796-
self.gen_arg_default()
1837+
self.gen_arg_defaults()
17971838

17981839
if self.fn_info.contains_nested and not self.fn_info.is_generator:
17991840
self.finalize_env_class()
@@ -1850,6 +1891,8 @@ def c() -> None:
18501891
else:
18511892
func_ir, func_reg = self.gen_func_ir(blocks, sig, env, fn_info, cdef)
18521893

1894+
self.calculate_arg_defaults(fn_info, env, func_reg)
1895+
18531896
return (func_ir, func_reg)
18541897

18551898
def gen_func_ir(self,
@@ -3776,7 +3819,8 @@ def visit_lambda_expr(self, expr: LambdaExpr) -> Value:
37763819
runtime_args = []
37773820
for arg, arg_type in zip(expr.arguments, typ.arg_types):
37783821
arg.variable.type = arg_type
3779-
runtime_args.append(RuntimeArg(arg.variable.name(), self.type_to_rtype(arg_type)))
3822+
runtime_args.append(
3823+
RuntimeArg(arg.variable.name(), self.type_to_rtype(arg_type), arg.kind))
37803824
ret_type = self.type_to_rtype(typ.ret_type)
37813825

37823826
fsig = FuncSignature(runtime_args, ret_type)
@@ -4857,7 +4901,7 @@ def instantiate_env_class(self) -> Value:
48574901
def gen_generator_func(self) -> None:
48584902
self.setup_generator_class()
48594903
self.load_env_registers()
4860-
self.gen_arg_default()
4904+
self.gen_arg_defaults()
48614905
self.finalize_env_class()
48624906
self.add(Return(self.instantiate_generator_class()))
48634907

test-data/commandline.test

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,6 @@ class Concrete2:
148148
class Trait2(Concrete2):
149149
pass
150150

151-
def warning(x: List[int] = []) -> None: # W: Unsupported default argument value
152-
pass
153-
154151
class Nope(Trait1, Concrete2): # E: Non-trait bases must appear first in parent list # E: Non-trait MRO must be linear
155152
pass
156153

test-data/run.test

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ with assertRaises(NameError):
635635
lol
636636

637637
[case testFunctionCallWithDefaultArgs]
638-
from typing import Tuple, List, Optional
638+
from typing import Tuple, List, Optional, Callable, Any
639639
def f(x: int, y: int = 3, s: str = "test", z: object = 5) -> Tuple[int, str]:
640640
def inner() -> int:
641641
return x + y
@@ -645,15 +645,36 @@ def g() -> None:
645645
assert f(s = "123", x = -2) == (1, "123")
646646
def h(a: Optional[object] = None, b: Optional[str] = None) -> Tuple[object, Optional[str]]:
647647
return (a, b)
648+
649+
def same(x: object = object()) -> object:
650+
return x
651+
652+
a_lambda: Callable[..., Any] = lambda n=20: n
653+
654+
def nested_funcs(n: int) -> List[Callable[..., Any]]:
655+
ls: List[Callable[..., Any]] = []
656+
for i in range(n):
657+
def f(i: int = i) -> int:
658+
return i
659+
ls.append(f)
660+
return ls
661+
662+
648663
[file driver.py]
649-
from native import f, g, h
664+
from native import f, g, h, same, nested_funcs, a_lambda
650665
g()
651666
assert f(2) == (5, "test")
652667
assert f(s = "123", x = -2) == (1, "123")
653668
assert h() == (None, None)
654669
assert h(10) == (10, None)
655670
assert h(b='a') == (None, 'a')
656671
assert h(10, 'a') == (10, 'a')
672+
assert same() == same()
673+
674+
assert [f() for f in nested_funcs(10)] == list(range(10))
675+
676+
assert a_lambda(10) == 10
677+
assert a_lambda() == 20
657678

658679
[case testMethodCallWithDefaultArgs]
659680
from typing import Tuple, List

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy