diff --git a/Src/IronPython/Compiler/Ast/AstMethods.cs b/Src/IronPython/Compiler/Ast/AstMethods.cs index fae2a641e..6c7a30a3e 100644 --- a/Src/IronPython/Compiler/Ast/AstMethods.cs +++ b/Src/IronPython/Compiler/Ast/AstMethods.cs @@ -81,6 +81,11 @@ internal static class AstMethods { // methods matching Python opcodes public static readonly MethodInfo DictUpdate = GetMethod((Action)PythonOps.DictUpdate); + public static readonly MethodInfo ListAppend = GetMethod((Action)PythonOps.ListAppend); + public static readonly MethodInfo ListExtend = GetMethod((Action)PythonOps.ListExtend); + public static readonly MethodInfo ListToTuple = GetMethod((Func)PythonOps.ListToTuple); + public static readonly MethodInfo SetAdd = GetMethod((Action)PythonOps.SetAdd); + public static readonly MethodInfo SetUpdate = GetMethod((Action)PythonOps.SetUpdate); private static MethodInfo GetMethod(Delegate x) { return x.Method; diff --git a/Src/IronPython/Compiler/Ast/Expression.cs b/Src/IronPython/Compiler/Ast/Expression.cs index 381b065f5..868fdba04 100644 --- a/Src/IronPython/Compiler/Ast/Expression.cs +++ b/Src/IronPython/Compiler/Ast/Expression.cs @@ -2,18 +2,39 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using MSAst = System.Linq.Expressions; +#nullable enable using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; + +using IronPython.Runtime.Binding; using Microsoft.Scripting; -using IronPython.Runtime.Binding; +using AstUtils = Microsoft.Scripting.Ast.Utils; +using MSAst = System.Linq.Expressions; namespace IronPython.Compiler.Ast { public abstract class Expression : Node { - internal static Expression[] EmptyArray = new Expression[0]; + internal static readonly Expression[] EmptyArray = Array.Empty(); + + protected internal static MSAst.BlockExpression UnpackSequenceHelper(IList items, MethodInfo makeEmpty, MethodInfo append, MethodInfo extend) { + var expressions = new ReadOnlyCollectionBuilder(items.Count + 2); + var varExpr = Expression.Variable(typeof(T), "$coll"); + expressions.Add(Expression.Assign(varExpr, Expression.Call(makeEmpty))); + foreach (var item in items) { + if (item is StarredExpression starredExpression) { + expressions.Add(Expression.Call(extend, varExpr, AstUtils.Convert(starredExpression.Value, typeof(object)))); + } else { + expressions.Add(Expression.Call(append, varExpr, AstUtils.Convert(item, typeof(object)))); + } + } + expressions.Add(varExpr); + return Expression.Block(typeof(T), new MSAst.ParameterExpression[] { varExpr }, expressions); + } internal virtual MSAst.Expression TransformSet(SourceSpan span, MSAst.Expression right, PythonOperationKind op) { // unreachable, CheckAssign prevents us from calling this at parse time. @@ -26,23 +47,15 @@ internal virtual MSAst.Expression TransformDelete() { throw new InvalidOperationException(); } - internal virtual ConstantExpression ConstantFold() => null; + internal virtual ConstantExpression? ConstantFold() => null; - internal virtual string CheckAssign() => "can't assign to " + NodeName; + internal virtual string? CheckAssign() => "can't assign to " + NodeName; - internal virtual string CheckAugmentedAssign() => CheckAssign(); + internal virtual string? CheckAugmentedAssign() => CheckAssign(); - internal virtual string CheckDelete() => "can't delete " + NodeName; + internal virtual string? CheckDelete() => "can't delete " + NodeName; - internal virtual bool IsConstant { - get { - var folded = ConstantFold(); - if (folded != null) { - return folded.IsConstant; - } - return false; - } - } + internal virtual bool IsConstant => ConstantFold()?.IsConstant ?? false; internal virtual object GetConstantValue() { var folded = ConstantFold(); diff --git a/Src/IronPython/Compiler/Ast/ListExpression.cs b/Src/IronPython/Compiler/Ast/ListExpression.cs index 31b07c212..dabf39f90 100644 --- a/Src/IronPython/Compiler/Ast/ListExpression.cs +++ b/Src/IronPython/Compiler/Ast/ListExpression.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +#nullable enable + +using IronPython.Runtime; + using MSAst = System.Linq.Expressions; namespace IronPython.Compiler.Ast { - using Ast = MSAst.Expression; - public class ListExpression : SequenceExpression { public ListExpression(params Expression[] items) : base(items) { @@ -14,9 +16,11 @@ public ListExpression(params Expression[] items) public override MSAst.Expression Reduce() { if (Items.Count == 0) { - return Ast.Call( - AstMethods.MakeEmptyList - ); + return Expression.Call(AstMethods.MakeEmptyList); + } + + if (HasStarredExpression) { + return UnpackSequenceHelper(Items, AstMethods.MakeEmptyList, AstMethods.ListAppend, AstMethods.ListExtend); } return Call( diff --git a/Src/IronPython/Compiler/Ast/SequenceExpression.cs b/Src/IronPython/Compiler/Ast/SequenceExpression.cs index 36c155131..0310d2673 100644 --- a/Src/IronPython/Compiler/Ast/SequenceExpression.cs +++ b/Src/IronPython/Compiler/Ast/SequenceExpression.cs @@ -2,23 +2,21 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +#nullable enable + using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Linq; using System.Runtime.CompilerServices; -using Microsoft.Scripting; -using Microsoft.Scripting.Runtime; - using IronPython.Runtime.Binding; -using IronPython.Runtime.Operations; -using MSAst = System.Linq.Expressions; +using Microsoft.Scripting; +using Microsoft.Scripting.Runtime; using AstUtils = Microsoft.Scripting.Ast.Utils; +using MSAst = System.Linq.Expressions; namespace IronPython.Compiler.Ast { - using Ast = MSAst.Expression; - public abstract class SequenceExpression : Expression { private readonly Expression[] _items; @@ -28,6 +26,8 @@ protected SequenceExpression(Expression[] items) { public IList Items => _items; + protected bool HasStarredExpression => Items.OfType().Any(); + internal override MSAst.Expression TransformSet(SourceSpan span, MSAst.Expression right, PythonOperationKind op) { // if we just have a simple named multi-assignment (e.g. a, b = 1,2) // then go ahead and step over the entire statement at once. If we have a @@ -58,7 +58,7 @@ internal override MSAst.Expression TransformSet(SourceSpan span, MSAst.Expressio } // 1. Evaluate the expression and assign the value to the temp. - MSAst.ParameterExpression right_temp = Ast.Variable(typeof(object), "unpacking"); + MSAst.ParameterExpression right_temp = Expression.Variable(typeof(object), "unpacking"); // 2. Add the assignment "right_temp = right" into the suite/block MSAst.Expression assignStmt1 = MakeAssignment(right_temp, right); @@ -92,7 +92,7 @@ internal override MSAst.Expression TransformSet(SourceSpan span, MSAst.Expressio ), typeof(object[])); // 4. Create temporary variable for the array - MSAst.ParameterExpression array_temp = Ast.Variable(typeof(object[]), "array"); + MSAst.ParameterExpression array_temp = Expression.Variable(typeof(object[]), "array"); // 5. Assign the value of the method call (mce) into the array temp // And add the assignment "array_temp = Ops.GetEnumeratorValues(...)" into the block @@ -112,7 +112,7 @@ internal override MSAst.Expression TransformSet(SourceSpan span, MSAst.Expressio } // 6. array_temp[i] - MSAst.Expression element = Ast.ArrayAccess( + MSAst.Expression element = Expression.ArrayAccess( array_temp, // array expression AstUtils.Constant(i) // index ); @@ -129,13 +129,13 @@ internal override MSAst.Expression TransformSet(SourceSpan span, MSAst.Expressio } // 9. add the sets as their own block so they can be marked as a single span, if necessary. sets.Add(AstUtils.Empty()); - MSAst.Expression itemSet = GlobalParent.AddDebugInfo(Ast.Block(sets.ToReadOnlyCollection()), leftSpan); + MSAst.Expression itemSet = GlobalParent.AddDebugInfo(Expression.Block(sets.ToReadOnlyCollection()), leftSpan); // 10. Return the suite statement (block) - return GlobalParent.AddDebugInfo(Ast.Block(new[] { array_temp, right_temp }, assignStmt1, assignStmt2, itemSet, AstUtils.Empty()), totalSpan); + return GlobalParent.AddDebugInfo(Expression.Block(new[] { array_temp, right_temp }, assignStmt1, assignStmt2, itemSet, AstUtils.Empty()), totalSpan); } - internal override string CheckAssign() { + internal override string? CheckAssign() { var starCount = 0; foreach (var item in Items) { if (item.CheckAssign() is { } checkAssign) { @@ -157,7 +157,7 @@ internal override string CheckAssign() { return null; } - internal override string CheckDelete() => null; + internal override string? CheckDelete() => null; internal override string CheckAugmentedAssign() => CheckAssign() ?? "illegal expression for augmented assignment"; @@ -170,7 +170,7 @@ internal override MSAst.Expression TransformDelete() { statements[i] = _items[i].TransformDelete(); } statements[_items.Length] = AstUtils.Empty(); - return GlobalParent.AddDebugInfo(Ast.Block(statements), Span); + return GlobalParent.AddDebugInfo(Expression.Block(statements), Span); } internal override bool CanThrow { diff --git a/Src/IronPython/Compiler/Ast/SetExpression.cs b/Src/IronPython/Compiler/Ast/SetExpression.cs index b66ce74d2..4165d9058 100644 --- a/Src/IronPython/Compiler/Ast/SetExpression.cs +++ b/Src/IronPython/Compiler/Ast/SetExpression.cs @@ -2,17 +2,19 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using MSAst = System.Linq.Expressions; +#nullable enable using System.Collections.Generic; +using System.Linq; + +using IronPython.Runtime; using Microsoft.Scripting.Utils; using AstUtils = Microsoft.Scripting.Ast.Utils; +using MSAst = System.Linq.Expressions; namespace IronPython.Compiler.Ast { - using Ast = MSAst.Expression; - public class SetExpression : Expression { private readonly Expression[] _items; @@ -24,7 +26,17 @@ public SetExpression(params Expression[] items) { public IList Items => _items; + protected bool HasStarredExpression => Items.OfType().Any(); + public override MSAst.Expression Reduce() { + if (Items.Count == 0) { + return Expression.Call(AstMethods.MakeEmptySet); + } + + if (HasStarredExpression) { + return UnpackSequenceHelper(Items, AstMethods.MakeEmptySet, AstMethods.SetAdd, AstMethods.SetUpdate); + } + return Expression.Call( AstMethods.MakeSet, NewArrayInit( diff --git a/Src/IronPython/Compiler/Ast/StarredExpression.cs b/Src/IronPython/Compiler/Ast/StarredExpression.cs index 74799fe83..6339113b5 100644 --- a/Src/IronPython/Compiler/Ast/StarredExpression.cs +++ b/Src/IronPython/Compiler/Ast/StarredExpression.cs @@ -29,9 +29,9 @@ public StarredExpression(Expression value) { internal override MSAst.Expression TransformSet(SourceSpan span, MSAst.Expression right, PythonOperationKind op) => Value.TransformSet(span, right, op); - internal override string CheckAssign() => Value.CheckAssign(); + internal override string? CheckAssign() => Value.CheckAssign(); - internal override string CheckDelete() => "can use starred expression only as assignment target"; + internal override string CheckDelete() => "can't use starred expression here"; // TODO: change error message in 3.9 internal override MSAst.Expression TransformDelete() => Value.TransformDelete(); @@ -76,10 +76,16 @@ public override bool Walk(ForStatement node) { } public override bool Walk(StarredExpression node) { - ReportSyntaxError("can use starred expression only as assignment target", node); + ReportSyntaxError("can't use starred expression here", node); return base.Walk(node); } + public override bool Walk(ListExpression node) => WalkItems(node.Items); + + public override bool Walk(SetExpression node) => WalkItems(node.Items); + + public override bool Walk(TupleExpression node) => WalkItems(node.Items); + private void ReportSyntaxError(string message, Node node) { context.Errors.Add(context.SourceUnit, message, node.Span, ErrorCodes.SyntaxError, Severity.FatalError); } diff --git a/Src/IronPython/Compiler/Ast/TupleExpression.cs b/Src/IronPython/Compiler/Ast/TupleExpression.cs index 53289b670..92209e73f 100644 --- a/Src/IronPython/Compiler/Ast/TupleExpression.cs +++ b/Src/IronPython/Compiler/Ast/TupleExpression.cs @@ -2,28 +2,21 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using System; - -using Microsoft.Scripting; -using Microsoft.Scripting.Runtime; +#nullable enable using IronPython.Runtime; -using IronPython.Runtime.Binding; using IronPython.Runtime.Operations; using MSAst = System.Linq.Expressions; - namespace IronPython.Compiler.Ast { - using Ast = MSAst.Expression; - public class TupleExpression : SequenceExpression { public TupleExpression(bool expandable, params Expression[] items) : base(items) { IsExpandable = expandable; } - internal override string CheckAssign() { + internal override string? CheckAssign() { if (Items.Count == 0) { // TODO: remove this when we get to 3.6 return "can't assign to ()"; @@ -32,7 +25,7 @@ internal override string CheckAssign() { return base.CheckAssign(); } - internal override string CheckDelete() { + internal override string? CheckDelete() { if (Items.Count == 0) return "can't delete ()"; // TODO: remove this when we get to 3.6 return base.CheckDelete(); @@ -40,22 +33,26 @@ internal override string CheckDelete() { public override MSAst.Expression Reduce() { if (IsExpandable) { - return Ast.NewArrayInit( + return Expression.NewArrayInit( typeof(object), ToObjectArray(Items) ); } if (Items.Count == 0) { - return Ast.Field( - null, - typeof(PythonOps).GetField(nameof(PythonOps.EmptyTuple)) + return Expression.Field( + null!, + typeof(PythonOps).GetField(nameof(PythonOps.EmptyTuple))! ); } - return Ast.Call( + if (HasStarredExpression) { + return Expression.Call(AstMethods.ListToTuple, UnpackSequenceHelper(Items, AstMethods.MakeEmptyList, AstMethods.ListAppend, AstMethods.ListExtend)); + } + + return Expression.Call( AstMethods.MakeTuple, - Ast.NewArrayInit( + Expression.NewArrayInit( typeof(object), ToObjectArray(Items) ) diff --git a/Src/IronPython/Compiler/Parser.cs b/Src/IronPython/Compiler/Parser.cs index dc437c512..b52e5d6f2 100644 --- a/Src/IronPython/Compiler/Parser.cs +++ b/Src/IronPython/Compiler/Parser.cs @@ -2416,6 +2416,7 @@ private Expression FinishTupleOrGenExp() { ret = FinishExpressionListAsExpr(expr); } else if (PeekToken(Tokens.KeywordForToken)) { // "(" expression "for" ... + if (expr is StarredExpression) ReportSyntaxError(expr.StartIndex, expr.EndIndex, "iterable unpacking cannot be used in comprehension"); ret = ParseGeneratorExpression(expr); } else { // "(" expression ")" @@ -2548,16 +2549,31 @@ private Expression FinishDictOrSetValue() { break; } bool first = false; - if (MaybeEat(TokenKind.Power)) { + if (PeekToken(TokenKind.Power)) { if (setMembers is not null) ReportSyntaxError("invalid syntax"); - if (dictMembers is null) { + else if (dictMembers is null) { dictMembers = new(); first = true; } + NextToken(); var expr = ParseExpr(); + var se = new SliceExpression(null, expr, null); se.SetLoc(_globalParent, expr.StartIndex, expr.EndIndex); - dictMembers.Add(se); + dictMembers?.Add(se); + } else if (PeekToken(TokenKind.Multiply)) { + if (dictMembers is not null) ReportSyntaxError("invalid syntax"); + else if (setMembers is null) { + setMembers = new(); + first = true; + } + var expr = ParseStarExpr(); + + if (PeekToken(Tokens.KeywordForToken)) { + if (!first) ReportSyntaxError("invalid syntax"); + } + + setMembers?.Add(expr); } else { Expression e1 = ParseTest(); if (MaybeEat(TokenKind.Colon)) { // dict literal @@ -2645,6 +2661,7 @@ private SetComprehension FinishSetComp(Expression item, int oStart, int oEnd) { new SourceSpan(_tokenizer.IndexToLocation(cStart), _tokenizer.IndexToLocation(cEnd)), 1); + if (item is StarredExpression) ReportSyntaxError(item.StartIndex, item.EndIndex, "iterable unpacking cannot be used in comprehension"); var ret = new SetComprehension(item, iters); ret.SetLoc(_globalParent, oStart, cEnd); return ret; @@ -2735,6 +2752,7 @@ private Expression FinishListValue() { if (PeekToken(Tokens.KeywordForToken)) { // although it's calling ParseCompIter(), because the peek token is a FOR it is going to // do the right thing. + if (expr is StarredExpression) ReportSyntaxError(expr.StartIndex, expr.EndIndex, "iterable unpacking cannot be used in comprehension"); ret = new ListComprehension(expr, ParseCompIter()); } else { // (',' (test|star_expr))* [','] diff --git a/Src/IronPython/Runtime/Operations/PythonOps.cs b/Src/IronPython/Runtime/Operations/PythonOps.cs index 564caef33..36e48fc87 100644 --- a/Src/IronPython/Runtime/Operations/PythonOps.cs +++ b/Src/IronPython/Runtime/Operations/PythonOps.cs @@ -1506,6 +1506,36 @@ public static void DictUpdate(CodeContext context, PythonDictionary dict, object } } + /// + /// LIST_APPEND + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static void ListAppend(PythonList list, object? o) => list.AddNoLock(o); + + /// + /// LIST_EXTEND + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static void ListExtend(PythonList list, object? o) => list.extend(o); + + /// + /// LIST_TO_TUPLE + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static PythonTuple ListToTuple(PythonList list) => new PythonTuple(list); + + /// + /// SET_ADD + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetAdd(SetCollection set, object? o) => set.add(o); + + /// + /// SET_UPDATE + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetUpdate(SetCollection set, object? o) => set.update(o); + /// /// Python Runtime Helper for enumerator unpacking (tuple assignments, ...) /// Creates enumerator from the input parameter e, and then extracts diff --git a/Src/StdLib/Lib/test/test_unpack_ex.py b/Src/StdLib/Lib/test/test_unpack_ex.py index b2008d44b..d237434bb 100644 --- a/Src/StdLib/Lib/test/test_unpack_ex.py +++ b/Src/StdLib/Lib/test/test_unpack_ex.py @@ -131,17 +131,17 @@ >>> *a # doctest:+ELLIPSIS Traceback (most recent call last): ... - SyntaxError: can use starred expression only as assignment target + SyntaxError: can't use starred expression here >>> *1 # doctest:+ELLIPSIS Traceback (most recent call last): ... - SyntaxError: can use starred expression only as assignment target + SyntaxError: can't use starred expression here >>> x = *a # doctest:+ELLIPSIS Traceback (most recent call last): ... - SyntaxError: can use starred expression only as assignment target + SyntaxError: can't use starred expression here Some size constraints (all fail.) diff --git a/Tests/test_unpack.py b/Tests/test_unpack.py index 1630f7021..8a6a3bda9 100644 --- a/Tests/test_unpack.py +++ b/Tests/test_unpack.py @@ -2,7 +2,9 @@ # The .NET Foundation licenses this file to you under the Apache 2.0 License. # See the LICENSE file in the project root for more information. -import test.support, unittest +import sys +import test.support +import unittest class UnpackTest(unittest.TestCase): def assertRaisesSyntaxError(self, body, expectedMessage, lineno = 1): @@ -87,25 +89,28 @@ def test_unpack_into_for_target_3(self): index = index + 1 def test_too_many_starred_assignments(self): - self.assertRaisesSyntaxError("*x, *k = range(5)", "two starred expressions in assignment") - self.assertRaisesSyntaxError("*x, *k, *r = range(5)", "two starred expressions in assignment") - self.assertRaisesSyntaxError("v, *x, n, *k = range(5)", "two starred expressions in assignment") - self.assertRaisesSyntaxError("h, t, *g, s, *x, m, *k = range(5)", "two starred expressions in assignment") + msg = "multiple starred expressions in assignment" if sys.version_info >= (3,9) else "two starred expressions in assignment" + self.assertRaisesSyntaxError("*x, *k = range(5)", msg) + self.assertRaisesSyntaxError("*x, *k, *r = range(5)", msg) + self.assertRaisesSyntaxError("v, *x, n, *k = range(5)", msg) + self.assertRaisesSyntaxError("h, t, *g, s, *x, m, *k = range(5)", msg) - self.assertRaisesSyntaxError("[*x, *k] = range(5)", "two starred expressions in assignment") - self.assertRaisesSyntaxError("[*x, *k, *r] = range(5)", "two starred expressions in assignment") - self.assertRaisesSyntaxError("[v, *x, n, *k] = range(5)", "two starred expressions in assignment") - self.assertRaisesSyntaxError("[h, t, *g, s, *x, m, *k] = range(5)", "two starred expressions in assignment") + self.assertRaisesSyntaxError("[*x, *k] = range(5)", msg) + self.assertRaisesSyntaxError("[*x, *k, *r] = range(5)", msg) + self.assertRaisesSyntaxError("[v, *x, n, *k] = range(5)", msg) + self.assertRaisesSyntaxError("[h, t, *g, s, *x, m, *k] = range(5)", msg) def test_assignment_to_unassignable_targets(self): - self.assertRaisesSyntaxError('[x, y, "a", z] = range(4)', "can't assign to literal") - self.assertRaisesSyntaxError('[x, y, a + 1, z] = range(4)', "can't assign to operator") + literal_msg = "cannot assign to literal" if sys.version_info >= (3,8) else "can't assign to literal" + operator_msg = "cannot assign to operator" if sys.version_info >= (3,8) else "can't assign to operator" + self.assertRaisesSyntaxError('[x, y, "a", z] = range(4)', literal_msg) + self.assertRaisesSyntaxError('[x, y, a + 1, z] = range(4)', operator_msg) - self.assertRaisesSyntaxError('(x, y, "a", z) = range(4)', "can't assign to literal") - self.assertRaisesSyntaxError('(x, y, a + 1, z) = range(4)', "can't assign to operator") + self.assertRaisesSyntaxError('(x, y, "a", z) = range(4)', literal_msg) + self.assertRaisesSyntaxError('(x, y, a + 1, z) = range(4)', operator_msg) - self.assertRaisesSyntaxError('x, y, "a", z = range(4)', "can't assign to literal") - self.assertRaisesSyntaxError('x, y, a + 1, z = range(4)', "can't assign to operator") + self.assertRaisesSyntaxError('x, y, "a", z = range(4)', literal_msg) + self.assertRaisesSyntaxError('x, y, a + 1, z = range(4)', operator_msg) def test_too_many_expressions_in_star_unpack(self): body = ", ".join("a%d" % i for i in range(1<<8)) + ", *rest = range(1<<8 + 1)" @@ -115,7 +120,10 @@ def test_too_many_expressions_in_star_unpack(self): self.assertRaisesSyntaxError(body, "too many expressions in star-unpacking assignment") def test_assign_to_empty(self): - self.assertRaisesSyntaxError('() = []', "can't assign to ()") + if sys.version_info >= (3,6): + exec("() = []") # TODO: remove exec once our baseline is >= 3.6 + else: + self.assertRaisesSyntaxError('() = []', "can't assign to ()") [] = () # OK def test_assign_trailing_comma_list_to_list(self): @@ -182,9 +190,15 @@ def test_assign_multiple_trailing_commas_fails(self): self.assertRaisesSyntaxError('(a, *b) = (1, 2, 3,,)', "invalid syntax") def test_delete_star_fails(self): - self.assertRaisesSyntaxError('del *a', "can use starred expression only as assignment target") - self.assertRaisesSyntaxError('del *a, b', "can use starred expression only as assignment target") - self.assertRaisesSyntaxError('del b, *a', "can use starred expression only as assignment target") + if sys.version_info >= (3,9): + msg = "cannot delete starred" + elif sys.implementation.name == "ironpython" or sys.version_info >= (3,5): + msg = "can't use starred expression here" + else: + msg = "can use starred expression only as assignment target" + self.assertRaisesSyntaxError('del *a', msg) + self.assertRaisesSyntaxError('del *a, b', msg) + self.assertRaisesSyntaxError('del b, *a', msg) def test_ipy3_gh841(self): "https://github.com/IronLanguages/ironpython3/issues/841" @@ -194,13 +208,33 @@ def test_ipy3_gh841(self): self.assertEqual(a, 1) self.assertEqual(b, [2]) + @unittest.skipUnless(sys.implementation.name == "ironpython" or sys.version_info >= (3,5), "new syntax in 3.5") def test_unpack_dict(self): - self.assertEqual({'x': 1, **{'y': 2}}, {'x': 1, 'y': 2}) - self.assertEqual({'x': 1, **{'x': 2}}, {'x': 2}) - self.assertEqual({**{'x': 2}, 'x': 1}, {'x': 1}) + # TODO: remove eval once our baseline is >= 3.5 + self.assertEqual(eval("{'x': 1, **{'y': 2}}"), {'x': 1, 'y': 2}) + self.assertEqual(eval("{'x': 1, **{'x': 2}}"), {'x': 2}) + self.assertEqual(eval("{**{'x': 2}, 'x': 1}"), {'x': 1}) + # TODO: remove exec once our baseline is >= 3.5 with self.assertRaises(TypeError): - {**[]} + exec("{**[]}") + + @unittest.skipUnless(sys.implementation.name == "ironpython" or sys.version_info >= (3,5), "new syntax in 3.5") + def test_unpack_sequence(self): + # TODO: remove eval once our baseline is >= 3.5 + self.assertEqual(eval("(*range(4), 4)"), (0,1,2,3,4)) + self.assertEqual(eval("[*range(4), 4]"), [0,1,2,3,4]) + self.assertEqual(eval("{*range(4), 4}"), {0,1,2,3,4}) + + # TODO: remove exec once our baseline is >= 3.5 + with self.assertRaises(TypeError): + exec("(*1,)") + + with self.assertRaises(TypeError): + exec("[*1]") + + with self.assertRaises(TypeError): + exec("{*1}") def test_main(): test.support.run_unittest(UnpackTest)