From fa5d393229f7cb16439ad3c19e0fe8d8144d4199 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Sat, 27 Feb 2021 11:10:16 -0500 Subject: [PATCH 1/2] Remove an obsolete compiler subroutine --- hy/compiler.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 7a8b8ef59..0a0fb742d 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -109,9 +109,6 @@ def ast_str(x, piecewise=False): "unquote", "unquote-splice", "unpack-mapping", "except")) -def named_constant(expr, v): - return asty.Constant(expr, value=v) - def special(names, pattern): """Declare special operators. The decorated method and the given pattern is assigned to _special_form_compilers for each of the listed names.""" @@ -960,7 +957,7 @@ def compile_with_expression(self, expr, root, args, body): # Initialize the tempvar to None in case the `with` exits # early with an exception. initial_assign = asty.Assign( - expr, targets=[name], value=named_constant(expr, None)) + expr, targets=[name], value=asty.Constant(expr, value=None)) node = asty.With if root == "with*" else asty.AsyncWith the_with = node(expr, @@ -1220,7 +1217,7 @@ def compile_logical_or_and_and_operator(self, expr, operator, args): opnode, default = ops[operator] osym = expr[0] if len(args) == 0: - return named_constant(osym, default) + return asty.Constant(osym, value=default) elif len(args) == 1: return self.compile(args[0]) ret = Result() @@ -1282,7 +1279,7 @@ def _get_c_op(self, sym): def compile_compare_op_expression(self, expr, root, args): if len(args) == 1: return (self.compile(args[0]) + - named_constant(expr, True)) + asty.Constant(expr, value=True)) ops = [self._get_c_op(root) for _ in args[1:]] exprs, ret, _ = self._compile_collect(args) @@ -1373,7 +1370,7 @@ def compile_augassign_expression(self, expr, root, target, values): @special((PY38, "setx"), [times(1, 1, SYM + FORM)]) def compile_def_expression(self, expr, root, decls): if not decls: - return named_constant(expr, None) + return asty.Constant(expr, value=None) result = Result() is_assignment_expr = root == HySymbol("setx") @@ -1479,7 +1476,7 @@ def make_not(operand): cond_compiled = (Result() + asty.Assign(cond, targets=[self._storeize(cond, cond_var)], - value=named_constant(cond, True)) + value=asty.Constant(cond, value=True)) + cond_var) orel = Result() @@ -1854,7 +1851,8 @@ def compile_symbol(self, symbol): self.imports[self._stdlib[ast_str(symbol)]].add(ast_str(symbol)) if ast_str(symbol) in ("None", "False", "True"): - return named_constant(symbol, ast.literal_eval(ast_str(symbol))) + return asty.Constant(symbol, value = + ast.literal_eval(ast_str(symbol))) return asty.Name(symbol, id=ast_str(symbol), ctx=ast.Load()) From aaa03bbdfdee98404c031928d1fafe640fdcc198 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Sun, 28 Feb 2021 09:23:47 -0500 Subject: [PATCH 2/2] Prevent more ways to assign to constants --- NEWS.rst | 1 + hy/compiler.py | 27 +++++++++++---------- tests/native_tests/language.hy | 44 ++++++++++++++++------------------ 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index b48f915d9..838b50c67 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -42,6 +42,7 @@ Bug Fixes * Fixed error handling for non-symbol macro names * `doc` and `#doc` now work with names that require mangling. * Fixed compiler crash on `.` form with empty attributes. +* Attempts to assign to constants are now more reliably detected. 0.20.0 (released 2021-01-25) ============================== diff --git a/hy/compiler.py b/hy/compiler.py index 0a0fb742d..455a37824 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -538,13 +538,20 @@ def _storeize(self, expr, name, func=None): new_name = ast.Starred( value=self._storeize(expr, name.value, func)) else: - raise self._syntax_error(expr, - "Can't assign or delete a %s" % type(expr).__name__) + raise self._syntax_error(expr, "Can't assign or delete a " + ( + "constant" + if isinstance(name, ast.Constant) + else type(expr).__name__)) new_name.ctx = func() ast.copy_location(new_name, name) return new_name + def _nonconst(self, name): + if str(name) in ("None", "True", "False"): + raise self._syntax_error(name, "Can't assign to constant") + return name + def _render_quoted_form(self, form, level): """ Render a quoted form as a new HyExpression. @@ -723,7 +730,7 @@ def _compile_catch_expression(self, expr, var, exceptions, body): name = None if len(exceptions) == 2: - name = ast_str(exceptions[0]) + name = ast_str(self._nonconst(exceptions[0])) exceptions_list = exceptions[-1] if exceptions else HyList() if isinstance(exceptions_list, HyList): @@ -1405,18 +1412,11 @@ def _compile_assign(self, ann, name, value, *, is_assignment_expr = False): if ann is not None: # An annotation / annotated assignment is more strict with the target expression. invalid_name = not isinstance(ld_name.expr, (ast.Name, ast.Attribute, ast.Subscript)) - else: - invalid_name = (str(name) in ("None", "True", "False") - or isinstance(ld_name.expr, ast.Call)) - - if invalid_name: - raise self._syntax_error(name, "illegal target for {}".format( - "annotation" if annotate_only else "assignment")) if (result.temp_variables and isinstance(name, HySymbol) and '.' not in name): - result.rename(name) + result.rename(self._nonconst(name)) if not is_assignment_expr: # Throw away .expr to ensure that (setv ...) returns None. result.expr = None @@ -1601,7 +1601,8 @@ def _compile_arguments_set(self, decls, implicit_default_none, ret): # positional args. args_defaults.append(None) - args_ast.append(asty.arg(sym, arg=ast_str(sym), annotation=ann_ast)) + args_ast.append(asty.arg( + sym, arg=ast_str(self._nonconst(sym)), annotation=ann_ast)) return args_ast, args_defaults, ret @@ -1635,7 +1636,7 @@ def compile_class_expression(self, expr, root, name, rest): return bases + asty.ClassDef( expr, decorator_list=[], - name=ast_str(name), + name=ast_str(self._nonconst(name)), keywords=keywords, starargs=None, kwargs=None, diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 61b26ac23..45bff8888 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -9,7 +9,7 @@ [operator [or_]] pickle [typing [get-type-hints List Dict]] - [hy.errors [HyLanguageError]] + [hy.errors [HyLanguageError HySyntaxError]] pytest) (import sys) @@ -66,20 +66,6 @@ (assert (= (get foo 0) 12))) -(defn test-setv-builtin [] - "NATIVE: test that setv doesn't work on names Python can't assign to - and that we can't mangle" - (for [form '[ - (setv None 1) - (defn None [] (print "hello")) - (setv False 1) - (setv True 0) - (defn True [] (print "hello"))]] - (with [e (pytest.raises SyntaxError)] - (eval form)) - (assert (in "illegal target for assignment" (str e))))) - - (defn test-setv-pairs [] "NATIVE: test that setv works on pairs of arguments" (setv a 1 b 2) @@ -138,16 +124,28 @@ (assert (= l [["eggs" "ham"]]))) -(defn test-store-errors [] - "NATIVE: test that setv raises the correct errors when given wrong argument types" - (for [[form s] '[ - [(setv (do 1 2) 1) "a non-expression"] - [(setv 1 1) "a HyInteger"] - [(setv {1 2} 1) "a HyDict"] - [(del 1 1) "a HyInteger"]]] +(defn test-illegal-assignments [] + (for [form '[ + (setv (do 1 2) 1) + (setv 1 1) + (setv {1 2} 1) + (del 1 1) + ; https://github.com/hylang/hy/issues/1780 + (setv None 1) + (setv False 1) + (setv True 1) + (defn None [] (print "hello")) + (defn True [] (print "hello")) + (defn f [True] (print "hello")) + (for [True [1 2 3]] (print "hello")) + (lfor True [1 2 3] True) + (lfor :setv True 1 True) + (with [True x] (print "hello")) + (try 1 (except [True AssertionError] 2)) + (defclass True [])]] (with [e (pytest.raises HyLanguageError)] (eval form)) - (assert (= e.value.msg (+ "Can't assign or delete " s))))) + (assert (in "Can't assign" e.value.msg)))) (defn test-no-str-as-sym []