From 9cedf72086984f7a8a6d03aaa98e6ab866ff43fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lozier?= Date: Fri, 2 Apr 2021 10:51:17 -0400 Subject: [PATCH 1/2] Unpack function arguments --- Src/IronPython/Compiler/Ast/Arg.cs | 8 +- Src/IronPython/Compiler/Ast/AstMethods.cs | 1 + Src/IronPython/Compiler/Ast/CallExpression.cs | 191 +++++++++++++---- .../Compiler/Ast/DictionaryExpression.cs | 1 + Src/IronPython/Compiler/Ast/SetExpression.cs | 4 - Src/IronPython/Compiler/Parser.cs | 27 +-- Src/IronPython/Modules/_ast.cs | 2 +- .../Runtime/Operations/PythonOps.cs | 21 ++ Src/StdLib/Lib/test/test_unpack_ex.py | 194 +++++++++++++++++- 9 files changed, 379 insertions(+), 70 deletions(-) diff --git a/Src/IronPython/Compiler/Ast/Arg.cs b/Src/IronPython/Compiler/Ast/Arg.cs index 042fefe19..4b9311605 100644 --- a/Src/IronPython/Compiler/Ast/Arg.cs +++ b/Src/IronPython/Compiler/Ast/Arg.cs @@ -2,22 +2,20 @@ // 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 IronPython.Runtime; -using Microsoft.Scripting; using Microsoft.Scripting.Actions; namespace IronPython.Compiler.Ast { public class Arg : Node { public Arg(Expression expression) : this(null, expression) { } - public Arg(string name, Expression expression) { + public Arg(string? name, Expression expression) { Name = name; Expression = expression; } - public string Name { get; } + public string? Name { get; } public Expression Expression { get; } diff --git a/Src/IronPython/Compiler/Ast/AstMethods.cs b/Src/IronPython/Compiler/Ast/AstMethods.cs index 6c7a30a3e..6d8baac24 100644 --- a/Src/IronPython/Compiler/Ast/AstMethods.cs +++ b/Src/IronPython/Compiler/Ast/AstMethods.cs @@ -80,6 +80,7 @@ internal static class AstMethods { public static readonly MethodInfo GeneratorCheckThrowableAndReturnSendValue = GetMethod((Func)PythonOps.GeneratorCheckThrowableAndReturnSendValue); // methods matching Python opcodes + public static readonly MethodInfo DictMerge = GetMethod((Action)PythonOps.DictMerge); 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); diff --git a/Src/IronPython/Compiler/Ast/CallExpression.cs b/Src/IronPython/Compiler/Ast/CallExpression.cs index 8ef9ea789..94c0de8ab 100644 --- a/Src/IronPython/Compiler/Ast/CallExpression.cs +++ b/Src/IronPython/Compiler/Ast/CallExpression.cs @@ -2,7 +2,7 @@ // 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; @@ -10,19 +10,21 @@ using System.Linq; using System.Runtime.CompilerServices; +using IronPython.Runtime; + using Microsoft.Scripting.Actions; -using Microsoft.Scripting.Utils; using Microsoft.Scripting.Interpreter; -using Microsoft.Scripting.Runtime; +using Microsoft.Scripting.Utils; -using IronPython.Runtime; -using IronPython.Runtime.Operations; -using IronPython.Runtime.Types; +using AstUtils = Microsoft.Scripting.Ast.Utils; +using MSAst = System.Linq.Expressions; namespace IronPython.Compiler.Ast { public class CallExpression : Expression, IInstructionProvider { public CallExpression(Expression target, Arg[] args) { + // TODO: use two arrays (args/keywords) instead + if (args == null) throw new ArgumentNullException(nameof(args)); Target = target; Args = args; } @@ -36,55 +38,168 @@ public CallExpression(Expression target, Arg[] args) { public bool NeedsLocalsDictionary() { if (!(Target is NameExpression nameExpr)) return false; - if (Args.Length == 0) { - if (nameExpr.Name == "locals") return true; - if (nameExpr.Name == "vars") return true; - if (nameExpr.Name == "dir") return true; - return false; - } else if (Args.Length == 1 && (nameExpr.Name == "dir" || nameExpr.Name == "vars")) { - if (Args[0].Name == "*" || Args[0].Name == "**") { - // could be splatting empty list or dict resulting in 0-param call which needs context - return true; - } - } else if (Args.Length == 2 && (nameExpr.Name == "dir" || nameExpr.Name == "vars")) { - if (Args[0].Name == "*" && Args[1].Name == "**") { - // could be splatting empty list and dict resulting in 0-param call which needs context - return true; - } - } else { - if (nameExpr.Name == "eval" || nameExpr.Name == "exec") return true; + if (nameExpr.Name == "eval" || nameExpr.Name == "exec") return true; + if (nameExpr.Name == "dir" || nameExpr.Name == "vars" || nameExpr.Name == "locals") { + // could be splatting empty list or dict resulting in 0-param call which needs context + return Args.All(arg => arg.Name == "*" || arg.Name == "**"); } + return false; } public override MSAst.Expression Reduce() { Arg[] args = Args; - if(Args.Length == 0 && ImplicitArgs.Count > 0) { + if (Args.Length == 0 && ImplicitArgs.Count > 0) { args = ImplicitArgs.ToArray(); } - MSAst.Expression[] values = new MSAst.Expression[args.Length + 2]; - Argument[] kinds = new Argument[args.Length]; + SplitArgs(args, out var simpleArgs, out var listArgs, out var namedArgs, out var dictArgs, out var numDict); + + Argument[] kinds = new Argument[simpleArgs.Length + Math.Min(listArgs.Length, 1) + namedArgs.Length + (dictArgs.Length - numDict) + Math.Min(numDict, 1)]; + MSAst.Expression[] values = new MSAst.Expression[2 + kinds.Length]; values[0] = Parent.LocalContext; values[1] = Target; - for (int i = 0; i < args.Length; i++) { - kinds[i] = args[i].GetArgumentInfo(); - values[i + 2] = args[i].Expression; + int i = 0; + + // add simple arguments + foreach (var arg in simpleArgs) { + kinds[i] = arg.GetArgumentInfo(); + values[i + 2] = arg.Expression; + i++; + } + + // unpack list arguments + if (listArgs.Length > 0) { + var arg = listArgs[0]; + Debug.Assert(arg.GetArgumentInfo().Kind == ArgumentType.List); + kinds[i] = arg.GetArgumentInfo(); + values[i + 2] = UnpackListHelper(listArgs); + i++; + } + + // add named arguments + foreach (var arg in namedArgs) { + kinds[i] = arg.GetArgumentInfo(); + values[i + 2] = arg.Expression; + i++; + } + + // add named arguments specified after a dict unpack + if (dictArgs.Length != numDict) { + foreach (var arg in dictArgs) { + var info = arg.GetArgumentInfo(); + if (info.Kind == ArgumentType.Named) { + kinds[i] = info; + values[i + 2] = arg.Expression; + i++; + } + } + } + + // unpack dict arguments + if (dictArgs.Length > 0) { + var arg = dictArgs[0]; + Debug.Assert(arg.GetArgumentInfo().Kind == ArgumentType.Dictionary); + kinds[i] = arg.GetArgumentInfo(); + values[i + 2] = UnpackDictHelper(Parent.LocalContext, dictArgs); } return Parent.Invoke( new CallSignature(kinds), values ); + + static void SplitArgs(Arg[] args, out ReadOnlySpan simpleArgs, out ReadOnlySpan listArgs, out ReadOnlySpan namedArgs, out ReadOnlySpan dictArgs, out int numDict) { + if (args.Length == 0) { + simpleArgs = default; + listArgs = default; + namedArgs = default; + dictArgs = default; + numDict = 0; + return; + } + + int idxSimple = args.Length; + int idxList = args.Length; + int idxNamed = args.Length; + int idxDict = args.Length; + numDict = 0; + + // we want idxSimple <= idxList <= idxNamed <= idxDict + for (var i = args.Length - 1; i >= 0; i--) { + var arg = args[i]; + var info = arg.GetArgumentInfo(); + switch (info.Kind) { + case ArgumentType.Simple: + idxSimple = i; + break; + case ArgumentType.List: + idxList = i; + break; + case ArgumentType.Named: + idxNamed = i; + break; + case ArgumentType.Dictionary: + idxDict = i; + numDict++; + break; + default: + throw new InvalidOperationException(); + } + } + dictArgs = args.AsSpan(idxDict); + if (idxNamed > idxDict) idxNamed = idxDict; + namedArgs = args.AsSpan(idxNamed, idxDict - idxNamed); + if (idxList > idxNamed) idxList = idxNamed; + listArgs = args.AsSpan(idxList, idxNamed - idxList); + if (idxSimple > idxList) idxSimple = idxList; + simpleArgs = args.AsSpan(idxSimple, idxList - idxSimple); + } + + static MSAst.Expression UnpackListHelper(ReadOnlySpan args) { + Debug.Assert(args.Length > 0); + Debug.Assert(args[0].GetArgumentInfo().Kind == ArgumentType.List); + if (args.Length == 1) return args[0].Expression; + + var expressions = new ReadOnlyCollectionBuilder(args.Length + 2); + var varExpr = Expression.Variable(typeof(PythonList), "$coll"); + expressions.Add(Expression.Assign(varExpr, Expression.Call(AstMethods.MakeEmptyList))); + foreach (var arg in args) { + if (arg.GetArgumentInfo().Kind == ArgumentType.List) { + expressions.Add(Expression.Call(AstMethods.ListExtend, varExpr, AstUtils.Convert(arg.Expression, typeof(object)))); + } else { + expressions.Add(Expression.Call(AstMethods.ListAppend, varExpr, AstUtils.Convert(arg.Expression, typeof(object)))); + } + } + expressions.Add(varExpr); + return Expression.Block(typeof(PythonList), new MSAst.ParameterExpression[] { varExpr }, expressions); + } + + static MSAst.Expression UnpackDictHelper(MSAst.Expression context, ReadOnlySpan args) { + Debug.Assert(args.Length > 0); + Debug.Assert(args[0].GetArgumentInfo().Kind == ArgumentType.Dictionary); + if (args.Length == 1) return args[0].Expression; + + var expressions = new List(args.Length + 2); + var varExpr = Expression.Variable(typeof(PythonDictionary), "$dict"); + expressions.Add(Expression.Assign(varExpr, Expression.Call(AstMethods.MakeEmptyDict))); + foreach (var arg in args) { + if (arg.GetArgumentInfo().Kind == ArgumentType.Dictionary) { + expressions.Add(Expression.Call(AstMethods.DictMerge, context, varExpr, arg.Expression)); + } + } + expressions.Add(varExpr); + return Expression.Block(typeof(PythonDictionary), new MSAst.ParameterExpression[] { varExpr }, expressions); + } } #region IInstructionProvider Members void IInstructionProvider.AddInstructions(LightCompiler compiler) { Arg[] args = Args; - if(args.Length == 0 && ImplicitArgs.Count > 0) { + if (args.Length == 0 && ImplicitArgs.Count > 0) { args = ImplicitArgs.ToArray(); } @@ -158,9 +273,9 @@ void IInstructionProvider.AddInstructions(LightCompiler compiler) { compiler.Instructions.Emit(new Invoke6Instruction(Parent.PyContext)); return; - // *** END GENERATED CODE *** + // *** END GENERATED CODE *** - #endregion + #endregion } compiler.Compile(Reduce()); } @@ -168,17 +283,9 @@ void IInstructionProvider.AddInstructions(LightCompiler compiler) { #endregion private abstract class InvokeInstruction : Instruction { - public override int ProducedStack { - get { - return 1; - } - } + public override int ProducedStack => 1; - public override string InstructionName { - get { - return "Python Invoke" + (ConsumedStack - 1); - } - } + public override string InstructionName => "Python Invoke" + (ConsumedStack - 1); } #region Generated Python Call Expression Instructions diff --git a/Src/IronPython/Compiler/Ast/DictionaryExpression.cs b/Src/IronPython/Compiler/Ast/DictionaryExpression.cs index ebd3304f8..6a1929642 100644 --- a/Src/IronPython/Compiler/Ast/DictionaryExpression.cs +++ b/Src/IronPython/Compiler/Ast/DictionaryExpression.cs @@ -22,6 +22,7 @@ public class DictionaryExpression : Expression, IInstructionProvider { private static readonly MSAst.Expression EmptyDictExpression = Expression.Call(AstMethods.MakeEmptyDict); public DictionaryExpression(params SliceExpression[] items) { + // TODO: use two arrays instead of SliceExpression foreach (var item in items) { if (item.SliceStart is null) _hasNullKey = true; if (item.SliceStop is null) throw PythonOps.ValueError("None disallowed in expression list"); diff --git a/Src/IronPython/Compiler/Ast/SetExpression.cs b/Src/IronPython/Compiler/Ast/SetExpression.cs index 4165d9058..4d9a337ce 100644 --- a/Src/IronPython/Compiler/Ast/SetExpression.cs +++ b/Src/IronPython/Compiler/Ast/SetExpression.cs @@ -29,10 +29,6 @@ public SetExpression(params Expression[] 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); } diff --git a/Src/IronPython/Compiler/Parser.cs b/Src/IronPython/Compiler/Parser.cs index b52e5d6f2..e14ab6a8a 100644 --- a/Src/IronPython/Compiler/Parser.cs +++ b/Src/IronPython/Compiler/Parser.cs @@ -2901,31 +2901,24 @@ private void PushFunction(FunctionDefinition function) { } private CallExpression FinishCallExpr(Expression target, params Arg[] args) { - bool hasArgsTuple = false; - bool hasKeywordDict = false; - int keywordCount = 0; - int extraArgs = 0; + bool hasKeyword = false; + bool hasKeywordUnpacking = false; foreach (Arg arg in args) { if (arg.Name == null) { - if (hasArgsTuple || hasKeywordDict || keywordCount > 0) { - ReportSyntaxError(IronPython.Resources.NonKeywordAfterKeywordArg); + if (hasKeywordUnpacking) { + ReportSyntaxError(arg.StartIndex, arg.EndIndex, "positional argument follows keyword argument unpacking"); + } else if (hasKeyword) { + ReportSyntaxError(arg.StartIndex, arg.EndIndex, "positional argument follows keyword argument"); } } else if (arg.Name == "*") { - if (hasArgsTuple || hasKeywordDict) { - ReportSyntaxError(IronPython.Resources.OneListArgOnly); + if (hasKeywordUnpacking) { + ReportSyntaxError(arg.StartIndex, arg.EndIndex, "iterable argument unpacking follows keyword argument unpacking"); } - hasArgsTuple = true; extraArgs++; } else if (arg.Name == "**") { - if (hasKeywordDict) { - ReportSyntaxError(IronPython.Resources.OneKeywordArgOnly); - } - hasKeywordDict = true; extraArgs++; + hasKeywordUnpacking = true; } else { - if (hasKeywordDict) { - ReportSyntaxError(IronPython.Resources.KeywordOutOfSequence); - } - keywordCount++; + hasKeyword = true; } } diff --git a/Src/IronPython/Modules/_ast.cs b/Src/IronPython/Modules/_ast.cs index 6d06f406f..e7be0be03 100755 --- a/Src/IronPython/Modules/_ast.cs +++ b/Src/IronPython/Modules/_ast.cs @@ -1283,7 +1283,7 @@ internal Dict(DictionaryExpression expr) keys = new PythonList(expr.Items.Count); values = new PythonList(expr.Items.Count); foreach (SliceExpression item in expr.Items) { - keys.Add(Convert(item.SliceStart)); + keys.Add(item.SliceStart is null ? null : Convert(item.SliceStart)); values.Add(Convert(item.SliceStop)); } } diff --git a/Src/IronPython/Runtime/Operations/PythonOps.cs b/Src/IronPython/Runtime/Operations/PythonOps.cs index 6f2d995e4..0b88f6249 100644 --- a/Src/IronPython/Runtime/Operations/PythonOps.cs +++ b/Src/IronPython/Runtime/Operations/PythonOps.cs @@ -1486,6 +1486,27 @@ public static PythonTuple MakeTupleFromSequence(object items) { return PythonTuple.Make(items); } + /// + /// DICT_MERGE + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static void DictMerge(CodeContext context, PythonDictionary dict, object? item) { + // call dict.keys() + if (!PythonTypeOps.TryInvokeUnaryOperator(context, item, "keys", out object keys)) { + throw TypeError($"'{PythonTypeOps.GetName(item)}' object is not a mapping"); + } + + // enumerate the keys getting their values + IEnumerator enumerator = GetEnumerator(keys); + while (enumerator.MoveNext()) { + object? o = enumerator.Current; + if (dict.ContainsKey(o) && (o is string || o is Extensible)) { + throw TypeError($"function got multiple values for keyword argument '{o}'"); + } + dict[o] = PythonOps.GetIndex(context, item, o); + } + } + /// /// DICT_UPDATE /// diff --git a/Src/StdLib/Lib/test/test_unpack_ex.py b/Src/StdLib/Lib/test/test_unpack_ex.py index d237434bb..f441bd45e 100644 --- a/Src/StdLib/Lib/test/test_unpack_ex.py +++ b/Src/StdLib/Lib/test/test_unpack_ex.py @@ -71,8 +71,193 @@ >>> a == 0 and b == [1, 2, 3] and c == 4 and d == [0, 1, 2, 3] and e == 4 True +Assignment unpacking + + >>> a, b, *c = range(5) + >>> a, b, c + (0, 1, [2, 3, 4]) + >>> *a, b, c = a, b, *c + >>> a, b, c + ([0, 1, 2], 3, 4) + +Set display element unpacking + + >>> a = [1, 2, 3] + >>> sorted({1, *a, 0, 4}) + [0, 1, 2, 3, 4] + + >>> {1, *1, 0, 4} + Traceback (most recent call last): + ... + TypeError: 'int' object is not iterable + +Dict display element unpacking + + >>> kwds = {'z': 0, 'w': 12} + >>> sorted({'x': 1, 'y': 2, **kwds}.items()) + [('w', 12), ('x', 1), ('y', 2), ('z', 0)] + + >>> sorted({**{'x': 1}, 'y': 2, **{'z': 3}}.items()) + [('x', 1), ('y', 2), ('z', 3)] + + >>> sorted({**{'x': 1}, 'y': 2, **{'x': 3}}.items()) + [('x', 3), ('y', 2)] + + >>> sorted({**{'x': 1}, **{'x': 3}, 'x': 4}.items()) + [('x', 4)] + + >>> {**{}} + {} + + >>> a = {} + >>> {**a}[0] = 1 + >>> a + {} + + >>> {**1} + Traceback (most recent call last): + ... + TypeError: 'int' object is not a mapping + + >>> {**[]} + Traceback (most recent call last): + ... + TypeError: 'list' object is not a mapping + + >>> len(eval("{" + ", ".join("**{{{}: {}}}".format(i, i) + ... for i in range(1000)) + "}")) + 1000 + + >>> {0:1, **{0:2}, 0:3, 0:4} + {0: 4} + +List comprehension element unpacking + + >>> a, b, c = [0, 1, 2], 3, 4 + >>> [*a, b, c] + [0, 1, 2, 3, 4] + + >>> l = [a, (3, 4), {5}, {6: None}, (i for i in range(7, 10))] + >>> [*item for item in l] + Traceback (most recent call last): + ... + SyntaxError: iterable unpacking cannot be used in comprehension + + >>> [*[0, 1] for i in range(10)] + Traceback (most recent call last): + ... + SyntaxError: iterable unpacking cannot be used in comprehension + + >>> [*'a' for i in range(10)] + Traceback (most recent call last): + ... + SyntaxError: iterable unpacking cannot be used in comprehension + + >>> [*[] for i in range(10)] + Traceback (most recent call last): + ... + SyntaxError: iterable unpacking cannot be used in comprehension + +Generator expression in function arguments + + >>> list(*x for x in (range(5) for i in range(3))) + Traceback (most recent call last): + ... + list(*x for x in (range(5) for i in range(3))) + ^ + SyntaxError: invalid syntax + + >>> dict(**x for x in [{1:2}]) + Traceback (most recent call last): + ... + dict(**x for x in [{1:2}]) + ^ + SyntaxError: invalid syntax + +Iterable argument unpacking + + >>> print(*[1], *[2], 3) + 1 2 3 + +Make sure that they don't corrupt the passed-in dicts. + + >>> def f(x, y): + ... print(x, y) + ... + >>> original_dict = {'x': 1} + >>> f(**original_dict, y=2) + 1 2 + >>> original_dict + {'x': 1} + Now for some failures +Make sure the raised errors are right for keyword argument unpackings + + >>> from collections.abc import MutableMapping + >>> class CrazyDict(MutableMapping): + ... def __init__(self): + ... self.d = {} + ... + ... def __iter__(self): + ... for x in self.d.__iter__(): + ... if x == 'c': + ... self.d['z'] = 10 + ... yield x + ... + ... def __getitem__(self, k): + ... return self.d[k] + ... + ... def __len__(self): + ... return len(self.d) + ... + ... def __setitem__(self, k, v): + ... self.d[k] = v + ... + ... def __delitem__(self, k): + ... del self.d[k] + ... + >>> d = CrazyDict() + >>> d.d = {chr(ord('a') + x): x for x in range(5)} + >>> e = {**d} + Traceback (most recent call last): + ... + RuntimeError: dictionary changed size during iteration + + >>> d.d = {chr(ord('a') + x): x for x in range(5)} + >>> def f(**kwargs): print(kwargs) + >>> f(**d) + Traceback (most recent call last): + ... + RuntimeError: dictionary changed size during iteration + +Overridden parameters + + >>> f(x=5, **{'x': 3}, y=2) + Traceback (most recent call last): + ... + TypeError: f() got multiple values for keyword argument 'x' + + >>> f(**{'x': 3}, x=5, y=2) + Traceback (most recent call last): + ... + TypeError: f() got multiple values for keyword argument 'x' + + >>> f(**{'x': 3}, **{'x': 5}, y=2) + Traceback (most recent call last): + ... + TypeError: function got multiple values for keyword argument 'x' + + >>> f(x=5, **{'x': 3}, **{'x': 2}) + Traceback (most recent call last): + ... + TypeError: function got multiple values for keyword argument 'x' + + >>> f(**{1: 3}, **{1: 5}) + Traceback (most recent call last): + ... + TypeError: f() keywords must be strings + Unpacking non-sequence >>> a, *b = 7 @@ -80,13 +265,20 @@ ... TypeError: 'int' object is not iterable -Unpacking sequence too short (note: changed error message as original message was from later version of CPython) +Unpacking sequence too short >>> a, *b, c, d, e = Seq() Traceback (most recent call last): ... ValueError: not enough values to unpack (expected at least 4, got 3) +Unpacking sequence too short and target appears last + + >>> a, b, c, d, *e = Seq() + Traceback (most recent call last): + ... + ValueError: not enough values to unpack (expected at least 4, got 3) + Unpacking a sequence where the test for too long raises a different kind of error From f5a6fe8bb7721c688b0684632783a0dc9c36029f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lozier?= Date: Sun, 4 Apr 2021 13:30:59 -0400 Subject: [PATCH 2/2] Fix test_grammar --- Src/StdLib/Lib/test/test_grammar.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/Src/StdLib/Lib/test/test_grammar.py b/Src/StdLib/Lib/test/test_grammar.py index 6f3b7fc7a..06d754278 100644 --- a/Src/StdLib/Lib/test/test_grammar.py +++ b/Src/StdLib/Lib/test/test_grammar.py @@ -1,7 +1,8 @@ # Python test set -- part 1, grammar. # This just tests whether the parser accepts them all. -from test.support import run_unittest, check_syntax_error +from test.support import check_syntax_error +import inspect import unittest import sys # testing import * @@ -205,6 +206,7 @@ def d01(a=1): pass d01(1) d01(*(1,)) d01(*[] or [2]) + d01(*() or (), *{} and (), **() or {}) d01(**{'a':2}) d01(**{'a':2} or {}) def d11(a, b=1): pass @@ -298,8 +300,12 @@ def f(*args, **kwargs): return args, kwargs self.assertEqual(f(1, x=2, *[3, 4], y=5), ((1, 3, 4), {'x':2, 'y':5})) - self.assertRaises(SyntaxError, eval, "f(1, *(2,3), 4)") + self.assertEqual(f(1, *(2,3), 4), ((1, 2, 3, 4), {})) self.assertRaises(SyntaxError, eval, "f(1, x=2, *(3,4), x=5)") + self.assertEqual(f(**{'eggs':'scrambled', 'spam':'fried'}), + ((), {'eggs':'scrambled', 'spam':'fried'})) + self.assertEqual(f(spam='fried', **{'eggs':'scrambled'}), + ((), {'eggs':'scrambled', 'spam':'fried'})) # argument annotation tests def f(x) -> list: pass @@ -988,7 +994,7 @@ def test_if_else_expr(self): # Test ifelse expressions in various cases def _checkeval(msg, ret): "helper to check that evaluation of expressions is done correctly" - print(x) + print(msg) return ret # the next line is not allowed anymore @@ -1019,9 +1025,20 @@ def test_paren_evaluation(self): self.assertFalse((False is 2) is 3) self.assertFalse(False is 2 is 3) + def test_matrix_mul(self): + # This is not intended to be a comprehensive test, rather just to be few + # samples of the @ operator in test_grammar.py. + class M: + def __matmul__(self, o): + return 4 + def __imatmul__(self, o): + self.other = o + return self + m = M() + self.assertEqual(m @ m, 4) + m @= 42 + self.assertEqual(m.other, 42) -def test_main(): - run_unittest(TokenTests, GrammarTests) if __name__ == '__main__': - test_main() + unittest.main()