diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 757451d6..274a3af8 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -1004,6 +1004,7 @@ def bind(cls): cls.base_match_for_stmt <<= attach(cls.base_match_for_stmt_ref, cls.method("base_match_for_stmt_handle")) cls.async_with_for_stmt <<= attach(cls.async_with_for_stmt_ref, cls.method("async_with_for_stmt_handle")) cls.unsafe_typedef_tuple <<= attach(cls.unsafe_typedef_tuple_ref, cls.method("unsafe_typedef_tuple_handle")) + cls.unpack_typedef <<= attach(cls.unpack_typedef_ref, cls.method("unpack_typedef_handle")) cls.impl_call <<= attach(cls.impl_call_ref, cls.method("impl_call_handle")) cls.protocol_intersect_expr <<= attach(cls.protocol_intersect_expr_ref, cls.method("protocol_intersect_expr_handle")) @@ -5229,6 +5230,11 @@ def unsafe_typedef_tuple_handle(self, original, loc, tokens): tuple_items = self.testlist_star_expr_handle(original, loc, tokens) return "_coconut.typing.Tuple[" + tuple_items + "]" + def unpack_typedef_handle(self, tokens): + """Handle **Type -> Unpack[Type] in type annotations.""" + typedef, = tokens + return "_coconut.typing.Unpack[" + typedef + "]" + def term_handle(self, tokens): """Handle terms seperated by mul-like operators.""" out = [tokens[0]] diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index fe227f95..8c901b94 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -1055,6 +1055,7 @@ class Grammar(object): typedef_tuple = Forward() typedef_ellipsis = Forward() typedef_op_item = Forward() + unpack_typedef = Forward() expr_lambdef = Forward() stmt_lambdef = Forward() @@ -1198,9 +1199,10 @@ class Grammar(object): # we include (var)arg_comma to ensure the pattern matches the whole arg arg_comma = comma | fixto(FollowedBy(rparen), "") setarg_comma = arg_comma | fixto(FollowedBy(colon), "") - typedef_ref = setname + colon.suppress() + typedef_test + arg_comma + unpack_typedef_ref = dubstar.suppress() + typedef_test + typedef_ref = setname + colon.suppress() + (unpack_typedef | typedef_test) + arg_comma default = condense(equals + test) - unsafe_typedef_default_ref = setname + colon.suppress() + typedef_test + Optional(default) + unsafe_typedef_default_ref = setname + colon.suppress() + (unpack_typedef | typedef_test) + Optional(default) typedef_default_ref = unsafe_typedef_default_ref + arg_comma tfpdef = condense(setname + arg_comma) | typedef tfpdef_default = condense(setname + Optional(default) + arg_comma) | typedef_default diff --git a/coconut/tests/src/extras.coco b/coconut/tests/src/extras.coco index db7c8aac..f62246fb 100644 --- a/coconut/tests/src/extras.coco +++ b/coconut/tests/src/extras.coco @@ -354,6 +354,16 @@ cannot reassign type variable 'T' (use explicit '\T' syntax if intended) (line 1 assert_raises(-> parse_with_state("final x = 1\nmatch y:\n case x: pass"), CoconutSyntaxError, err_has="final") assert_raises(-> parse_with_state("final x = 1\nfinal x = 2"), CoconutSyntaxError, err_has="final") + # typing.Unpack syntax: **Type -> _coconut.typing.Unpack[Type] + assert "_coconut.typing.Unpack[int]" in parse_with_state("def f(**kwargs: **int): pass", "block") + assert "_coconut.typing.Unpack[MyDict]" in parse_with_state("def f(**kwargs: **MyDict): pass", "block") + assert "_coconut.typing.Unpack[MyDict]" in parse_with_state("def f(x: **MyDict): pass", "block") + assert "_coconut.typing.Unpack[dict[str, int]]" in parse_with_state("def f(**kwargs: **dict[str, int]): pass", "block") + assert "_coconut.typing.Unpack[" in parse_with_state("def f(a: int, **kwargs: **MyDict): pass", "block") + # ensure normal type annotations are not affected + assert "Unpack" not in parse_with_state("def f(**kwargs: str): pass", "block") + assert "Unpack" not in parse_with_state("def f(x: int): pass", "block") + return True