diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index d99f823976b..fc897d3b4c7 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -10,6 +10,7 @@ badcert badsyntax baseinfo basetype +binop boolop BUILDSTDLIB bxor @@ -73,6 +74,7 @@ HASPOINTER HASSTRUCT HASUNION heaptype +hexdigit HIGHRES IFUNC IMMUTABLETYPE diff --git a/Lib/test/test_ast/data/ast_repr.txt b/Lib/test/test_ast/data/ast_repr.txt new file mode 100644 index 00000000000..1c1985519cd --- /dev/null +++ b/Lib/test/test_ast/data/ast_repr.txt @@ -0,0 +1,214 @@ +Module(body=[Expr(value=Constant(value='module docstring', kind=None))], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Expr(value=Constant(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[arg(...)], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[arg(...)], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=arg(...), kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=arg(...), kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=arg(...), kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=arg(...), kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=arg(...), defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[arg(...), ..., arg(...)], vararg=arg(...), kwonlyargs=[arg(...)], kw_defaults=[Constant(...)], kwarg=arg(...), defaults=[Constant(...), ..., Dict(...)]), body=[Expr(value=Constant(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=Subscript(value=Name(...), slice=Tuple(...), ctx=Load(...)), type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=Subscript(value=Name(...), slice=Tuple(...), ctx=Load(...)), type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=Subscript(value=Name(...), slice=Tuple(...), ctx=Load(...)), type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[ClassDef(name='C', bases=[], keywords=[], body=[Pass()], decorator_list=[], type_params=[])], type_ignores=[]) +Module(body=[ClassDef(name='C', bases=[], keywords=[], body=[Expr(value=Constant(...))], decorator_list=[], type_params=[])], type_ignores=[]) +Module(body=[ClassDef(name='C', bases=[Name(id='object', ctx=Load(...))], keywords=[], body=[Pass()], decorator_list=[], type_params=[])], type_ignores=[]) +Module(body=[ClassDef(name='C', bases=[Name(id='A', ctx=Load(...)), Name(id='B', ctx=Load(...))], keywords=[], body=[Pass()], decorator_list=[], type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Return(value=Constant(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Return(value=None)], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[Delete(targets=[Name(id='v', ctx=Del(...))])], type_ignores=[]) +Module(body=[Assign(targets=[Name(id='v', ctx=Store(...))], value=Constant(value=1, kind=None), type_comment=None)], type_ignores=[]) +Module(body=[Assign(targets=[Tuple(elts=[Name(...), Name(...)], ctx=Store(...))], value=Name(id='c', ctx=Load(...)), type_comment=None)], type_ignores=[]) +Module(body=[Assign(targets=[Tuple(elts=[Name(...), Name(...)], ctx=Store(...))], value=Name(id='c', ctx=Load(...)), type_comment=None)], type_ignores=[]) +Module(body=[Assign(targets=[List(elts=[Name(...), Name(...)], ctx=Store(...))], value=Name(id='c', ctx=Load(...)), type_comment=None)], type_ignores=[]) +Module(body=[Assign(targets=[Subscript(value=Name(...), slice=Name(...), ctx=Store(...))], value=Name(id='c', ctx=Load(...)), type_comment=None)], type_ignores=[]) +Module(body=[AnnAssign(target=Name(id='x', ctx=Store(...)), annotation=Subscript(value=Name(...), slice=Tuple(...), ctx=Load(...)), value=None, simple=1)], type_ignores=[]) +Module(body=[AnnAssign(target=Name(id='x', ctx=Store(...)), annotation=Subscript(value=Name(...), slice=Tuple(...), ctx=Load(...)), value=None, simple=1)], type_ignores=[]) +Module(body=[AnnAssign(target=Name(id='x', ctx=Store(...)), annotation=Subscript(value=Name(...), slice=Tuple(...), ctx=Load(...)), value=None, simple=1)], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=Add(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=Sub(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=Mult(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=MatMult(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=Div(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=Mod(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=Pow(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=LShift(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=RShift(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=BitOr(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=BitXor(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=BitAnd(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[AugAssign(target=Name(id='v', ctx=Store(...)), op=FloorDiv(), value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[For(target=Name(id='v', ctx=Store(...)), iter=Name(id='v', ctx=Load(...)), body=[Pass()], orelse=[], type_comment=None)], type_ignores=[]) +Module(body=[For(target=Name(id='v', ctx=Store(...)), iter=Name(id='v', ctx=Load(...)), body=[Pass()], orelse=[Pass()], type_comment=None)], type_ignores=[]) +Module(body=[While(test=Name(id='v', ctx=Load(...)), body=[Pass()], orelse=[])], type_ignores=[]) +Module(body=[While(test=Name(id='v', ctx=Load(...)), body=[Pass()], orelse=[Pass()])], type_ignores=[]) +Module(body=[If(test=Name(id='v', ctx=Load(...)), body=[Pass()], orelse=[])], type_ignores=[]) +Module(body=[If(test=Name(id='a', ctx=Load(...)), body=[Pass()], orelse=[If(test=Name(...), body=[Pass(...)], orelse=[])])], type_ignores=[]) +Module(body=[If(test=Name(id='a', ctx=Load(...)), body=[Pass()], orelse=[Pass()])], type_ignores=[]) +Module(body=[If(test=Name(id='a', ctx=Load(...)), body=[Pass()], orelse=[If(test=Name(...), body=[Pass(...)], orelse=[Pass(...)])])], type_ignores=[]) +Module(body=[If(test=Name(id='a', ctx=Load(...)), body=[Pass()], orelse=[If(test=Name(...), body=[Pass(...)], orelse=[If(...)])])], type_ignores=[]) +Module(body=[With(items=[withitem(context_expr=Name(...), optional_vars=None)], body=[Pass()], type_comment=None)], type_ignores=[]) +Module(body=[With(items=[withitem(context_expr=Name(...), optional_vars=None), withitem(context_expr=Name(...), optional_vars=None)], body=[Pass()], type_comment=None)], type_ignores=[]) +Module(body=[With(items=[withitem(context_expr=Name(...), optional_vars=Name(...))], body=[Pass()], type_comment=None)], type_ignores=[]) +Module(body=[With(items=[withitem(context_expr=Name(...), optional_vars=Name(...)), withitem(context_expr=Name(...), optional_vars=Name(...))], body=[Pass()], type_comment=None)], type_ignores=[]) +Module(body=[With(items=[withitem(context_expr=Name(...), optional_vars=Name(...))], body=[Pass()], type_comment=None)], type_ignores=[]) +Module(body=[With(items=[withitem(context_expr=Name(...), optional_vars=None), withitem(context_expr=Name(...), optional_vars=None)], body=[Pass()], type_comment=None)], type_ignores=[]) +Module(body=[Raise(exc=None, cause=None)], type_ignores=[]) +Module(body=[Raise(exc=Call(func=Name(...), args=[Constant(...)], keywords=[]), cause=None)], type_ignores=[]) +Module(body=[Raise(exc=Name(id='Exception', ctx=Load(...)), cause=None)], type_ignores=[]) +Module(body=[Raise(exc=Call(func=Name(...), args=[Constant(...)], keywords=[]), cause=Constant(value=None, kind=None))], type_ignores=[]) +Module(body=[Try(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name=None, body=[Pass(...)])], orelse=[], finalbody=[])], type_ignores=[]) +Module(body=[Try(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name='exc', body=[Pass(...)])], orelse=[], finalbody=[])], type_ignores=[]) +Module(body=[Try(body=[Pass()], handlers=[], orelse=[], finalbody=[Pass()])], type_ignores=[]) +Module(body=[TryStar(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name=None, body=[Pass(...)])], orelse=[], finalbody=[])], type_ignores=[]) +Module(body=[TryStar(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name='exc', body=[Pass(...)])], orelse=[], finalbody=[])], type_ignores=[]) +Module(body=[Try(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name=None, body=[Pass(...)])], orelse=[Pass()], finalbody=[Pass()])], type_ignores=[]) +Module(body=[Try(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name='exc', body=[Pass(...)])], orelse=[Pass()], finalbody=[Pass()])], type_ignores=[]) +Module(body=[TryStar(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name='exc', body=[Pass(...)])], orelse=[Pass()], finalbody=[Pass()])], type_ignores=[]) +Module(body=[Assert(test=Name(id='v', ctx=Load(...)), msg=None)], type_ignores=[]) +Module(body=[Assert(test=Name(id='v', ctx=Load(...)), msg=Constant(value='message', kind=None))], type_ignores=[]) +Module(body=[Import(names=[alias(name='sys', asname=None)])], type_ignores=[]) +Module(body=[Import(names=[alias(name='foo', asname='bar')])], type_ignores=[]) +Module(body=[ImportFrom(module='sys', names=[alias(name='x', asname='y')], level=0)], type_ignores=[]) +Module(body=[ImportFrom(module='sys', names=[alias(name='v', asname=None)], level=0)], type_ignores=[]) +Module(body=[Global(names=['v'])], type_ignores=[]) +Module(body=[Expr(value=Constant(value=1, kind=None))], type_ignores=[]) +Module(body=[Pass()], type_ignores=[]) +Module(body=[For(target=Name(id='v', ctx=Store(...)), iter=Name(id='v', ctx=Load(...)), body=[Break()], orelse=[], type_comment=None)], type_ignores=[]) +Module(body=[For(target=Name(id='v', ctx=Store(...)), iter=Name(id='v', ctx=Load(...)), body=[Continue()], orelse=[], type_comment=None)], type_ignores=[]) +Module(body=[For(target=Tuple(elts=[Name(...), Name(...)], ctx=Store(...)), iter=Name(id='c', ctx=Load(...)), body=[Pass()], orelse=[], type_comment=None)], type_ignores=[]) +Module(body=[For(target=Tuple(elts=[Name(...), Name(...)], ctx=Store(...)), iter=Name(id='c', ctx=Load(...)), body=[Pass()], orelse=[], type_comment=None)], type_ignores=[]) +Module(body=[For(target=List(elts=[Name(...), Name(...)], ctx=Store(...)), iter=Name(id='c', ctx=Load(...)), body=[Pass()], orelse=[], type_comment=None)], type_ignores=[]) +Module(body=[Expr(value=GeneratorExp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=DictComp(key=Name(...), value=Name(...), generators=[comprehension(...), comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=DictComp(key=Name(...), value=Name(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=SetComp(elt=Name(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=SetComp(elt=Name(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[AsyncFunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Expr(value=Constant(...)), Expr(value=Await(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[AsyncFunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[AsyncFor(target=Name(...), iter=Name(...), body=[Expr(...)], orelse=[Expr(...)], type_comment=None)], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[AsyncFunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[AsyncWith(items=[withitem(...)], body=[Expr(...)], type_comment=None)], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[Expr(value=Dict(keys=[None, Constant(...)], values=[Dict(...), Constant(...)]))], type_ignores=[]) +Module(body=[Expr(value=Set(elts=[Starred(...), Constant(...)]))], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Expr(value=Yield(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Expr(value=YieldFrom(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[AsyncFunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Expr(value=ListComp(...))], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[Name(id='deco1', ctx=Load(...)), ..., Call(func=Name(...), args=[Constant(...)], keywords=[])], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[AsyncFunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[Name(id='deco1', ctx=Load(...)), ..., Call(func=Name(...), args=[Constant(...)], keywords=[])], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[ClassDef(name='C', bases=[], keywords=[], body=[Pass()], decorator_list=[Name(id='deco1', ctx=Load(...)), ..., Call(func=Name(...), args=[Constant(...)], keywords=[])], type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[Call(func=Name(...), args=[GeneratorExp(...)], keywords=[])], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[Attribute(value=Attribute(...), attr='c', ctx=Load(...))], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[Expr(value=NamedExpr(target=Name(...), value=Constant(...)))], type_ignores=[]) +Module(body=[If(test=NamedExpr(target=Name(...), value=Call(...)), body=[Pass()], orelse=[])], type_ignores=[]) +Module(body=[While(test=NamedExpr(target=Name(...), value=Call(...)), body=[Pass()], orelse=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...), ..., arg(...)], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...)], vararg=None, kwonlyargs=[arg(...), arg(...)], kw_defaults=[None, None], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...)], vararg=None, kwonlyargs=[arg(...), arg(...)], kw_defaults=[None, None], kwarg=arg(...), defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...), arg(...)], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[Constant(...), ..., Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...)], vararg=None, kwonlyargs=[arg(...)], kw_defaults=[Constant(...)], kwarg=None, defaults=[Constant(...), Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...)], vararg=None, kwonlyargs=[arg(...)], kw_defaults=[None], kwarg=None, defaults=[Constant(...), Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...)], vararg=None, kwonlyargs=[arg(...)], kw_defaults=[Constant(...)], kwarg=arg(...), defaults=[Constant(...), Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[arg(...)], args=[arg(...)], vararg=None, kwonlyargs=[arg(...)], kw_defaults=[None], kwarg=arg(...), defaults=[Constant(...), Constant(...)]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[])], type_ignores=[]) +Module(body=[TypeAlias(name=Name(id='X', ctx=Store(...)), type_params=[], value=Name(id='int', ctx=Load(...)))], type_ignores=[]) +Module(body=[TypeAlias(name=Name(id='X', ctx=Store(...)), type_params=[TypeVar(name='T', bound=None, default_value=None)], value=Name(id='int', ctx=Load(...)))], type_ignores=[]) +Module(body=[TypeAlias(name=Name(id='X', ctx=Store(...)), type_params=[TypeVar(name='T', bound=None, default_value=None), ..., ParamSpec(name='P', default_value=None)], value=Tuple(elts=[Name(...), ..., Name(...)], ctx=Load(...)))], type_ignores=[]) +Module(body=[TypeAlias(name=Name(id='X', ctx=Store(...)), type_params=[TypeVar(name='T', bound=Name(...), default_value=None), ..., ParamSpec(name='P', default_value=None)], value=Tuple(elts=[Name(...), ..., Name(...)], ctx=Load(...)))], type_ignores=[]) +Module(body=[TypeAlias(name=Name(id='X', ctx=Store(...)), type_params=[TypeVar(name='T', bound=Tuple(...), default_value=None), ..., ParamSpec(name='P', default_value=None)], value=Tuple(elts=[Name(...), ..., Name(...)], ctx=Load(...)))], type_ignores=[]) +Module(body=[TypeAlias(name=Name(id='X', ctx=Store(...)), type_params=[TypeVar(name='T', bound=Name(...), default_value=Constant(...)), ..., ParamSpec(name='P', default_value=Constant(...))], value=Tuple(elts=[Name(...), ..., Name(...)], ctx=Load(...)))], type_ignores=[]) +Module(body=[ClassDef(name='X', bases=[], keywords=[], body=[Pass()], decorator_list=[], type_params=[TypeVar(name='T', bound=None, default_value=None)])], type_ignores=[]) +Module(body=[ClassDef(name='X', bases=[], keywords=[], body=[Pass()], decorator_list=[], type_params=[TypeVar(name='T', bound=None, default_value=None), ..., ParamSpec(name='P', default_value=None)])], type_ignores=[]) +Module(body=[ClassDef(name='X', bases=[], keywords=[], body=[Pass()], decorator_list=[], type_params=[TypeVar(name='T', bound=Name(...), default_value=None), ..., ParamSpec(name='P', default_value=None)])], type_ignores=[]) +Module(body=[ClassDef(name='X', bases=[], keywords=[], body=[Pass()], decorator_list=[], type_params=[TypeVar(name='T', bound=Tuple(...), default_value=None), ..., ParamSpec(name='P', default_value=None)])], type_ignores=[]) +Module(body=[ClassDef(name='X', bases=[], keywords=[], body=[Pass()], decorator_list=[], type_params=[TypeVar(name='T', bound=Name(...), default_value=Constant(...)), ..., ParamSpec(name='P', default_value=Constant(...))])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[TypeVar(name='T', bound=None, default_value=None)])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[TypeVar(name='T', bound=None, default_value=None), ..., ParamSpec(name='P', default_value=None)])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[TypeVar(name='T', bound=Name(...), default_value=None), ..., ParamSpec(name='P', default_value=None)])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[TypeVar(name='T', bound=Tuple(...), default_value=None), ..., ParamSpec(name='P', default_value=None)])], type_ignores=[]) +Module(body=[FunctionDef(name='f', args=arguments(posonlyargs=[], args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[], returns=None, type_comment=None, type_params=[TypeVar(name='T', bound=Name(...), default_value=Constant(...)), ..., ParamSpec(name='P', default_value=Constant(...))])], type_ignores=[]) +Module(body=[Match(subject=Name(id='x', ctx=Load(...)), cases=[match_case(pattern=MatchValue(...), guard=None, body=[Pass(...)])])], type_ignores=[]) +Module(body=[Match(subject=Name(id='x', ctx=Load(...)), cases=[match_case(pattern=MatchValue(...), guard=None, body=[Pass(...)]), match_case(pattern=MatchAs(...), guard=None, body=[Pass(...)])])], type_ignores=[]) +Module(body=[Expr(value=Constant(value=None, kind=None))], type_ignores=[]) +Module(body=[Expr(value=Constant(value=True, kind=None))], type_ignores=[]) +Module(body=[Expr(value=Constant(value=False, kind=None))], type_ignores=[]) +Module(body=[Expr(value=BoolOp(op=And(...), values=[Name(...), Name(...)]))], type_ignores=[]) +Module(body=[Expr(value=BoolOp(op=Or(...), values=[Name(...), Name(...)]))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=Add(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=Sub(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=Mult(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=Div(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=MatMult(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=FloorDiv(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=Pow(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=Mod(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=RShift(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=LShift(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=BitXor(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=BitOr(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=BinOp(left=Name(...), op=BitAnd(...), right=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=UnaryOp(op=Not(...), operand=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=UnaryOp(op=UAdd(...), operand=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=UnaryOp(op=USub(...), operand=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=UnaryOp(op=Invert(...), operand=Name(...)))], type_ignores=[]) +Module(body=[Expr(value=Lambda(args=arguments(...), body=Constant(...)))], type_ignores=[]) +Module(body=[Expr(value=Dict(keys=[Constant(...)], values=[Constant(...)]))], type_ignores=[]) +Module(body=[Expr(value=Dict(keys=[], values=[]))], type_ignores=[]) +Module(body=[Expr(value=Set(elts=[Constant(...)]))], type_ignores=[]) +Module(body=[Expr(value=Dict(keys=[Constant(...)], values=[Constant(...)]))], type_ignores=[]) +Module(body=[Expr(value=List(elts=[Constant(...), Constant(...)], ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=Tuple(elts=[Constant(...)], ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=Set(elts=[Constant(...), Constant(...)]))], type_ignores=[]) +Module(body=[Expr(value=ListComp(elt=Name(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=GeneratorExp(elt=Name(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=SetComp(elt=Name(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=DictComp(key=Name(...), value=Name(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=ListComp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=ListComp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=ListComp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=SetComp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=SetComp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=SetComp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=GeneratorExp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=GeneratorExp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=GeneratorExp(elt=Tuple(...), generators=[comprehension(...)]))], type_ignores=[]) +Module(body=[Expr(value=Compare(left=Constant(...), ops=[Lt(...), Lt(...)], comparators=[Constant(...), Constant(...)]))], type_ignores=[]) +Module(body=[Expr(value=Compare(left=Name(...), ops=[Eq(...)], comparators=[Name(...)]))], type_ignores=[]) +Module(body=[Expr(value=Compare(left=Name(...), ops=[LtE(...)], comparators=[Name(...)]))], type_ignores=[]) +Module(body=[Expr(value=Compare(left=Name(...), ops=[GtE(...)], comparators=[Name(...)]))], type_ignores=[]) +Module(body=[Expr(value=Compare(left=Name(...), ops=[NotEq(...)], comparators=[Name(...)]))], type_ignores=[]) +Module(body=[Expr(value=Compare(left=Name(...), ops=[Is(...)], comparators=[Name(...)]))], type_ignores=[]) +Module(body=[Expr(value=Compare(left=Name(...), ops=[IsNot(...)], comparators=[Name(...)]))], type_ignores=[]) +Module(body=[Expr(value=Compare(left=Name(...), ops=[In(...)], comparators=[Name(...)]))], type_ignores=[]) +Module(body=[Expr(value=Compare(left=Name(...), ops=[NotIn(...)], comparators=[Name(...)]))], type_ignores=[]) +Module(body=[Expr(value=Call(func=Name(...), args=[], keywords=[]))], type_ignores=[]) +Module(body=[Expr(value=Call(func=Name(...), args=[Constant(...), ..., Starred(...)], keywords=[keyword(...), keyword(...)]))], type_ignores=[]) +Module(body=[Expr(value=Call(func=Name(...), args=[Starred(...)], keywords=[]))], type_ignores=[]) +Module(body=[Expr(value=Call(func=Name(...), args=[GeneratorExp(...)], keywords=[]))], type_ignores=[]) +Module(body=[Expr(value=Constant(value=10, kind=None))], type_ignores=[]) +Module(body=[Expr(value=Constant(value=1j, kind=None))], type_ignores=[]) +Module(body=[Expr(value=Constant(value='string', kind=None))], type_ignores=[]) +Module(body=[Expr(value=Attribute(value=Name(...), attr='b', ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=Subscript(value=Name(...), slice=Slice(...), ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=Name(id='v', ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=List(elts=[Constant(...), ..., Constant(...)], ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=List(elts=[], ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=Tuple(elts=[Constant(...), ..., Constant(...)], ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=Tuple(elts=[Constant(...), ..., Constant(...)], ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=Tuple(elts=[], ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=Call(func=Attribute(...), args=[Subscript(...)], keywords=[]))], type_ignores=[]) +Module(body=[Expr(value=Subscript(value=List(...), slice=Slice(...), ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=Subscript(value=List(...), slice=Slice(...), ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=Subscript(value=List(...), slice=Slice(...), ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=Subscript(value=List(...), slice=Slice(...), ctx=Load(...)))], type_ignores=[]) +Module(body=[Expr(value=IfExp(test=Name(...), body=Call(...), orelse=Call(...)))], type_ignores=[]) +Module(body=[Expr(value=JoinedStr(values=[FormattedValue(...)]))], type_ignores=[]) +Module(body=[Expr(value=JoinedStr(values=[FormattedValue(...)]))], type_ignores=[]) +Module(body=[Expr(value=JoinedStr(values=[FormattedValue(...)]))], type_ignores=[]) +Module(body=[Expr(value=JoinedStr(values=[Constant(...), ..., Constant(...)]))], type_ignores=[]) +Module(body=[Expr(value=TemplateStr(values=[Interpolation(...)]))], type_ignores=[]) +Module(body=[Expr(value=TemplateStr(values=[Interpolation(...)]))], type_ignores=[]) +Module(body=[Expr(value=TemplateStr(values=[Interpolation(...)]))], type_ignores=[]) +Module(body=[Expr(value=TemplateStr(values=[Interpolation(...)]))], type_ignores=[]) +Module(body=[Expr(value=TemplateStr(values=[Constant(...), ..., Constant(...)]))], type_ignores=[]) \ No newline at end of file diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index ded5251ef66..d1711cb0f3f 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -97,7 +97,6 @@ def test_AST_objects(self): # "ast.AST constructor takes 0 positional arguments" ast.AST(2) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "type object 'ast.AST' has no attribute '_fields'" does not match "'AST' object has no attribute '_fields'" def test_AST_fields_NULL_check(self): # See: https://github.com/python/cpython/issues/126105 old_value = ast.AST._fields @@ -127,7 +126,6 @@ class X: support.gc_collect() self.assertIsNone(ref()) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_snippets(self): for input, output, kind in ((exec_tests, exec_results, "exec"), (single_tests, single_results, "single"), @@ -140,7 +138,6 @@ def test_snippets(self): with self.subTest(action="compiling", input=i, kind=kind): compile(ast_tree, "?", kind) - @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: expected some sort of expr, but got <_ast.TemplateStr object at 0x7e85c34e0> def test_ast_validation(self): # compile() is the only function that calls PyAST_Validate snippets_to_validate = exec_tests + single_tests + eval_tests @@ -170,7 +167,6 @@ def test_optimization_levels__debug__(self): self.assertIsInstance(res.body[0].value, ast.Name) self.assertEqual(res.body[0].value.id, expected) - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_invalid_position_information(self): invalid_linenos = [ (10, 1), (-10, -11), (10, -11), (-5, -2), (-5, 1) @@ -226,7 +222,6 @@ def test_negative_locations_for_compile(self): # This also must not crash: ast.parse(tree, optimize=2) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_docstring_optimization_single_node(self): # https://github.com/python/cpython/issues/137308 class_example1 = textwrap.dedent(''' @@ -293,7 +288,6 @@ async def some(): compile(mod, "a", "exec") compile(mod, "a", "exec", optimize=opt_level) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_docstring_optimization_multiple_nodes(self): # https://github.com/python/cpython/issues/137308 class_example = textwrap.dedent( @@ -415,7 +409,6 @@ def test_base_classes(self): self.assertIsSubclass(ast.comprehension, ast.AST) self.assertIsSubclass(ast.Gt, ast.AST) - @unittest.expectedFailure # TODO: RUSTPYTHON; type object 'Module' has no attribute '__annotations__' def test_field_attr_existence(self): for name, item in ast.__dict__.items(): # constructor has a different signature @@ -439,7 +432,6 @@ def _construct_ast_class(self, cls): kwargs[name] = self._construct_ast_class(typ) return cls(**kwargs) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: type object 'arguments' has no attribute '__annotations__' def test_arguments(self): x = ast.arguments() self.assertEqual(x._fields, ('posonlyargs', 'args', 'vararg', 'kwonlyargs', @@ -467,7 +459,6 @@ def test_field_attr_writable(self): x._fields = 666 self.assertEqual(x._fields, 666) - @unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered def test_classattrs(self): with self.assertWarns(DeprecationWarning): x = ast.Constant() @@ -538,7 +529,6 @@ def test_module(self): x = ast.Module(body, []) self.assertEqual(x.body, body) - @unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered def test_nodeclasses(self): # Zero arguments constructor explicitly allowed (but deprecated) with self.assertWarns(DeprecationWarning): @@ -590,7 +580,6 @@ def test_no_fields(self): x = ast.Sub() self.assertEqual(x._fields, ()) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'but got expr()' not found in 'expected some sort of expr, but got <_ast.expr object at 0x7e911a9a0>' def test_invalid_sum(self): pos = dict(lineno=2, col_offset=3) m = ast.Module([ast.Expr(ast.expr(**pos), **pos)], []) @@ -670,7 +659,6 @@ def test_issue39579_dotted_name_end_col_offset(self): attr_b = tree.body[0].decorator_list[0].value self.assertEqual(attr_b.end_col_offset, 4) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: None != 'withitem(expr context_expr, expr? optional_vars)' def test_ast_asdl_signature(self): self.assertEqual(ast.withitem.__doc__, "withitem(expr context_expr, expr? optional_vars)") self.assertEqual(ast.GtE.__doc__, "GtE") @@ -688,7 +676,6 @@ def test_compare_basics(self): ast.compare(ast.parse("x = 10;y = 20"), ast.parse("class C:pass")) ) - @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Cannot add list and tuple def test_compare_modified_ast(self): # The ast API is a bit underspecified. The objects are mutable, # and even _fields and _attributes are mutable. The compare() does @@ -808,7 +795,6 @@ def test_compare_attributes_option_missing_attribute(self): del a2.lineno self.assertTrue(ast.compare(a1, a2, compare_attributes=True)) - @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError not raised def test_positional_only_feature_version(self): ast.parse('def foo(x, /): ...', feature_version=(3, 8)) ast.parse('def bar(x=1, /): ...', feature_version=(3, 8)) @@ -824,20 +810,17 @@ def test_positional_only_feature_version(self): with self.assertRaises(SyntaxError): ast.parse('lambda x=1, /: ...', feature_version=(3, 7)) - @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError not raised def test_assignment_expression_feature_version(self): ast.parse('(x := 0)', feature_version=(3, 8)) with self.assertRaises(SyntaxError): ast.parse('(x := 0)', feature_version=(3, 7)) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised def test_pep750_tstring(self): code = 't""' ast.parse(code, feature_version=(3, 14)) with self.assertRaises(SyntaxError): ast.parse(code, feature_version=(3, 13)) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised def test_pep758_except_without_parens(self): code = textwrap.dedent(""" try: @@ -906,7 +889,6 @@ def test_pep758_except_with_single_expr(self): ast.parse(code, feature_version=(3, 14)) ast.parse(code, feature_version=(3, 13)) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised def test_pep758_except_star_without_parens(self): code = textwrap.dedent(""" try: @@ -922,7 +904,6 @@ def test_conditional_context_managers_parse_with_low_feature_version(self): # regression test for gh-115881 ast.parse('with (x() if y else z()): ...', feature_version=(3, 8)) - @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError not raised def test_exception_groups_feature_version(self): code = dedent(''' try: ... @@ -932,7 +913,6 @@ def test_exception_groups_feature_version(self): with self.assertRaises(SyntaxError): ast.parse(code, feature_version=(3, 10)) - @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError not raised def test_type_params_feature_version(self): samples = [ "type X = int", @@ -964,7 +944,6 @@ def test_invalid_major_feature_version(self): with self.assertRaises(ValueError): ast.parse('pass', feature_version=(4, 0)) - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_constant_as_name(self): for constant in "True", "False", "None": expr = ast.Expression(ast.Name(constant, ast.Load())) @@ -1072,7 +1051,6 @@ def test_none_checks(self) -> None: for node, attr, source in tests: self.assert_none_check(node, attr, source) - @unittest.expectedFailure # TODO: RUSTPYTHON; FileNotFoundError: [Errno 2] No such file or directory: '/Users/youknowone/Projects/RustPython/crates/pylib/Lib/test/test_ast/data/ast_repr.txt' def test_repr(self) -> None: snapshots = AST_REPR_DATA_FILE.read_text().split("\n") for test, snapshot in zip(ast_repr_get_test_cases(), snapshots, strict=True): @@ -1130,7 +1108,6 @@ def do(cls): yield from do(ast.AST) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickling(self): import pickle @@ -1266,7 +1243,6 @@ def test_replace_native(self): self.assertIs(getattr(repl, a), new_attr) self.assertFalse(ast.compare(node, repl, compare_attributes=True)) - @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: replace() does not support Name objects def test_replace_accept_known_class_fields(self): nid, ctx = object(), object() @@ -1283,7 +1259,6 @@ def test_replace_accept_known_class_fields(self): self.assertIs(repl.id, new_nid) self.assertIs(repl.ctx, node.ctx) # no changes - @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: replace() does not support Name objects def test_replace_accept_known_class_attributes(self): node = ast.parse('x').body[0].value self.assertEqual(node.id, 'x') @@ -1309,7 +1284,6 @@ def test_replace_accept_known_class_attributes(self): self.assertEqual(state['ctx'], node.ctx) self.assertEqual(state['lineno'], lineno) - @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: replace() does not support MyNode objects def test_replace_accept_known_custom_class_fields(self): class MyNode(ast.AST): _fields = ('name', 'data') @@ -1341,7 +1315,6 @@ class MyNode(ast.AST): self.assertIs(repl.name, node.name) self.assertIs(repl.data, repl_data) - @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: replace() does not support MyNode objects def test_replace_accept_known_custom_class_attributes(self): class MyNode(ast.AST): x = 0 @@ -1423,7 +1396,6 @@ def test_replace_reject_missing_field(self): self.assertIs(repl.id, 'y') self.assertIs(repl.ctx, context) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'FunctionDef' object has no attribute 'returns' def test_replace_accept_missing_field_with_default(self): node = ast.FunctionDef(name="foo", args=ast.arguments()) self.assertIs(node.returns, None) @@ -1783,7 +1755,6 @@ def test_increment_lineno(self): self.assertEqual(ast.increment_lineno(src).lineno, 2) self.assertIsNone(ast.increment_lineno(src).end_lineno) - @unittest.expectedFailure # TODO: RUSTPYTHON; IndexError: index out of range def test_increment_lineno_on_module(self): src = ast.parse(dedent("""\ a = 1 @@ -2046,7 +2017,6 @@ def stmt(self, stmt, msg=None): mod = ast.Module([stmt], []) self.mod(mod, msg) - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_module(self): m = ast.Interactive([ast.Expr(ast.Name("x", ast.Store()))]) self.mod(m, "must have Load context", "single") @@ -2103,7 +2073,6 @@ def fac(args): return ast.FunctionDef("x", args, [ast.Pass()], [], None, None, []) self._check_arguments(fac, self.stmt) - @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: class pattern defines no positional sub-patterns (__match_args__ missing) def test_funcdef_pattern_matching(self): # gh-104799: New fields on FunctionDef should be added at the end def matcher(node): @@ -2167,7 +2136,6 @@ def test_assign(self): ast.Name("y", ast.Store())), "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_augassign(self): aug = ast.AugAssign(ast.Name("x", ast.Load()), ast.Add(), ast.Name("y", ast.Load())) @@ -2176,7 +2144,6 @@ def test_augassign(self): ast.Name("y", ast.Store())) self.stmt(aug, "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_for(self): x = ast.Name("x", ast.Store()) y = ast.Name("y", ast.Load()) @@ -2190,7 +2157,6 @@ def test_for(self): self.stmt(ast.For(x, y, [e], []), "must have Load context") self.stmt(ast.For(x, y, [p], [e]), "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_while(self): self.stmt(ast.While(ast.Constant(3), [], []), "empty body on While") self.stmt(ast.While(ast.Name("x", ast.Store()), [ast.Pass()], []), @@ -2199,7 +2165,6 @@ def test_while(self): [ast.Expr(ast.Name("x", ast.Store()))]), "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_if(self): self.stmt(ast.If(ast.Constant(3), [], []), "empty body on If") i = ast.If(ast.Name("x", ast.Store()), [ast.Pass()], []) @@ -2210,7 +2175,6 @@ def test_if(self): [ast.Expr(ast.Name("x", ast.Store()))]) self.stmt(i, "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: empty items on With def test_with(self): p = ast.Pass() self.stmt(ast.With([], [p]), "empty items on With") @@ -2221,7 +2185,6 @@ def test_with(self): i = ast.withitem(ast.Constant(3), ast.Name("x", ast.Load())) self.stmt(ast.With([i], [p]), "must have Store context") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_raise(self): r = ast.Raise(None, ast.Constant(3)) self.stmt(r, "Raise with cause but no exception") @@ -2230,7 +2193,6 @@ def test_raise(self): r = ast.Raise(ast.Constant(4), ast.Name("x", ast.Store())) self.stmt(r, "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_try(self): p = ast.Pass() t = ast.Try([], [], [], [p]) @@ -2251,7 +2213,6 @@ def test_try(self): t = ast.Try([p], e, [p], [ast.Expr(ast.Name("x", ast.Store()))]) self.stmt(t, "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ValueError not raised def test_try_star(self): p = ast.Pass() t = ast.TryStar([], [], [], [p]) @@ -2272,7 +2233,6 @@ def test_try_star(self): t = ast.TryStar([p], e, [p], [ast.Expr(ast.Name("x", ast.Store()))]) self.stmt(t, "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_assert(self): self.stmt(ast.Assert(ast.Name("x", ast.Store()), None), "must have Load context") @@ -2280,25 +2240,20 @@ def test_assert(self): ast.Name("y", ast.Store())) self.stmt(assrt, "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_import(self): self.stmt(ast.Import([]), "empty names on Import") - @unittest.expectedFailure # TODO: RUSTPYTHON; OverflowError: Python int too large to convert to Rust u32 def test_importfrom(self): imp = ast.ImportFrom(None, [ast.alias("x", None)], -42) self.stmt(imp, "Negative ImportFrom level") self.stmt(ast.ImportFrom(None, [], 0), "empty names on ImportFrom") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_global(self): self.stmt(ast.Global([]), "empty names on Global") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_nonlocal(self): self.stmt(ast.Nonlocal([]), "empty names on Nonlocal") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_expr(self): e = ast.Expr(ast.Name("x", ast.Store())) self.stmt(e, "must have Load context") @@ -2314,7 +2269,6 @@ def test_boolop(self): b = ast.BoolOp(ast.And(), [ast.Constant(4), ast.Name("x", ast.Store())]) self.expr(b, "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_unaryop(self): u = ast.UnaryOp(ast.Not(), ast.Name("x", ast.Store())) self.expr(u, "must have Load context") @@ -2328,7 +2282,6 @@ def fac(args): return ast.Lambda(args, ast.Name("x", ast.Load())) self._check_arguments(fac, self.expr) - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_ifexp(self): l = ast.Name("x", ast.Load()) s = ast.Name("y", ast.Store()) @@ -2400,7 +2353,6 @@ def factory(comps): return ast.DictComp(k, v, comps) self._check_comprehension(factory) - @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: 'yield' outside function def test_yield(self): self.expr(ast.Yield(ast.Name("x", ast.Store())), "must have Load") self.expr(ast.YieldFrom(ast.Name("x", ast.Store())), "must have Load") @@ -2430,12 +2382,10 @@ def test_call(self): call = ast.Call(func, args, bad_keywords) self.expr(call, "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_attribute(self): attr = ast.Attribute(ast.Name("x", ast.Store()), "y", ast.Load()) self.expr(attr, "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_subscript(self): sub = ast.Subscript(ast.Name("x", ast.Store()), ast.Constant(3), ast.Load()) @@ -2454,7 +2404,6 @@ def test_subscript(self): sl = ast.Tuple([s], ast.Load()) self.expr(ast.Subscript(x, sl, ast.Load()), "must have Load context") - @unittest.expectedFailure # TODO: RUSTPYTHON; ValueError not raised def test_starred(self): left = ast.List([ast.Starred(ast.Name("x", ast.Load()), ast.Store())], ast.Store()) @@ -2474,7 +2423,6 @@ def test_list(self): def test_tuple(self): self._sequence(ast.Tuple) - @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_resource('cpu') def test_stdlib_validates(self): for module in STDLIB_FILES: @@ -2620,7 +2568,6 @@ def test_stdlib_validates(self): ast.MatchMapping([], [], rest="_"), ] - @unittest.expectedFailure # TODO: RUSTPYTHON def test_match_validation_pattern(self): name_x = ast.Name('x', ast.Load()) for pattern in self._MATCH_PATTERNS: @@ -2669,7 +2616,6 @@ def test_singletons(self): value = self.compile_constant(const) self.assertIs(value, const) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_values(self): nested_tuple = (1,) nested_frozenset = frozenset({1}) @@ -2685,7 +2631,6 @@ def test_values(self): result = self.compile_constant(value) self.assertEqual(result, value) - @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: cannot assign to literal def test_assign_to_constant(self): tree = ast.parse("x = 1") @@ -3250,7 +3195,6 @@ def visit_Call(self, node: ast.Call): class ASTConstructorTests(unittest.TestCase): """Test the autogenerated constructors for AST nodes.""" - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: DeprecationWarning not triggered def test_FunctionDef(self): args = ast.arguments() self.assertEqual(args.args, []) @@ -3264,7 +3208,6 @@ def test_FunctionDef(self): self.assertEqual(node.name, 'foo') self.assertEqual(node.decorator_list, []) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: None is not an instance of def test_expr_context(self): name = ast.Name("x") self.assertEqual(name.id, "x") @@ -3311,7 +3254,6 @@ class FieldsAndTypes(ast.AST): obj = FieldsAndTypes(a=1) self.assertEqual(obj.a, 1) - @unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered def test_custom_attributes(self): class MyAttrs(ast.AST): _attributes = ("a", "b") @@ -3528,7 +3470,6 @@ def test_help_message(self): ast.main(args=flag) self.assertStartsWith(output.getvalue(), 'usage: ') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_exec_mode_flag(self): # test 'python -m ast -m/--mode exec' source = 'x: bool = 1 # type: ignore[assignment]' @@ -3547,7 +3488,6 @@ def test_exec_mode_flag(self): with self.subTest(flag=flag): self.check_output(source, expect, flag) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_single_mode_flag(self): # test 'python -m ast -m/--mode single' source = 'pass' @@ -3576,7 +3516,6 @@ def test_eval_mode_flag(self): with self.subTest(flag=flag): self.check_output(source, expect, flag) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_func_type_mode_flag(self): # test 'python -m ast -m/--mode func_type' source = '(int, str) -> list[int]' @@ -3636,7 +3575,6 @@ def test_indent_flag(self): with self.subTest(flag=flag): self.check_output(source, expect, flag) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: type object '_ast.Module' has no attribute '_field_types' def test_feature_version_flag(self): # test 'python -m ast --feature-version 3.9/3.10' source = ''' @@ -3686,7 +3624,6 @@ def test_no_optimize_flag(self): with self.subTest(flag=flag): self.check_output(source, expect, flag) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_optimize_flag(self): # test 'python -m ast -O/--optimize 1/2' source = ''' @@ -3781,7 +3718,6 @@ def test_folding_format(self): self.assert_ast(code, non_optimized_target, optimized_target) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_folding_match_case_allowed_expressions(self): def get_match_case_values(node): result = [] @@ -3843,7 +3779,6 @@ def get_match_case_values(node): values = get_match_case_values(case.pattern) self.assertListEqual(constants, values) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: type object '_ast.Module' has no attribute '_field_types' def test_match_case_not_folded_in_unoptimized_ast(self): src = textwrap.dedent(""" match a: diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 132e144fa5b..a2d2e3bb395 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -445,7 +445,6 @@ def f(): """doc""" rv = ns['f']() self.assertEqual(rv, tuple(expected)) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_compile_top_level_await_no_coro(self): """Make sure top level non-await codes get the correct coroutine flags""" modes = ('single', 'exec') @@ -552,7 +551,6 @@ async def sleep(delay, result=None): run_yielding_async_fn(lambda: eval(co, globals_)) self.assertEqual(globals_['a'], 1) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_compile_top_level_await_invalid_cases(self): # helper function just to check we can run top=level async-for async def arange(n): @@ -593,7 +591,6 @@ async def __aexit__(self, *exc_info): mode, flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_compile_async_generator(self): """ With the PyCF_ALLOW_TOP_LEVEL_AWAIT flag added in 3.8, we want to @@ -640,7 +637,7 @@ def test_delattr(self): msg = r"^attribute name must be string, not 'int'$" self.assertRaisesRegex(TypeError, msg, delattr, sys, 1) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: '__repr__' unexpectedly found in ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'bar'] def test_dir(self): # dir(wrong number of arguments) self.assertRaises(TypeError, dir, 42, 42) @@ -894,7 +891,6 @@ def test_exec_kwargs(self): exec('global z\nz = 1', locals=g) self.assertEqual(g, {}) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_exec_globals(self): code = compile("print('Hello World!')", "", "exec") # no builtin function @@ -904,7 +900,6 @@ def test_exec_globals(self): self.assertRaises(TypeError, exec, code, {'__builtins__': 123}) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_exec_globals_frozen(self): class frozendict_error(Exception): pass @@ -937,7 +932,6 @@ def __setitem__(self, key, value): self.assertRaises(frozendict_error, exec, code, namespace) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_exec_globals_error_on_get(self): # custom `globals` or `builtins` can raise errors on item access class setonlyerror(Exception): @@ -957,7 +951,6 @@ def __getitem__(self, key): self.assertRaises(setonlyerror, exec, code, {'__builtins__': setonlydict({'superglobal': 1})}) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_exec_globals_dict_subclass(self): class customdict(dict): # this one should not do anything fancy pass @@ -969,7 +962,6 @@ class customdict(dict): # this one should not do anything fancy self.assertRaisesRegex(NameError, "name 'superglobal' is not defined", exec, code, {'__builtins__': customdict()}) - @unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name 'superglobal' is not defined def test_eval_builtins_mapping(self): code = compile("superglobal", "test", "eval") # works correctly @@ -1009,7 +1001,7 @@ def test_exec_redirected(self): finally: sys.stdout = savestdout - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Unexpected keyword argument closure def test_exec_closure(self): def function_without_closures(): return 3 * 5 @@ -1680,7 +1672,6 @@ def test_open(self): self.assertRaises(ValueError, open, 'a\x00b') self.assertRaises(ValueError, open, b'a\x00b') - @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(sys.flags.utf8_mode, "utf-8 mode is enabled") def test_open_default_encoding(self): with EnvironmentVarGuard() as env: @@ -2715,7 +2706,6 @@ def detach_readline(self): else: yield - @unittest.expectedFailure # TODO: RUSTPYTHON def test_input_tty(self): # Test input() functionality when wired to a tty self.check_input_tty("prompt", b"quux") @@ -2730,20 +2720,17 @@ def test_input_tty_non_ascii_unicode_errors(self): # Check stdin/stdout error handler is used when invoking PyOS_Readline() self.check_input_tty("prompté", b"quux\xe9", "ascii") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_input_tty_null_in_prompt(self): self.check_input_tty("prompt\0", b"", expected='ValueError: input: prompt string cannot contain ' 'null characters') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_input_tty_nonencodable_prompt(self): self.check_input_tty("prompté", b"quux", "ascii", stdout_errors='strict', expected="UnicodeEncodeError: 'ascii' codec can't encode " "character '\\xe9' in position 6: ordinal not in " "range(128)") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_input_tty_nondecodable_input(self): self.check_input_tty("prompt", b"quux\xe9", "ascii", stdin_errors='strict', expected="UnicodeDecodeError: 'ascii' codec can't decode " @@ -2960,7 +2947,7 @@ def test_type_qualname(self): A.__qualname__ = b'B' self.assertEqual(A.__qualname__, 'D.E') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: '__firstlineno__' unexpectedly found in mappingproxy({'__firstlineno__': 42, '__module__': 'testmodule', '__dict__': , '__doc__': None}) def test_type_firstlineno(self): A = type('A', (), {'__firstlineno__': 42}) self.assertEqual(A.__name__, 'A') @@ -2972,7 +2959,7 @@ def test_type_firstlineno(self): A.__firstlineno__ = 43 self.assertEqual(A.__dict__['__firstlineno__'], 43) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Expected type 'tuple' but 'str' found. def test_type_typeparams(self): class A[T]: pass diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index e06e9f7f4a9..0370ce8d946 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -76,8 +76,6 @@ def test___globals__(self): self.cannot_set_attr(self.b, '__globals__', 2, (AttributeError, TypeError)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test___builtins__(self): self.assertIs(self.b.__builtins__, __builtins__) self.cannot_set_attr(self.b, '__builtins__', 2, diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 7d6f5de95a8..22c675875ad 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -82,8 +82,7 @@ def syntax_error_bad_indentation2(self): def tokenizer_error_with_caret_range(self): compile("blech ( ", "?", "exec") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 11 != 14 def test_caret(self): err = self.get_exception_format(self.syntax_error_with_caret, SyntaxError) @@ -196,8 +195,7 @@ def f(): finally: unlink(TESTFN) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 3 != 4 def test_bad_indentation(self): err = self.get_exception_format(self.syntax_error_bad_indentation, IndentationError) @@ -477,8 +475,7 @@ def do_test(firstlines, message, charset, lineno): # Issue #18960: coding spec should have no effect do_test("x=0\n# coding: GBK\n", "h\xe9 ho", 'utf-8', 5) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; + b'ZeroDivisionError: division by zero'] def test_print_traceback_at_exit(self): # Issue #22599: Ensure that it is possible to use the traceback module # to display an exception at Python exit @@ -619,8 +616,6 @@ class TracebackErrorLocationCaretTestBase: """ Tests for printing code error expressions as part of PEP 657 """ - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_basic_caret(self): # NOTE: In caret tests, "if True:" is used as a way to force indicator # display, since the raising expression spans only part of the line. @@ -640,8 +635,6 @@ def f(): result_lines = self.get_exception(f) self.assertEqual(result_lines, expected_f.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_line_with_unicode(self): # Make sure that even if a line contains multi-byte unicode characters # the correct carets are printed. @@ -661,8 +654,6 @@ def f_with_unicode(): result_lines = self.get_exception(f_with_unicode) self.assertEqual(result_lines, expected_f.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_in_type_annotation(self): def f_with_type(): def foo(a: THIS_DOES_NOT_EXIST ) -> int: @@ -681,8 +672,6 @@ def foo(a: THIS_DOES_NOT_EXIST ) -> int: result_lines = self.get_exception(f_with_type) self.assertEqual(result_lines, expected_f.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_multiline_expression(self): # Make sure no carets are printed for expressions spanning multiple # lines. @@ -708,8 +697,6 @@ def f_with_multiline(): result_lines = self.get_exception(f_with_multiline) self.assertEqual(result_lines, expected_f.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_multiline_expression_syntax_error(self): # Make sure an expression spanning multiple lines that has # a syntax error is correctly marked with carets. @@ -774,8 +761,6 @@ def f_with_multiline(): result_lines = self.get_exception(f_with_multiline) self.assertEqual(result_lines, expected_f.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_multiline_expression_bin_op(self): # Make sure no carets are printed for expressions spanning multiple # lines. @@ -800,8 +785,6 @@ def f_with_multiline(): result_lines = self.get_exception(f_with_multiline) self.assertEqual(result_lines, expected_f.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_binary_operators(self): def f_with_binary_operator(): divisor = 20 @@ -820,8 +803,6 @@ def f_with_binary_operator(): result_lines = self.get_exception(f_with_binary_operator) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_binary_operators_with_unicode(self): def f_with_binary_operator(): áóí = 20 @@ -840,8 +821,6 @@ def f_with_binary_operator(): result_lines = self.get_exception(f_with_binary_operator) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_binary_operators_two_char(self): def f_with_binary_operator(): divisor = 20 @@ -860,8 +839,6 @@ def f_with_binary_operator(): result_lines = self.get_exception(f_with_binary_operator) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_binary_operators_with_spaces_and_parenthesis(self): def f_with_binary_operator(): a = 1 @@ -881,8 +858,6 @@ def f_with_binary_operator(): result_lines = self.get_exception(f_with_binary_operator) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_binary_operators_multiline(self): def f_with_binary_operator(): b = 1 @@ -909,8 +884,6 @@ def f_with_binary_operator(): result_lines = self.get_exception(f_with_binary_operator) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_binary_operators_multiline_two_char(self): def f_with_binary_operator(): b = 1 @@ -948,8 +921,6 @@ def f_with_binary_operator(): result_lines = self.get_exception(f_with_binary_operator) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_binary_operators_multiline_with_unicode(self): def f_with_binary_operator(): b = 1 @@ -972,8 +943,6 @@ def f_with_binary_operator(): result_lines = self.get_exception(f_with_binary_operator) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_subscript(self): def f_with_subscript(): some_dict = {'x': {'y': None}} @@ -992,8 +961,6 @@ def f_with_subscript(): result_lines = self.get_exception(f_with_subscript) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_subscript_unicode(self): def f_with_subscript(): some_dict = {'ó': {'á': {'í': {'theta': 1}}}} @@ -1012,8 +979,6 @@ def f_with_subscript(): result_lines = self.get_exception(f_with_subscript) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_subscript_with_spaces_and_parenthesis(self): def f_with_binary_operator(): a = [] @@ -1033,8 +998,6 @@ def f_with_binary_operator(): result_lines = self.get_exception(f_with_binary_operator) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_subscript_multiline(self): def f_with_subscript(): bbbbb = {} @@ -1071,8 +1034,6 @@ def f_with_subscript(): result_lines = self.get_exception(f_with_subscript) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_call(self): def f_with_call(): def f1(a): @@ -1096,8 +1057,6 @@ def f2(b): result_lines = self.get_exception(f_with_call) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_call_unicode(self): def f_with_call(): def f1(a): @@ -1121,8 +1080,6 @@ def f2(b): result_lines = self.get_exception(f_with_call) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_call_with_spaces_and_parenthesis(self): def f_with_binary_operator(): def f(a): @@ -1144,8 +1101,6 @@ def f(a): result_lines = self.get_exception(f_with_binary_operator) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_for_call_multiline(self): def f_with_call(): class C: @@ -1179,8 +1134,6 @@ def g(x): result_lines = self.get_exception(f_with_call) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_many_lines(self): def f(): x = 1 @@ -1205,8 +1158,6 @@ def f(): result_lines = self.get_exception(f) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_many_lines_no_caret(self): def f(): x = 1 @@ -1229,8 +1180,6 @@ def f(): result_lines = self.get_exception(f) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_many_lines_binary_op(self): def f_with_binary_operator(): b = 1 @@ -1269,8 +1218,6 @@ def f_with_binary_operator(): result_lines = self.get_exception(f_with_binary_operator) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_traceback_specialization_with_syntax_error(self): bytecode = compile("1 / 0 / 1 / 2\n", TESTFN, "exec") @@ -1294,8 +1241,6 @@ def test_traceback_specialization_with_syntax_error(self): ) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_traceback_very_long_line(self): source = "if True: " + "a" * 256 bytecode = compile(source, TESTFN, "exec") @@ -1319,8 +1264,6 @@ def test_traceback_very_long_line(self): ) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_secondary_caret_not_elided(self): # Always show a line's indicators if they include the secondary character. def f_with_subscript(): @@ -1340,8 +1283,6 @@ def f_with_subscript(): result_lines = self.get_exception(f_with_subscript) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_caret_exception_group(self): # Notably, this covers whether indicators handle margin strings correctly. # (Exception groups use margin strings to display vertical indicators.) @@ -1372,8 +1313,6 @@ def assertSpecialized(self, func, expected_specialization): specialization_line = result_lines[-1] self.assertEqual(specialization_line.lstrip(), expected_specialization) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_specialization_variations(self): self.assertSpecialized(lambda: 1/0, "~^~") @@ -1406,8 +1345,6 @@ def test_specialization_variations(self): self.assertSpecialized(lambda: 1// 0, "~^^~~") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_decorator_application_lineno_correct(self): def dec_error(func): raise TypeError @@ -1452,8 +1389,6 @@ class A: pass ) self.assertEqual(result_lines, expected_error.splitlines()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_multiline_method_call_a(self): def f(): (None @@ -1471,8 +1406,6 @@ def f(): ] self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_multiline_method_call_b(self): def f(): (None. @@ -1489,8 +1422,6 @@ def f(): ] self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_multiline_method_call_c(self): def f(): (None @@ -1508,8 +1439,6 @@ def f(): ] self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_wide_characters_unicode_with_problematic_byte_offset(self): def f(): width @@ -1526,8 +1455,6 @@ def f(): self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_byte_offset_with_wide_characters_middle(self): def f(): width = 1 @@ -1544,8 +1471,6 @@ def f(): ] self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_byte_offset_multiline(self): def f(): www = 1 @@ -1568,8 +1493,6 @@ def f(): ] self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_byte_offset_with_wide_characters_term_highlight(self): def f(): 说明说明 = 1 @@ -1588,8 +1511,6 @@ def f(): ] self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_byte_offset_with_emojis_term_highlight(self): def f(): return "✨🐍" + func_说明说明("📗🚛", @@ -1607,8 +1528,6 @@ def f(): ] self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_byte_offset_wide_chars_subscript(self): def f(): my_dct = { @@ -1632,8 +1551,6 @@ def f(): ] self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_memory_error(self): def f(): raise MemoryError() @@ -1647,8 +1564,6 @@ def f(): ' raise MemoryError()'] self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_anchors_for_simple_return_statements_are_elided(self): def g(): 1/0 @@ -1738,8 +1653,6 @@ def f(): ] self.assertEqual(result_lines, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_anchors_for_simple_assign_statements_are_elided(self): def g(): 1/0 @@ -1842,6 +1755,118 @@ class PurePythonTracebackErrorCaretTests( traceback printing in traceback.py. """ + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ~~~~~~~~^^~~'] + def test_caret_for_binary_operators_two_char(self): + return super().test_caret_for_binary_operators_two_char() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ~~~~~~~~^~~'] + def test_caret_for_binary_operators(self): + return super().test_caret_for_binary_operators() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ~~~~~~^^^^^^^^^'] + def test_caret_for_subscript_with_spaces_and_parenthesis(self): + return super().test_caret_for_subscript_with_spaces_and_parenthesis() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ~~~~~~~~~^~~~~~~~~~~~'] + def test_byte_offset_with_wide_characters_term_highlight(self): + return super().test_byte_offset_with_wide_characters_term_highlight() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ~~~~~~~~~~^~'] + def test_caret_for_binary_operators_with_spaces_and_parenthesis(self): + return super().test_caret_for_binary_operators_with_spaces_and_parenthesis() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ~~~~~~~~~~~~~~~~~~~^^^^^'] + def test_caret_for_subscript(self): + return super().test_caret_for_subscript() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^'] + def test_byte_offset_wide_chars_subscript(self): + return super().test_byte_offset_wide_chars_subscript() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^'] + def test_caret_for_subscript_unicode(self): + return super().test_caret_for_subscript_unicode() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ~'] + def test_caret_for_binary_operators_multiline(self): + return super().test_caret_for_binary_operators_multiline() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ~~~~~~^~~'] + def test_caret_for_binary_operators_multiline_with_unicode(self): + return super().test_caret_for_binary_operators_multiline_with_unicode() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ^^^^^'] + def test_traceback_specialization_with_syntax_error(self): + return super().test_traceback_specialization_with_syntax_error() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ^^^^^^^^^^^^^'] + def test_caret_multiline_expression_syntax_error(self): + return super().test_caret_multiline_expression_syntax_error() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ~'] + def test_caret_multiline_expression_bin_op(self): + return super().test_caret_multiline_expression_bin_op() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ' ~~~~~~~~~~~~~~~~~~~^^^^^'] + def test_secondary_caret_not_elided(self): + return super().test_secondary_caret_not_elided() + + @unittest.expectedFailure # TODO: RUSTPYTHON; + ~^~ + def test_specialization_variations(self): + return super().test_specialization_variations() + + @unittest.expectedFailure # TODO: RUSTPYTHON; - ' ^^^^'] + def test_multiline_method_call_b(self): + return super().test_multiline_method_call_b() + + @unittest.expectedFailure # TODO: RUSTPYTHON; ? ^^^^ ++ + def test_caret_for_binary_operators_with_unicode(self): + return super().test_caret_for_binary_operators_with_unicode() + + @unittest.expectedFailure # TODO: RUSTPYTHON; ? ++ + def test_multiline_method_call_a(self): + return super().test_multiline_method_call_a() + + @unittest.expectedFailure # TODO: RUSTPYTHON; ? +++ + def test_multiline_method_call_c(self): + return super().test_multiline_method_call_c() + + @unittest.expectedFailure # TODO: RUSTPYTHON; ? ^ + def test_many_lines(self): + return super().test_many_lines() + + @unittest.expectedFailure # TODO: RUSTPYTHON; ? ^ + def test_many_lines_no_caret(self): + return super().test_many_lines_no_caret() + + @unittest.expectedFailure # TODO: RUSTPYTHON; ? ^ + + def test_anchors_for_simple_assign_statements_are_elided(self): + return super().test_anchors_for_simple_assign_statements_are_elided() + + @unittest.expectedFailure # TODO: RUSTPYTHON; ? ^ + + def test_anchors_for_simple_return_statements_are_elided(self): + return super().test_anchors_for_simple_return_statements_are_elided() + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: No exception thrown. + def test_caret_in_type_annotation(self): + return super().test_caret_in_type_annotation() + + @unittest.expectedFailure # TODO: RUSTPYTHON; Diff is 652 characters long. Set self.maxDiff to None to see it. + def test_decorator_application_lineno_correct(self): + return super().test_decorator_application_lineno_correct() + + @unittest.expectedFailure # TODO: RUSTPYTHON; Diff is 684 characters long. Set self.maxDiff to None to see it. + def test_many_lines_binary_op(self): + return super().test_many_lines_binary_op() + + @unittest.expectedFailure # TODO: RUSTPYTHON; Diff is 726 characters long. Set self.maxDiff to None to see it. + def test_caret_for_binary_operators_multiline_two_char(self): + return super().test_caret_for_binary_operators_multiline_two_char() + + @unittest.expectedFailure # TODO: RUSTPYTHON; Diff is 732 characters long. Set self.maxDiff to None to see it. + def test_caret_for_subscript_multiline(self): + return super().test_caret_for_subscript_multiline() + @cpython_only # @requires_debug_ranges() # XXX: RUSTPYTHON patch @@ -2163,8 +2188,6 @@ def h(count=10): actual = stderr_g.getvalue().splitlines() self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure # @requires_debug_ranges() # XXX: RUSTPYTHON patch def test_recursive_traceback(self): if self.DEBUG_RANGES: @@ -2653,8 +2676,6 @@ def __str__(self): # #### Exception Groups #### - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_group_basic(self): def exc(): raise ExceptionGroup("eg", [ValueError(1), TypeError(2)]) @@ -2676,8 +2697,6 @@ def exc(): report = self.get_report(exc) self.assertEqual(report, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_group_cause(self): def exc(): EG = ExceptionGroup @@ -2714,8 +2733,6 @@ def exc(): report = self.get_report(exc) self.assertEqual(report, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_group_context_with_context(self): def exc(): EG = ExceptionGroup @@ -2763,8 +2780,6 @@ def exc(): report = self.get_report(exc) self.assertEqual(report, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_group_nested(self): def exc(): EG = ExceptionGroup @@ -2941,8 +2956,6 @@ def test_exception_group_depth_limit(self): report = self.get_report(exc) self.assertEqual(report, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_group_with_notes(self): def exc(): try: @@ -2993,8 +3006,6 @@ def exc(): report = self.get_report(exc) self.assertEqual(report, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_exception_group_with_multiple_notes(self): def exc(): try: @@ -3172,8 +3183,7 @@ def last_returns_frame4(self): def last_returns_frame5(self): return self.last_returns_frame4() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 1 not greater than 5 def test_extract_stack(self): frame = self.last_returns_frame5() def extract(**kwargs): @@ -3264,8 +3274,7 @@ class MiscTracebackCases(unittest.TestCase): # Check non-printing functions in traceback module # - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 1 != 0 def test_clear(self): def outer(): middle() @@ -3481,8 +3490,7 @@ def format_frame_summary(self, frame_summary, colorize=False): f' File "{__file__}", line {lno}, in f\n 1/0\n' ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; Actual: _should_show_carets(13, 14, ['# this line will be used during rendering'], None) def test_summary_should_show_carets(self): # See: https://github.com/python/cpython/issues/122353 @@ -3639,8 +3647,7 @@ def test_context(self): self.assertEqual(type(exc_obj).__name__, exc.exc_type_str) self.assertEqual(str(exc_obj), str(exc)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 11 not greater than 1000 def test_long_context_chain(self): def f(): try: @@ -3864,8 +3871,7 @@ def test_traceback_header(self): self.assertEqual(list(exc.format()), ["Exception: haven\n"]) # @requires_debug_ranges() # XXX: RUSTPYTHON patch - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ? ^ + def test_print(self): def f(): x = 12 @@ -3968,8 +3974,7 @@ def test_exception_group_format_exception_onlyi_recursive(self): self.assertEqual(formatted, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; Diff is 2265 characters long. Set self.maxDiff to None to see it. def test_exception_group_format(self): teg = traceback.TracebackException.from_exception(self.eg) @@ -4314,8 +4319,6 @@ def raise_attribute_error_with_bad_name(): ) self.assertNotIn("?", result_lines[-1]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_attribute_error_inside_nested_getattr(self): class A: bluch = 1 @@ -4359,8 +4362,6 @@ def callable(): ) return result_lines[0] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_import_from_suggestions(self): substitution = textwrap.dedent("""\ noise = more_noise = a = bc = None @@ -4411,8 +4412,6 @@ def test_import_from_suggestions(self): actual = self.get_import_from_suggestion(code, 'bluch') self.assertIn(suggestion, actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_import_from_suggestions_underscored(self): code = "bluch = None" self.assertIn("'bluch'", self.get_import_from_suggestion(code, 'blach')) @@ -4424,8 +4423,6 @@ def test_import_from_suggestions_underscored(self): self.assertIn("'_bluch'", self.get_import_from_suggestion(code, '_luch')) self.assertNotIn("'_bluch'", self.get_import_from_suggestion(code, 'bluch')) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_import_from_suggestions_non_string(self): modWithNonStringAttr = textwrap.dedent("""\ globals()[0] = 1 @@ -4759,6 +4756,22 @@ class PurePythonSuggestionFormattingTests( traceback printing in traceback.py. """ + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "'bluch'" not found in "ImportError: cannot import name 'blach'" + def test_import_from_suggestions_underscored(self): + return super().test_import_from_suggestions_underscored() + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "'bluch'" not found in "ImportError: cannot import name 'blech'" + def test_import_from_suggestions_non_string(self): + return super().test_import_from_suggestions_non_string() + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "'bluchin'?" not found in "ImportError: cannot import name 'bluch'" + def test_import_from_suggestions(self): + return super().test_import_from_suggestions() + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'Did you mean' not found in "AttributeError: 'A' object has no attribute 'blich'" + def test_attribute_error_inside_nested_getattr(self): + return super().test_attribute_error_inside_nested_getattr() + @cpython_only class CPythonSuggestionFormattingTests( @@ -4813,8 +4826,7 @@ def CHECK(a, b, expected): CHECK("AttributeError", "AttributeErrorTests", 10) CHECK("ABA", "AAB", 4) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: /Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/levenshtein_examples.json is missing. Run `make regen-test-levenshtein` @support.requires_resource('cpu') def test_levenshtein_distance_short_circuit(self): if not LEVENSHTEIN_DATA_FILE.is_file(): @@ -4871,8 +4883,7 @@ class MyList(list): class TestColorizedTraceback(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "y = \x1b[31mx['a']['b']\x1b[0m\x1b[1;31m['c']\x1b[0m" not found in 'Traceback (most recent call last):\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4764\x1b[0m, in \x1b[35mtest_colorized_traceback\x1b[0m\n \x1b[31mbar\x1b[0m\x1b[1;31m()\x1b[0m\n \x1b[31m~~~\x1b[0m\x1b[1;31m^^\x1b[0m\n bar = .bar at 0xb57b09180>\n baz1 = .baz1 at 0xb57b09e00>\n baz2 = .baz2 at 0xb57b09cc0>\n e = TypeError("\'NoneType\' object is not subscriptable")\n foo = .foo at 0xb57b08140>\n self = \n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4760\x1b[0m, in \x1b[35mbar\x1b[0m\n return baz1(1,\n 2,3\n ,4)\n baz1 = .baz1 at 0xb57b09e00>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4757\x1b[0m, in \x1b[35mbaz1\x1b[0m\n return baz2(1,2,3,4)\n args = (1, 2, 3, 4)\n baz2 = .baz2 at 0xb57b09cc0>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4754\x1b[0m, in \x1b[35mbaz2\x1b[0m\n return \x1b[31m(lambda *args: foo(*args))\x1b[0m\x1b[1;31m(1,2,3,4)\x1b[0m\n \x1b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~\x1b[0m\x1b[1;31m^^^^^^^^^\x1b[0m\n args = (1, 2, 3, 4)\n foo = .foo at 0xb57b08140>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4754\x1b[0m, in \x1b[35m\x1b[0m\n return (lambda *args: \x1b[31mfoo\x1b[0m\x1b[1;31m(*args)\x1b[0m)(1,2,3,4)\n \x1b[31m~~~\x1b[0m\x1b[1;31m^^^^^^^\x1b[0m\n args = (1, 2, 3, 4)\n foo = .foo at 0xb57b08140>\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4751\x1b[0m, in \x1b[35mfoo\x1b[0m\n y = x[\'a\'][\'b\'][\x1b[1;31m\'c\'\x1b[0m]\n \x1b[1;31m^^^\x1b[0m\n args = (1, 2, 3, 4)\n x = {\'a\': {\'b\': None}}\n\x1b[1;35mTypeError\x1b[0m: \x1b[35m\'NoneType\' object is not subscriptable\x1b[0m\n' def test_colorized_traceback(self): def foo(*args): x = {'a':{'b': None}} @@ -4905,8 +4916,7 @@ def bar(): self.assertIn("return baz1(1,\n 2,3\n ,4)", lines) self.assertIn(red + "bar" + reset + boldr + "()" + reset, lines) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ' File \x1b[35m""\x1b[0m, line \x1b[35m1\x1b[0m\n a \x1b[1;31m$\x1b[0m b\n \x1b[1;31m^\x1b[0m\n\x1b[1;35mSyntaxError\x1b[0m: \x1b[35minvalid syntax\x1b[0m\n' not found in 'Traceback (most recent call last):\n File \x1b[35m"/Users/al03219714/Projects/RustPython/crates/pylib/Lib/test/test_traceback.py"\x1b[0m, line \x1b[35m4782\x1b[0m, in \x1b[35mtest_colorized_syntax_error\x1b[0m\n \x1b[31mcompile\x1b[0m\x1b[1;31m("a $ b", "", "exec")\x1b[0m\n \x1b[31m~~~~~~~\x1b[0m\x1b[1;31m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\x1b[0m\n e = SyntaxError(\'got unexpected token $\')\n self = \n File \x1b[35m""\x1b[0m, line \x1b[35m1\x1b[0m\n a \x1b[1;31m$\x1b[0m b\n \x1b[1;31m^\x1b[0m\n\x1b[1;35mSyntaxError\x1b[0m: \x1b[35mgot unexpected token $\x1b[0m\n' def test_colorized_syntax_error(self): try: compile("a $ b", "", "exec") @@ -4928,8 +4938,7 @@ def test_colorized_syntax_error(self): ) self.assertIn(expected, actual) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; ModuleNotFoundError: No module named '_testcapi' def test_colorized_traceback_is_the_default(self): def foo(): 1/0 @@ -4962,8 +4971,7 @@ def foo(): f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}'] self.assertEqual(actual, expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; Diff is 1795 characters long. Set self.maxDiff to None to see it. def test_colorized_traceback_from_exception_group(self): def foo(): exceptions = [] diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index c7480fb3476..35e4652a87b 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -202,7 +202,6 @@ def test_fstrings_pep701(self): self.check_ast_roundtrip('f" something { my_dict["key"] } something else "') self.check_ast_roundtrip('f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_tstrings(self): self.check_ast_roundtrip("t'foo'") self.check_ast_roundtrip("t'foo {bar}'") @@ -527,7 +526,6 @@ def test_constant_tuples(self): locs(ast.Module([ast.Expr(ast.Constant(value=(1, 2, 3)))])), "(1, 2, 3)" ) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_function_type(self): for function_type in ( "() -> int", @@ -567,7 +565,6 @@ def test_type_ignore(self): ): self.check_ast_roundtrip(statement, type_comments=True) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'TypeVar' object has no attribute 'default_value' def test_unparse_interactive_semicolons(self): # gh-129598: Fix ast.unparse() when ast.Interactive contains multiple statements self.check_src_roundtrip("i = 1; 'expr'; raise Exception", mode='single') @@ -853,7 +850,6 @@ def test_star_expr_assign_target_multiple(self): self.check_src_roundtrip("[a, b] = [c, d] = [e, f] = g") self.check_src_roundtrip("a, b = [c, d] = e, f = g") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_multiquote_joined_string(self): self.check_ast_roundtrip("f\"'''{1}\\\"\\\"\\\"\" ") self.check_ast_roundtrip("""f"'''{1}""\\"" """) @@ -868,7 +864,6 @@ def test_multiquote_joined_string(self): self.check_ast_roundtrip("""f'''""\"''\\'{"\\n\\"'"}''' """) self.check_ast_roundtrip("""f'''""\"''\\'{""\"\\n\\"'''""\" '''\\n'''}''' """) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxWarning not triggered def test_backslash_in_format_spec(self): import re msg = re.escape('"\\ " is an invalid escape sequence. ' @@ -1054,7 +1049,6 @@ def files_to_test(cls): return items - @unittest.expectedFailure # TODO: RUSTPYTHON def test_files(self): with warnings.catch_warnings(): warnings.simplefilter('ignore', SyntaxWarning) diff --git a/crates/doc/generate.py b/crates/doc/generate.py index 73cb462bb9f..189e69705e1 100644 --- a/crates/doc/generate.py +++ b/crates/doc/generate.py @@ -49,7 +49,7 @@ def key(self) -> str: def doc(self) -> str: assert self.raw_doc is not None - return re.sub(UNICODE_ESCAPE, r"\\u{\1}", inspect.cleandoc(self.raw_doc)) + return re.sub(UNICODE_ESCAPE, r"\\u{\1}", self.raw_doc.strip()) def is_c_extension(module: types.ModuleType) -> bool: @@ -90,7 +90,15 @@ def is_child_of(obj: typing.Any, module: types.ModuleType) -> bool: ------- bool """ - return inspect.getmodule(obj) is module + if inspect.getmodule(obj) is module: + return True + # Some C modules (e.g. _ast) set __module__ to a different name (e.g. "ast"), + # causing inspect.getmodule() to return a different module object. + # Fall back to checking the module's namespace directly. + obj_name = getattr(obj, "__name__", None) + if obj_name is not None: + return module.__dict__.get(obj_name) is obj + return False def iter_modules() -> "Iterable[types.ModuleType]": diff --git a/crates/doc/src/data.inc.rs b/crates/doc/src/data.inc.rs index 4411587ca8b..f592d042901 100644 --- a/crates/doc/src/data.inc.rs +++ b/crates/doc/src/data.inc.rs @@ -12,6 +12,131 @@ pub static DB: phf::Map<&'static str, &'static str> = phf::phf_map! { "_abc._reset_caches" => "Internal ABC helper to reset both caches of a given class.\n\nShould be only used by refleak.py", "_abc._reset_registry" => "Internal ABC helper to reset registry of a given class.\n\nShould be only used by refleak.py", "_abc.get_cache_token" => "Returns the current ABC cache token.\n\nThe token is an opaque object (supporting equality testing) identifying the\ncurrent version of the ABC cache for virtual subclasses. The token changes\nwith every call to register() on any ABC.", + "_ast.Add" => "Add", + "_ast.And" => "And", + "_ast.AnnAssign" => "AnnAssign(expr target, expr annotation, expr? value, int simple)", + "_ast.Assert" => "Assert(expr test, expr? msg)", + "_ast.Assign" => "Assign(expr* targets, expr value, string? type_comment)", + "_ast.AsyncFor" => "AsyncFor(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment)", + "_ast.AsyncFunctionDef" => "AsyncFunctionDef(identifier name, arguments args, stmt* body, expr* decorator_list, expr? returns, string? type_comment, type_param* type_params)", + "_ast.AsyncWith" => "AsyncWith(withitem* items, stmt* body, string? type_comment)", + "_ast.Attribute" => "Attribute(expr value, identifier attr, expr_context ctx)", + "_ast.AugAssign" => "AugAssign(expr target, operator op, expr value)", + "_ast.Await" => "Await(expr value)", + "_ast.BinOp" => "BinOp(expr left, operator op, expr right)", + "_ast.BitAnd" => "BitAnd", + "_ast.BitOr" => "BitOr", + "_ast.BitXor" => "BitXor", + "_ast.BoolOp" => "BoolOp(boolop op, expr* values)", + "_ast.Break" => "Break", + "_ast.Call" => "Call(expr func, expr* args, keyword* keywords)", + "_ast.ClassDef" => "ClassDef(identifier name, expr* bases, keyword* keywords, stmt* body, expr* decorator_list, type_param* type_params)", + "_ast.Compare" => "Compare(expr left, cmpop* ops, expr* comparators)", + "_ast.Constant" => "Constant(constant value, string? kind)", + "_ast.Continue" => "Continue", + "_ast.Del" => "Del", + "_ast.Delete" => "Delete(expr* targets)", + "_ast.Dict" => "Dict(expr?* keys, expr* values)", + "_ast.DictComp" => "DictComp(expr key, expr value, comprehension* generators)", + "_ast.Div" => "Div", + "_ast.Eq" => "Eq", + "_ast.ExceptHandler" => "ExceptHandler(expr? type, identifier? name, stmt* body)", + "_ast.Expr" => "Expr(expr value)", + "_ast.Expression" => "Expression(expr body)", + "_ast.FloorDiv" => "FloorDiv", + "_ast.For" => "For(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment)", + "_ast.FormattedValue" => "FormattedValue(expr value, int conversion, expr? format_spec)", + "_ast.FunctionDef" => "FunctionDef(identifier name, arguments args, stmt* body, expr* decorator_list, expr? returns, string? type_comment, type_param* type_params)", + "_ast.FunctionType" => "FunctionType(expr* argtypes, expr returns)", + "_ast.GeneratorExp" => "GeneratorExp(expr elt, comprehension* generators)", + "_ast.Global" => "Global(identifier* names)", + "_ast.Gt" => "Gt", + "_ast.GtE" => "GtE", + "_ast.If" => "If(expr test, stmt* body, stmt* orelse)", + "_ast.IfExp" => "IfExp(expr test, expr body, expr orelse)", + "_ast.Import" => "Import(alias* names)", + "_ast.ImportFrom" => "ImportFrom(identifier? module, alias* names, int? level)", + "_ast.In" => "In", + "_ast.Interactive" => "Interactive(stmt* body)", + "_ast.Interpolation" => "Interpolation(expr value, constant str, int conversion, expr? format_spec)", + "_ast.Invert" => "Invert", + "_ast.Is" => "Is", + "_ast.IsNot" => "IsNot", + "_ast.JoinedStr" => "JoinedStr(expr* values)", + "_ast.LShift" => "LShift", + "_ast.Lambda" => "Lambda(arguments args, expr body)", + "_ast.List" => "List(expr* elts, expr_context ctx)", + "_ast.ListComp" => "ListComp(expr elt, comprehension* generators)", + "_ast.Load" => "Load", + "_ast.Lt" => "Lt", + "_ast.LtE" => "LtE", + "_ast.MatMult" => "MatMult", + "_ast.Match" => "Match(expr subject, match_case* cases)", + "_ast.MatchAs" => "MatchAs(pattern? pattern, identifier? name)", + "_ast.MatchClass" => "MatchClass(expr cls, pattern* patterns, identifier* kwd_attrs, pattern* kwd_patterns)", + "_ast.MatchMapping" => "MatchMapping(expr* keys, pattern* patterns, identifier? rest)", + "_ast.MatchOr" => "MatchOr(pattern* patterns)", + "_ast.MatchSequence" => "MatchSequence(pattern* patterns)", + "_ast.MatchSingleton" => "MatchSingleton(constant value)", + "_ast.MatchStar" => "MatchStar(identifier? name)", + "_ast.MatchValue" => "MatchValue(expr value)", + "_ast.Mod" => "Mod", + "_ast.Module" => "Module(stmt* body, type_ignore* type_ignores)", + "_ast.Mult" => "Mult", + "_ast.Name" => "Name(identifier id, expr_context ctx)", + "_ast.NamedExpr" => "NamedExpr(expr target, expr value)", + "_ast.Nonlocal" => "Nonlocal(identifier* names)", + "_ast.Not" => "Not", + "_ast.NotEq" => "NotEq", + "_ast.NotIn" => "NotIn", + "_ast.Or" => "Or", + "_ast.ParamSpec" => "ParamSpec(identifier name, expr? default_value)", + "_ast.Pass" => "Pass", + "_ast.Pow" => "Pow", + "_ast.RShift" => "RShift", + "_ast.Raise" => "Raise(expr? exc, expr? cause)", + "_ast.Return" => "Return(expr? value)", + "_ast.Set" => "Set(expr* elts)", + "_ast.SetComp" => "SetComp(expr elt, comprehension* generators)", + "_ast.Slice" => "Slice(expr? lower, expr? upper, expr? step)", + "_ast.Starred" => "Starred(expr value, expr_context ctx)", + "_ast.Store" => "Store", + "_ast.Sub" => "Sub", + "_ast.Subscript" => "Subscript(expr value, expr slice, expr_context ctx)", + "_ast.TemplateStr" => "TemplateStr(expr* values)", + "_ast.Try" => "Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)", + "_ast.TryStar" => "TryStar(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)", + "_ast.Tuple" => "Tuple(expr* elts, expr_context ctx)", + "_ast.TypeAlias" => "TypeAlias(expr name, type_param* type_params, expr value)", + "_ast.TypeIgnore" => "TypeIgnore(int lineno, string tag)", + "_ast.TypeVar" => "TypeVar(identifier name, expr? bound, expr? default_value)", + "_ast.TypeVarTuple" => "TypeVarTuple(identifier name, expr? default_value)", + "_ast.UAdd" => "UAdd", + "_ast.USub" => "USub", + "_ast.UnaryOp" => "UnaryOp(unaryop op, expr operand)", + "_ast.While" => "While(expr test, stmt* body, stmt* orelse)", + "_ast.With" => "With(withitem* items, stmt* body, string? type_comment)", + "_ast.Yield" => "Yield(expr? value)", + "_ast.YieldFrom" => "YieldFrom(expr value)", + "_ast.alias" => "alias(identifier name, identifier? asname)", + "_ast.arg" => "arg(identifier arg, expr? annotation, string? type_comment)", + "_ast.arguments" => "arguments(arg* posonlyargs, arg* args, arg? vararg, arg* kwonlyargs, expr* kw_defaults, arg? kwarg, expr* defaults)", + "_ast.boolop" => "boolop = And | Or", + "_ast.cmpop" => "cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn", + "_ast.comprehension" => "comprehension(expr target, expr iter, expr* ifs, int is_async)", + "_ast.excepthandler" => "excepthandler = ExceptHandler(expr? type, identifier? name, stmt* body)", + "_ast.expr" => "expr = BoolOp(boolop op, expr* values)\n | NamedExpr(expr target, expr value)\n | BinOp(expr left, operator op, expr right)\n | UnaryOp(unaryop op, expr operand)\n | Lambda(arguments args, expr body)\n | IfExp(expr test, expr body, expr orelse)\n | Dict(expr?* keys, expr* values)\n | Set(expr* elts)\n | ListComp(expr elt, comprehension* generators)\n | SetComp(expr elt, comprehension* generators)\n | DictComp(expr key, expr value, comprehension* generators)\n | GeneratorExp(expr elt, comprehension* generators)\n | Await(expr value)\n | Yield(expr? value)\n | YieldFrom(expr value)\n | Compare(expr left, cmpop* ops, expr* comparators)\n | Call(expr func, expr* args, keyword* keywords)\n | FormattedValue(expr value, int conversion, expr? format_spec)\n | Interpolation(expr value, constant str, int conversion, expr? format_spec)\n | JoinedStr(expr* values)\n | TemplateStr(expr* values)\n | Constant(constant value, string? kind)\n | Attribute(expr value, identifier attr, expr_context ctx)\n | Subscript(expr value, expr slice, expr_context ctx)\n | Starred(expr value, expr_context ctx)\n | Name(identifier id, expr_context ctx)\n | List(expr* elts, expr_context ctx)\n | Tuple(expr* elts, expr_context ctx)\n | Slice(expr? lower, expr? upper, expr? step)", + "_ast.expr_context" => "expr_context = Load | Store | Del", + "_ast.keyword" => "keyword(identifier? arg, expr value)", + "_ast.match_case" => "match_case(pattern pattern, expr? guard, stmt* body)", + "_ast.mod" => "mod = Module(stmt* body, type_ignore* type_ignores)\n | Interactive(stmt* body)\n | Expression(expr body)\n | FunctionType(expr* argtypes, expr returns)", + "_ast.operator" => "operator = Add | Sub | Mult | MatMult | Div | Mod | Pow | LShift | RShift | BitOr | BitXor | BitAnd | FloorDiv", + "_ast.pattern" => "pattern = MatchValue(expr value)\n | MatchSingleton(constant value)\n | MatchSequence(pattern* patterns)\n | MatchMapping(expr* keys, pattern* patterns, identifier? rest)\n | MatchClass(expr cls, pattern* patterns, identifier* kwd_attrs, pattern* kwd_patterns)\n | MatchStar(identifier? name)\n | MatchAs(pattern? pattern, identifier? name)\n | MatchOr(pattern* patterns)", + "_ast.stmt" => "stmt = FunctionDef(identifier name, arguments args, stmt* body, expr* decorator_list, expr? returns, string? type_comment, type_param* type_params)\n | AsyncFunctionDef(identifier name, arguments args, stmt* body, expr* decorator_list, expr? returns, string? type_comment, type_param* type_params)\n | ClassDef(identifier name, expr* bases, keyword* keywords, stmt* body, expr* decorator_list, type_param* type_params)\n | Return(expr? value)\n | Delete(expr* targets)\n | Assign(expr* targets, expr value, string? type_comment)\n | TypeAlias(expr name, type_param* type_params, expr value)\n | AugAssign(expr target, operator op, expr value)\n | AnnAssign(expr target, expr annotation, expr? value, int simple)\n | For(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment)\n | AsyncFor(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment)\n | While(expr test, stmt* body, stmt* orelse)\n | If(expr test, stmt* body, stmt* orelse)\n | With(withitem* items, stmt* body, string? type_comment)\n | AsyncWith(withitem* items, stmt* body, string? type_comment)\n | Match(expr subject, match_case* cases)\n | Raise(expr? exc, expr? cause)\n | Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)\n | TryStar(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)\n | Assert(expr test, expr? msg)\n | Import(alias* names)\n | ImportFrom(identifier? module, alias* names, int? level)\n | Global(identifier* names)\n | Nonlocal(identifier* names)\n | Expr(expr value)\n | Pass\n | Break\n | Continue", + "_ast.type_ignore" => "type_ignore = TypeIgnore(int lineno, string tag)", + "_ast.type_param" => "type_param = TypeVar(identifier name, expr? bound, expr? default_value)\n | ParamSpec(identifier name, expr? default_value)\n | TypeVarTuple(identifier name, expr? default_value)", + "_ast.unaryop" => "unaryop = Invert | Not | UAdd | USub", + "_ast.withitem" => "withitem(expr context_expr, expr? optional_vars)", "_asyncio" => "Accelerator module for asyncio", "_asyncio.Future" => "This class is *almost* compatible with concurrent.futures.Future.\n\nDifferences:\n\n- result() and exception() do not take a timeout argument and\n raise an exception when the future isn't done yet.\n\n- Callbacks registered with add_done_callback() are always called\n via the event loop's call_soon_threadsafe().\n\n- This class is not compatible with the wait() and as_completed()\n methods in the concurrent.futures package.", "_asyncio.Future.__await__" => "Return an iterator to be used in await expression.", diff --git a/crates/literal/src/float.rs b/crates/literal/src/float.rs index 4d0d65cbb34..79c655487cb 100644 --- a/crates/literal/src/float.rs +++ b/crates/literal/src/float.rs @@ -23,7 +23,7 @@ fn parse_inner(literal: &[u8]) -> Option { } pub fn is_integer(v: f64) -> bool { - (v - v.round()).abs() < f64::EPSILON + v.is_finite() && v.fract() == 0.0 } fn format_nan(case: Case) -> String { diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index 94ffffafc39..838265d62c9 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -42,7 +42,7 @@ impl Frame { } #[pygetset] - fn f_builtins(&self) -> PyDictRef { + fn f_builtins(&self) -> PyObjectRef { self.builtins.clone() } diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index 163b484a8b0..522056169eb 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -130,7 +130,7 @@ impl PyFunction { let builtins = globals.get_item("__builtins__", vm).unwrap_or_else(|_| { // If not in globals, inherit from current execution context if let Some(frame) = vm.current_frame() { - frame.builtins.clone().into() + frame.builtins.clone() } else { vm.builtins.dict().into() } @@ -515,7 +515,7 @@ impl Py { let frame = Frame::new( code.clone(), Scope::new(Some(locals), self.globals.clone()), - vm.builtins.dict(), + self.builtins.clone(), self.closure.as_ref().map_or(&[], |c| c.as_slice()), Some(self.to_owned().into()), vm, diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index fd46f9058c0..74de2c12eb4 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -937,24 +937,44 @@ impl PyType { #[pygetset] fn __annotations__(&self, vm: &VirtualMachine) -> PyResult { - if !self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) { - return Err(vm.new_attribute_error(format!( - "type object '{}' has no attribute '__annotations__'", - self.name() - ))); - } - - // First try __annotations__ (e.g. for "from __future__ import annotations") let attrs = self.attributes.read(); if let Some(annotations) = attrs.get(identifier!(vm, __annotations__)).cloned() { - return Ok(annotations); + // Ignore the __annotations__ descriptor stored on type itself. + if !annotations.class().is(vm.ctx.types.getset_type) { + if vm.is_none(&annotations) + || annotations.class().is(vm.ctx.types.dict_type) + || self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) + { + return Ok(annotations); + } + return Err(vm.new_attribute_error(format!( + "type object '{}' has no attribute '__annotations__'", + self.name() + ))); + } } // Then try __annotations_cache__ if let Some(annotations) = attrs.get(identifier!(vm, __annotations_cache__)).cloned() { - return Ok(annotations); + if vm.is_none(&annotations) + || annotations.class().is(vm.ctx.types.dict_type) + || self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) + { + return Ok(annotations); + } + return Err(vm.new_attribute_error(format!( + "type object '{}' has no attribute '__annotations__'", + self.name() + ))); } drop(attrs); + if !self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) { + return Err(vm.new_attribute_error(format!( + "type object '{}' has no attribute '__annotations__'", + self.name() + ))); + } + // Get __annotate__ and call it if callable let annotate = self.__annotate__(vm)?; let annotations = if annotate.is_callable() { diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 90c20a62597..807d751e723 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -72,7 +72,7 @@ pub struct Frame { pub(crate) cells_frees: Box<[PyCellRef]>, pub locals: ArgMapping, pub globals: PyDictRef, - pub builtins: PyDictRef, + pub builtins: PyObjectRef, // on feature=threading, this is a duplicate of FrameState.lasti, but it's faster to do an // atomic store than it is to do a fetch_add, for every instruction executed @@ -137,7 +137,7 @@ impl Frame { pub(crate) fn new( code: PyRef, scope: Scope, - builtins: PyDictRef, + builtins: PyObjectRef, closure: &[PyCellRef], func_obj: Option, vm: &VirtualMachine, @@ -352,7 +352,7 @@ struct ExecutingFrame<'a> { cells_frees: &'a [PyCellRef], locals: &'a ArgMapping, globals: &'a PyDictRef, - builtins: &'a PyDictRef, + builtins: &'a PyObjectRef, object: &'a Py, lasti: &'a Lasti, state: &'a mut FrameState, @@ -1207,7 +1207,31 @@ impl ExecutingFrame<'_> { Instruction::LoadAttr { idx } => self.load_attr(vm, idx.get(arg)), Instruction::LoadSuperAttr { arg: idx } => self.load_super_attr(vm, idx.get(arg)), Instruction::LoadBuildClass => { - self.push_value(vm.builtins.get_attr(identifier!(vm, __build_class__), vm)?); + let build_class = + if let Some(builtins_dict) = self.builtins.downcast_ref::() { + builtins_dict + .get_item_opt(identifier!(vm, __build_class__), vm)? + .ok_or_else(|| { + vm.new_name_error( + "__build_class__ not found".to_owned(), + identifier!(vm, __build_class__).to_owned(), + ) + })? + } else { + self.builtins + .get_item(identifier!(vm, __build_class__), vm) + .map_err(|e| { + if e.fast_isinstance(vm.ctx.exceptions.key_error) { + vm.new_name_error( + "__build_class__ not found".to_owned(), + identifier!(vm, __build_class__).to_owned(), + ) + } else { + e + } + })? + }; + self.push_value(build_class); Ok(None) } Instruction::LoadLocals => { @@ -2124,11 +2148,26 @@ impl ExecutingFrame<'_> { #[inline] fn load_global_or_builtin(&self, name: &Py, vm: &VirtualMachine) -> PyResult { - self.globals - .get_chain(self.builtins, name, vm)? - .ok_or_else(|| { - vm.new_name_error(format!("name '{name}' is not defined"), name.to_owned()) + if let Some(builtins_dict) = self.builtins.downcast_ref::() { + // Fast path: builtins is a dict + self.globals + .get_chain(builtins_dict, name, vm)? + .ok_or_else(|| { + vm.new_name_error(format!("name '{name}' is not defined"), name.to_owned()) + }) + } else { + // Slow path: builtins is not a dict, use generic __getitem__ + if let Some(value) = self.globals.get_item_opt(name, vm)? { + return Ok(value); + } + self.builtins.get_item(name, vm).map_err(|e| { + if e.fast_isinstance(vm.ctx.exceptions.key_error) { + vm.new_name_error(format!("name '{name}' is not defined"), name.to_owned()) + } else { + e + } }) + } } #[cfg_attr(feature = "flame-it", flame("Frame"))] diff --git a/crates/vm/src/stdlib/ast.rs b/crates/vm/src/stdlib/ast.rs index bdf90811259..cb99fde6356 100644 --- a/crates/vm/src/stdlib/ast.rs +++ b/crates/vm/src/stdlib/ast.rs @@ -9,7 +9,7 @@ pub(crate) use python::_ast::module_def; mod pyast; use crate::builtins::{PyInt, PyStr}; -use crate::stdlib::ast::module::{Mod, ModInteractive}; +use crate::stdlib::ast::module::{Mod, ModFunctionType, ModInteractive}; use crate::stdlib::ast::node::BoxedSlice; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult, @@ -36,6 +36,8 @@ use rustpython_codegen as codegen; pub(crate) use python::_ast::NodeAst; mod python; +mod repr; +mod validate; mod argument; mod basic; @@ -196,6 +198,27 @@ fn range_from_object( let start_col_val: i32 = start_column.try_to_primitive(vm)?; let end_col_val: i32 = end_column.try_to_primitive(vm)?; + if start_row_val > end_row_val { + return Err(vm.new_value_error(format!( + "AST node line range ({}, {}) is not valid", + start_row_val, end_row_val + ))); + } + if (start_row_val < 0 && end_row_val != start_row_val) + || (start_col_val < 0 && end_col_val != start_col_val) + { + return Err(vm.new_value_error(format!( + "AST node column range ({}, {}) for line range ({}, {}) is not valid", + start_col_val, end_col_val, start_row_val, end_row_val + ))); + } + if start_row_val == end_row_val && start_col_val > end_col_val { + return Err(vm.new_value_error(format!( + "line {}, column {}-{} is not a valid range", + start_row_val, start_col_val, end_col_val + ))); + } + let location = PySourceRange { start: PySourceLocation { row: Row(if start_row_val > 0 { @@ -306,10 +329,116 @@ pub(crate) fn parse( vm: &VirtualMachine, source: &str, mode: parser::Mode, + optimize: u8, + target_version: Option, + type_comments: bool, ) -> Result { let source_file = SourceFileBuilder::new("".to_owned(), source.to_owned()).finish(); - let top = parser::parse(source, mode.into()) - .map_err(|parse_error| { + let mut options = parser::ParseOptions::from(mode); + let target_version = target_version.unwrap_or(ast::PythonVersion::PY314); + options = options.with_target_version(target_version); + let parsed = parser::parse(source, options).map_err(|parse_error| { + let range = text_range_to_source_range(&source_file, parse_error.location); + ParseError { + error: parse_error.error, + raw_location: parse_error.location, + location: range.start.to_source_location(), + end_location: range.end.to_source_location(), + source_path: "".to_string(), + } + })?; + + if let Some(error) = parsed.unsupported_syntax_errors().first() { + let range = text_range_to_source_range(&source_file, error.range()); + return Err(ParseError { + error: parser::ParseErrorType::OtherError(error.to_string()), + raw_location: error.range(), + location: range.start.to_source_location(), + end_location: range.end.to_source_location(), + source_path: "".to_string(), + } + .into()); + } + + let mut top = parsed.into_syntax(); + if optimize > 0 { + fold_match_value_constants(&mut top); + } + if optimize >= 2 { + strip_docstrings(&mut top); + } + let top = match top { + ast::Mod::Module(m) => Mod::Module(m), + ast::Mod::Expression(e) => Mod::Expression(e), + }; + let obj = top.ast_to_object(vm, &source_file); + if type_comments && obj.class().is(pyast::NodeModModule::static_type()) { + let type_ignores = type_ignores_from_source(vm, source)?; + let dict = obj.as_object().dict().unwrap(); + dict.set_item("type_ignores", vm.ctx.new_list(type_ignores).into(), vm) + .unwrap(); + } + Ok(obj) +} + +#[cfg(feature = "parser")] +pub(crate) fn wrap_interactive(vm: &VirtualMachine, module_obj: PyObjectRef) -> PyResult { + if !module_obj.class().is(pyast::NodeModModule::static_type()) { + return Err(vm.new_type_error("expected Module node".to_owned())); + } + let body = get_node_field(vm, &module_obj, "body", "Module")?; + let node = NodeAst + .into_ref_with_type(vm, pyast::NodeModInteractive::static_type().to_owned()) + .unwrap(); + let dict = node.as_object().dict().unwrap(); + dict.set_item("body", body, vm).unwrap(); + Ok(node.into()) +} + +#[cfg(feature = "parser")] +pub(crate) fn parse_func_type( + vm: &VirtualMachine, + source: &str, + optimize: u8, + target_version: Option, +) -> Result { + let _ = optimize; + let _ = target_version; + let source = source.trim(); + let mut depth = 0i32; + let mut split_at = None; + let mut chars = source.chars().peekable(); + let mut idx = 0usize; + while let Some(ch) = chars.next() { + match ch { + '(' | '[' | '{' => depth += 1, + ')' | ']' | '}' => depth -= 1, + '-' if depth == 0 && chars.peek() == Some(&'>') => { + split_at = Some(idx); + break; + } + _ => {} + } + idx += ch.len_utf8(); + } + + let Some(split_at) = split_at else { + return Err(ParseError { + error: parser::ParseErrorType::OtherError("invalid func_type".to_owned()), + raw_location: TextRange::default(), + location: SourceLocation::default(), + end_location: SourceLocation::default(), + source_path: "".to_owned(), + } + .into()); + }; + + let left = source[..split_at].trim(); + let right = source[split_at + 2..].trim(); + + let parse_expr = |expr_src: &str| -> Result { + let source_file = SourceFileBuilder::new("".to_owned(), expr_src.to_owned()).finish(); + let parsed = parser::parse_expression(expr_src).map_err(|parse_error| { let range = text_range_to_source_range(&source_file, parse_error.location); ParseError { error: parse_error.error, @@ -318,13 +447,286 @@ pub(crate) fn parse( end_location: range.end.to_source_location(), source_path: "".to_string(), } - })? - .into_syntax(); - let top = match top { - ast::Mod::Module(m) => Mod::Module(m), - ast::Mod::Expression(e) => Mod::Expression(e), + })?; + Ok(*parsed.into_syntax().body) }; - Ok(top.ast_to_object(vm, &source_file)) + + let arg_expr = parse_expr(left)?; + let returns = parse_expr(right)?; + + let argtypes: Vec = match arg_expr { + ast::Expr::Tuple(tup) => tup.elts, + ast::Expr::Name(_) | ast::Expr::Subscript(_) | ast::Expr::Attribute(_) => vec![arg_expr], + other => vec![other], + }; + + let func_type = ModFunctionType { + argtypes: argtypes.into_boxed_slice(), + returns, + range: TextRange::default(), + }; + let source_file = SourceFileBuilder::new("".to_owned(), source.to_owned()).finish(); + Ok(func_type.ast_to_object(vm, &source_file)) +} + +fn type_ignores_from_source( + vm: &VirtualMachine, + source: &str, +) -> Result, CompileError> { + let mut ignores = Vec::new(); + for (idx, line) in source.lines().enumerate() { + let Some(pos) = line.find("#") else { + continue; + }; + let comment = &line[pos + 1..]; + let comment = comment.trim_start(); + let Some(rest) = comment.strip_prefix("type: ignore") else { + continue; + }; + let tag = rest.trim_start(); + let tag = if tag.is_empty() { "" } else { tag }; + let node = NodeAst + .into_ref_with_type( + vm, + pyast::NodeTypeIgnoreTypeIgnore::static_type().to_owned(), + ) + .unwrap(); + let dict = node.as_object().dict().unwrap(); + let lineno = idx + 1; + dict.set_item("lineno", vm.ctx.new_int(lineno).into(), vm) + .unwrap(); + dict.set_item("tag", vm.ctx.new_str(tag).into(), vm) + .unwrap(); + ignores.push(node.into()); + } + Ok(ignores) +} + +#[cfg(feature = "parser")] +fn fold_match_value_constants(top: &mut ast::Mod) { + match top { + ast::Mod::Module(module) => fold_stmts(&mut module.body), + ast::Mod::Expression(_expr) => {} + } +} + +#[cfg(feature = "parser")] +fn strip_docstrings(top: &mut ast::Mod) { + match top { + ast::Mod::Module(module) => strip_docstring_in_body(&mut module.body), + ast::Mod::Expression(_expr) => {} + } +} + +#[cfg(feature = "parser")] +fn strip_docstring_in_body(body: &mut Vec) { + if let Some(range) = take_docstring(body) + && body.is_empty() + { + let start_offset = range.start(); + let end_offset = start_offset + TextSize::from(4); + let pass_range = TextRange::new(start_offset, end_offset); + body.push(ast::Stmt::Pass(ast::StmtPass { + node_index: Default::default(), + range: pass_range, + })); + } + for stmt in body { + match stmt { + ast::Stmt::FunctionDef(def) => strip_docstring_in_body(&mut def.body), + ast::Stmt::ClassDef(def) => strip_docstring_in_body(&mut def.body), + _ => {} + } + } +} + +#[cfg(feature = "parser")] +fn take_docstring(body: &mut Vec) -> Option { + let ast::Stmt::Expr(expr_stmt) = body.first()? else { + return None; + }; + if matches!(expr_stmt.value.as_ref(), ast::Expr::StringLiteral(_)) { + let range = expr_stmt.range; + body.remove(0); + return Some(range); + } + None +} + +#[cfg(feature = "parser")] +fn fold_stmts(stmts: &mut [ast::Stmt]) { + for stmt in stmts { + fold_stmt(stmt); + } +} + +#[cfg(feature = "parser")] +fn fold_stmt(stmt: &mut ast::Stmt) { + use ast::Stmt; + match stmt { + Stmt::FunctionDef(def) => fold_stmts(&mut def.body), + Stmt::ClassDef(def) => fold_stmts(&mut def.body), + Stmt::For(stmt) => { + fold_stmts(&mut stmt.body); + fold_stmts(&mut stmt.orelse); + } + Stmt::While(stmt) => { + fold_stmts(&mut stmt.body); + fold_stmts(&mut stmt.orelse); + } + Stmt::If(stmt) => { + fold_stmts(&mut stmt.body); + for clause in &mut stmt.elif_else_clauses { + fold_stmts(&mut clause.body); + } + } + Stmt::With(stmt) => { + fold_stmts(&mut stmt.body); + } + Stmt::Try(stmt) => { + fold_stmts(&mut stmt.body); + fold_stmts(&mut stmt.orelse); + fold_stmts(&mut stmt.finalbody); + } + Stmt::Match(stmt) => { + for case in &mut stmt.cases { + fold_pattern(&mut case.pattern); + if let Some(expr) = case.guard.as_deref_mut() { + fold_expr(expr); + } + fold_stmts(&mut case.body); + } + } + _ => {} + } +} + +#[cfg(feature = "parser")] +fn fold_pattern(pattern: &mut ast::Pattern) { + use ast::Pattern; + match pattern { + Pattern::MatchValue(value) => fold_expr(&mut value.value), + Pattern::MatchSequence(seq) => { + for pattern in &mut seq.patterns { + fold_pattern(pattern); + } + } + Pattern::MatchMapping(mapping) => { + for key in &mut mapping.keys { + fold_expr(key); + } + for pattern in &mut mapping.patterns { + fold_pattern(pattern); + } + } + Pattern::MatchClass(class) => { + for pattern in &mut class.arguments.patterns { + fold_pattern(pattern); + } + for keyword in &mut class.arguments.keywords { + fold_pattern(&mut keyword.pattern); + } + } + Pattern::MatchAs(match_as) => { + if let Some(pattern) = match_as.pattern.as_deref_mut() { + fold_pattern(pattern); + } + } + Pattern::MatchOr(match_or) => { + for pattern in &mut match_or.patterns { + fold_pattern(pattern); + } + } + Pattern::MatchSingleton(_) | Pattern::MatchStar(_) => {} + } +} + +#[cfg(feature = "parser")] +fn fold_expr(expr: &mut ast::Expr) { + use ast::Expr; + if let Expr::UnaryOp(unary) = expr { + fold_expr(&mut unary.operand); + if matches!(unary.op, ast::UnaryOp::USub) + && let Expr::NumberLiteral(number_literal) = unary.operand.as_ref() + { + let number = match &number_literal.value { + ast::Number::Int(value) => { + if *value == ast::Int::ZERO { + Some(ast::Number::Int(ast::Int::ZERO)) + } else { + None + } + } + ast::Number::Float(value) => Some(ast::Number::Float(-value)), + ast::Number::Complex { real, imag } => Some(ast::Number::Complex { + real: -real, + imag: -imag, + }), + }; + if let Some(number) = number { + *expr = Expr::NumberLiteral(ast::ExprNumberLiteral { + node_index: unary.node_index.clone(), + range: unary.range, + value: number, + }); + return; + } + } + } + if let Expr::BinOp(binop) = expr { + fold_expr(&mut binop.left); + fold_expr(&mut binop.right); + + let Expr::NumberLiteral(left) = binop.left.as_ref() else { + return; + }; + let Expr::NumberLiteral(right) = binop.right.as_ref() else { + return; + }; + + if let Some(number) = fold_number_binop(&left.value, &binop.op, &right.value) { + *expr = Expr::NumberLiteral(ast::ExprNumberLiteral { + node_index: binop.node_index.clone(), + range: binop.range, + value: number, + }); + } + } +} + +#[cfg(feature = "parser")] +fn fold_number_binop( + left: &ast::Number, + op: &ast::Operator, + right: &ast::Number, +) -> Option { + let (left_real, left_imag, left_is_complex) = number_to_complex(left)?; + let (right_real, right_imag, right_is_complex) = number_to_complex(right)?; + + if !(left_is_complex || right_is_complex) { + return None; + } + + match op { + ast::Operator::Add => Some(ast::Number::Complex { + real: left_real + right_real, + imag: left_imag + right_imag, + }), + ast::Operator::Sub => Some(ast::Number::Complex { + real: left_real - right_real, + imag: left_imag - right_imag, + }), + _ => None, + } +} + +#[cfg(feature = "parser")] +fn number_to_complex(number: &ast::Number) -> Option<(f64, f64, bool)> { + match number { + ast::Number::Complex { real, imag } => Some((*real, *imag, true)), + ast::Number::Float(value) => Some((*value, 0.0, false)), + ast::Number::Int(value) => value.as_i64().map(|value| (value as f64, 0.0, false)), + } } #[cfg(feature = "codegen")] @@ -342,6 +744,7 @@ pub(crate) fn compile( let source_file = SourceFileBuilder::new(filename.to_owned(), "".to_owned()).finish(); let ast: Mod = Node::ast_from_object(vm, &source_file, object)?; + validate::validate_mod(vm, &ast)?; let ast = match ast { Mod::Module(m) => ast::Mod::Module(m), Mod::Interactive(ModInteractive { range, body }) => ast::Mod::Module(ast::ModModule { @@ -360,16 +763,27 @@ pub(crate) fn compile( Ok(vm.ctx.new_code(code).into()) } +#[cfg(feature = "codegen")] +pub(crate) fn validate_ast_object(vm: &VirtualMachine, object: PyObjectRef) -> PyResult<()> { + let source_file = SourceFileBuilder::new("".to_owned(), "".to_owned()).finish(); + let ast: Mod = Node::ast_from_object(vm, &source_file, object)?; + validate::validate_mod(vm, &ast)?; + Ok(()) +} + // Used by builtins::compile() pub const PY_COMPILE_FLAG_AST_ONLY: i32 = 0x0400; // The following flags match the values from Include/cpython/compile.h // Caveat emptor: These flags are undocumented on purpose and depending // on their effect outside the standard library is **unsupported**. +pub const PY_CF_SOURCE_IS_UTF8: i32 = 0x0100; pub const PY_CF_DONT_IMPLY_DEDENT: i32 = 0x200; +pub const PY_CF_IGNORE_COOKIE: i32 = 0x0800; pub const PY_CF_ALLOW_INCOMPLETE_INPUT: i32 = 0x4000; pub const PY_CF_OPTIMIZED_AST: i32 = 0x8000 | PY_COMPILE_FLAG_AST_ONLY; pub const PY_CF_TYPE_COMMENTS: i32 = 0x1000; +pub const PY_CF_ALLOW_TOP_LEVEL_AWAIT: i32 = 0x2000; // __future__ flags - sync with Lib/__future__.py // TODO: These flags aren't being used in rust code @@ -389,7 +803,10 @@ const CO_FUTURE_ANNOTATIONS: i32 = 0x1000000; // Used by builtins::compile() - the summary of all flags pub const PY_COMPILE_FLAGS_MASK: i32 = PY_COMPILE_FLAG_AST_ONLY + | PY_CF_SOURCE_IS_UTF8 | PY_CF_DONT_IMPLY_DEDENT + | PY_CF_IGNORE_COOKIE + | PY_CF_ALLOW_TOP_LEVEL_AWAIT | PY_CF_ALLOW_INCOMPLETE_INPUT | PY_CF_OPTIMIZED_AST | PY_CF_TYPE_COMMENTS diff --git a/crates/vm/src/stdlib/ast/basic.rs b/crates/vm/src/stdlib/ast/basic.rs index 612b6144eea..ca518eaa520 100644 --- a/crates/vm/src/stdlib/ast/basic.rs +++ b/crates/vm/src/stdlib/ast/basic.rs @@ -5,7 +5,7 @@ use rustpython_compiler_core::SourceFile; impl Node for ast::Identifier { fn ast_to_object(self, vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { let id = self.as_str(); - vm.ctx.new_str(id).into() + vm.ctx.intern_str(id).to_object() } fn ast_from_object( diff --git a/crates/vm/src/stdlib/ast/constant.rs b/crates/vm/src/stdlib/ast/constant.rs index a6aac224585..1030a037f17 100644 --- a/crates/vm/src/stdlib/ast/constant.rs +++ b/crates/vm/src/stdlib/ast/constant.rs @@ -301,31 +301,41 @@ fn constant_to_ruff_expr(value: Constant) -> ast::Expr { // TODO: Does this matter? parenthesized: true, }), - ConstantLiteral::FrozenSet(value) => ast::Expr::Call(ast::ExprCall { - node_index: Default::default(), - range, - // idk lol - func: Box::new(ast::Expr::Name(ast::ExprName { - node_index: Default::default(), - range: TextRange::default(), - id: ast::name::Name::new_static("frozenset"), - ctx: ast::ExprContext::Load, - })), - arguments: ast::Arguments { + ConstantLiteral::FrozenSet(value) => { + let args = if value.is_empty() { + Vec::new() + } else { + vec![ast::Expr::Set(ast::ExprSet { + node_index: Default::default(), + range: TextRange::default(), + elts: value + .into_iter() + .map(|value| { + constant_to_ruff_expr(Constant { + range: TextRange::default(), + value, + }) + }) + .collect(), + })] + }; + ast::Expr::Call(ast::ExprCall { node_index: Default::default(), range, - args: value - .into_iter() - .map(|value| { - constant_to_ruff_expr(Constant { - range: TextRange::default(), - value, - }) - }) - .collect(), - keywords: Box::default(), - }, - }), + func: Box::new(ast::Expr::Name(ast::ExprName { + node_index: Default::default(), + range: TextRange::default(), + id: ast::name::Name::new_static("frozenset"), + ctx: ast::ExprContext::Load, + })), + arguments: ast::Arguments { + node_index: Default::default(), + range, + args: args.into(), + keywords: Box::default(), + }, + }) + } ConstantLiteral::Float(value) => ast::Expr::NumberLiteral(ast::ExprNumberLiteral { node_index: Default::default(), range, diff --git a/crates/vm/src/stdlib/ast/elif_else_clause.rs b/crates/vm/src/stdlib/ast/elif_else_clause.rs index b27e956077e..0afdbc02ac1 100644 --- a/crates/vm/src/stdlib/ast/elif_else_clause.rs +++ b/crates/vm/src/stdlib/ast/elif_else_clause.rs @@ -29,6 +29,10 @@ pub(super) fn ast_to_object( let orelse = if let Some(next) = rest.next() { if next.test.is_some() { + let next = ast::ElifElseClause { + range: TextRange::new(next.range.start(), range.end()), + ..next + }; vm.ctx .new_list(vec![ast_to_object(next, rest, vm, source_file)]) .into() @@ -58,7 +62,9 @@ pub(super) fn ast_from_object( )?; let range = range_from_object(vm, source_file, object, "If")?; - let elif_else_clauses = if let [ast::Stmt::If(_)] = &*orelse { + let elif_else_clauses = if orelse.is_empty() { + vec![] + } else if let [ast::Stmt::If(_)] = &*orelse { let Some(ast::Stmt::If(ast::StmtIf { node_index: _, range, diff --git a/crates/vm/src/stdlib/ast/expression.rs b/crates/vm/src/stdlib/ast/expression.rs index ebfa471c842..654dc234684 100644 --- a/crates/vm/src/stdlib/ast/expression.rs +++ b/crates/vm/src/stdlib/ast/expression.rs @@ -126,6 +126,13 @@ impl Node for ast::Expr { Constant::ast_from_object(vm, source_file, object)?.into_expr() } else if cls.is(pyast::NodeExprJoinedStr::static_type()) { JoinedStr::ast_from_object(vm, source_file, object)?.into_expr() + } else if cls.is(pyast::NodeExprTemplateStr::static_type()) { + let template = string::TemplateStr::ast_from_object(vm, source_file, object)?; + return string::template_str_to_expr(vm, template); + } else if cls.is(pyast::NodeExprInterpolation::static_type()) { + let interpolation = + string::TStringInterpolation::ast_from_object(vm, source_file, object)?; + return string::interpolation_to_expr(vm, interpolation); } else { return Err(vm.new_type_error(format!( "expected some sort of expr, but got {}", @@ -455,6 +462,11 @@ impl Node for ast::ExprDict { source_file, get_node_field(vm, &object, "values", "Dict")?, )?; + if keys.len() != values.len() { + return Err(vm.new_value_error( + "Dict doesn't have the same number of keys as values".to_owned(), + )); + } let items = keys .into_iter() .zip(values) @@ -647,8 +659,18 @@ impl Node for ast::ExprGenerator { elt, generators, range, - parenthesized: _, + parenthesized, } = self; + let range = if parenthesized { + range + } else { + TextRange::new( + range + .start() + .saturating_sub(ruff_text_size::TextSize::from(1)), + range.end() + ruff_text_size::TextSize::from(1), + ) + }; let node = NodeAst .into_ref_with_type(vm, pyast::NodeExprGeneratorExp::static_type().to_owned()) .unwrap(); @@ -1234,6 +1256,9 @@ impl Node for ast::ExprContext { unimplemented!("Invalid expression context is not allowed in Python AST") } }; + if let Some(instance) = node_type.get_attr(vm.ctx.intern_str("_instance")) { + return instance; + } NodeAst .into_ref_with_type(vm, node_type.to_owned()) .unwrap() diff --git a/crates/vm/src/stdlib/ast/module.rs b/crates/vm/src/stdlib/ast/module.rs index 78f897b8930..cfedba606b0 100644 --- a/crates/vm/src/stdlib/ast/module.rs +++ b/crates/vm/src/stdlib/ast/module.rs @@ -86,7 +86,7 @@ impl Node for ast::ModModule { vm, ) .unwrap(); - node_add_location(&dict, range, vm, source_file); + let _ = range; node.into() } @@ -126,7 +126,7 @@ impl Node for ModInteractive { let dict = node.as_object().dict().unwrap(); dict.set_item("body", body.ast_to_object(vm, source_file), vm) .unwrap(); - node_add_location(&dict, range, vm, source_file); + let _ = range; node.into() } @@ -160,7 +160,7 @@ impl Node for ast::ModExpression { let dict = node.as_object().dict().unwrap(); dict.set_item("body", body.ast_to_object(vm, source_file), vm) .unwrap(); - node_add_location(&dict, range, vm, source_file); + let _ = range; node.into() } @@ -207,7 +207,7 @@ impl Node for ModFunctionType { .unwrap(); dict.set_item("returns", returns.ast_to_object(vm, source_file), vm) .unwrap(); - node_add_location(&dict, range, vm, source_file); + let _ = range; node.into() } diff --git a/crates/vm/src/stdlib/ast/operator.rs b/crates/vm/src/stdlib/ast/operator.rs index 23aa63c7031..dd1ef3b1883 100644 --- a/crates/vm/src/stdlib/ast/operator.rs +++ b/crates/vm/src/stdlib/ast/operator.rs @@ -8,6 +8,9 @@ impl Node for ast::BoolOp { Self::And => pyast::NodeBoolOpAnd::static_type(), Self::Or => pyast::NodeBoolOpOr::static_type(), }; + if let Some(instance) = node_type.get_attr(vm.ctx.intern_str("_instance")) { + return instance; + } NodeAst .into_ref_with_type(vm, node_type.to_owned()) .unwrap() @@ -51,6 +54,9 @@ impl Node for ast::Operator { Self::BitAnd => pyast::NodeOperatorBitAnd::static_type(), Self::FloorDiv => pyast::NodeOperatorFloorDiv::static_type(), }; + if let Some(instance) = node_type.get_attr(vm.ctx.intern_str("_instance")) { + return instance; + } NodeAst .into_ref_with_type(vm, node_type.to_owned()) .unwrap() @@ -107,6 +113,9 @@ impl Node for ast::UnaryOp { Self::UAdd => pyast::NodeUnaryOpUAdd::static_type(), Self::USub => pyast::NodeUnaryOpUSub::static_type(), }; + if let Some(instance) = node_type.get_attr(vm.ctx.intern_str("_instance")) { + return instance; + } NodeAst .into_ref_with_type(vm, node_type.to_owned()) .unwrap() @@ -151,6 +160,9 @@ impl Node for ast::CmpOp { Self::In => pyast::NodeCmpOpIn::static_type(), Self::NotIn => pyast::NodeCmpOpNotIn::static_type(), }; + if let Some(instance) = node_type.get_attr(vm.ctx.intern_str("_instance")) { + return instance; + } NodeAst .into_ref_with_type(vm, node_type.to_owned()) .unwrap() diff --git a/crates/vm/src/stdlib/ast/other.rs b/crates/vm/src/stdlib/ast/other.rs index c7a1974351a..5c0803ac594 100644 --- a/crates/vm/src/stdlib/ast/other.rs +++ b/crates/vm/src/stdlib/ast/other.rs @@ -26,7 +26,7 @@ impl Node for ast::ConversionFlag { // /// This is just a string, not strictly an AST node. But it makes AST conversions easier. impl Node for ast::name::Name { fn ast_to_object(self, vm: &VirtualMachine, _source_file: &SourceFile) -> PyObjectRef { - vm.ctx.new_str(self.as_str()).to_pyobject(vm) + vm.ctx.intern_str(self.as_str()).to_object() } fn ast_from_object( diff --git a/crates/vm/src/stdlib/ast/parameter.rs b/crates/vm/src/stdlib/ast/parameter.rs index dc4f32203ca..15ff237e50d 100644 --- a/crates/vm/src/stdlib/ast/parameter.rs +++ b/crates/vm/src/stdlib/ast/parameter.rs @@ -42,7 +42,7 @@ impl Node for ast::Parameters { .unwrap(); dict.set_item("defaults", defaults.ast_to_object(vm, source_file), vm) .unwrap(); - node_add_location(&dict, range, vm, source_file); + let _ = range; node.into() } @@ -61,7 +61,7 @@ impl Node for ast::Parameters { source_file, get_node_field(vm, &object, "kw_defaults", "arguments")?, )?; - let kwonlyargs = merge_keyword_parameter_defaults(kwonlyargs, kw_defaults); + let kwonlyargs = merge_keyword_parameter_defaults(vm, kwonlyargs, kw_defaults)?; let posonlyargs = Node::ast_from_object( vm, @@ -78,7 +78,8 @@ impl Node for ast::Parameters { source_file, get_node_field(vm, &object, "defaults", "arguments")?, )?; - let (posonlyargs, args) = merge_positional_parameter_defaults(posonlyargs, args, defaults); + let (posonlyargs, args) = + merge_positional_parameter_defaults(vm, posonlyargs, args, defaults)?; Ok(Self { node_index: Default::default(), @@ -321,13 +322,14 @@ fn extract_positional_parameter_defaults( /// Merges the keyword parameters with their default values, opposite of [`extract_positional_parameter_defaults`]. fn merge_positional_parameter_defaults( + vm: &VirtualMachine, posonlyargs: PositionalParameters, args: PositionalParameters, defaults: ParameterDefaults, -) -> ( +) -> PyResult<( Vec, Vec, -) { +)> { let posonlyargs = posonlyargs.args; let args = args.args; let defaults = defaults.defaults; @@ -352,7 +354,11 @@ fn merge_positional_parameter_defaults( // If an argument has a default value, insert it // Note that "defaults" will only contain default values for the last "n" parameters // so we need to skip the first "total_argument_count - n" arguments. - let default_argument_count = posonlyargs.len() + args.len() - defaults.len(); + let total_args = posonlyargs.len() + args.len(); + if defaults.len() > total_args { + return Err(vm.new_value_error("more positional defaults than args on arguments")); + } + let default_argument_count = total_args - defaults.len(); for (arg, default) in posonlyargs .iter_mut() .chain(args.iter_mut()) @@ -362,7 +368,7 @@ fn merge_positional_parameter_defaults( arg.default = default; } - (posonlyargs, args) + Ok((posonlyargs, args)) } fn extract_keyword_parameter_defaults( @@ -400,15 +406,21 @@ fn extract_keyword_parameter_defaults( /// Merges the keyword parameters with their default values, opposite of [`extract_keyword_parameter_defaults`]. fn merge_keyword_parameter_defaults( + vm: &VirtualMachine, kw_only_args: KeywordParameters, defaults: ParameterDefaults, -) -> Vec { - core::iter::zip(kw_only_args.keywords, defaults.defaults) +) -> PyResult> { + if kw_only_args.keywords.len() != defaults.defaults.len() { + return Err( + vm.new_value_error("length of kwonlyargs is not the same as kw_defaults on arguments") + ); + } + Ok(core::iter::zip(kw_only_args.keywords, defaults.defaults) .map(|(parameter, default)| ast::ParameterWithDefault { node_index: Default::default(), parameter, default, range: Default::default(), }) - .collect() + .collect()) } diff --git a/crates/vm/src/stdlib/ast/pattern.rs b/crates/vm/src/stdlib/ast/pattern.rs index 4531a989cb3..a78e8b5a844 100644 --- a/crates/vm/src/stdlib/ast/pattern.rs +++ b/crates/vm/src/stdlib/ast/pattern.rs @@ -357,16 +357,21 @@ impl Node for ast::PatternMatchClass { source_file, get_node_field(vm, &object, "patterns", "MatchClass")?, )?; - let kwd_attrs = Node::ast_from_object( + let kwd_attrs: PatternMatchClassKeywordAttributes = Node::ast_from_object( vm, source_file, get_node_field(vm, &object, "kwd_attrs", "MatchClass")?, )?; - let kwd_patterns = Node::ast_from_object( + let kwd_patterns: PatternMatchClassKeywordPatterns = Node::ast_from_object( vm, source_file, get_node_field(vm, &object, "kwd_patterns", "MatchClass")?, )?; + if kwd_attrs.0.len() != kwd_patterns.0.len() { + return Err(vm.new_value_error( + "MatchClass has mismatched kwd_attrs and kwd_patterns".to_owned(), + )); + } let (patterns, keywords) = merge_pattern_match_class(patterns, kwd_attrs, kwd_patterns); Ok(Self { diff --git a/crates/vm/src/stdlib/ast/pyast.rs b/crates/vm/src/stdlib/ast/pyast.rs index a32385a3e87..0cba8c0106c 100644 --- a/crates/vm/src/stdlib/ast/pyast.rs +++ b/crates/vm/src/stdlib/ast/pyast.rs @@ -1,7 +1,7 @@ #![allow(clippy::all)] use super::*; -use crate::builtins::{PyGenericAlias, PyTuple, PyTypeRef, make_union}; +use crate::builtins::{PyGenericAlias, PyTuple, PyTupleRef, PyTypeRef, make_union}; use crate::common::ascii; use crate::convert::ToPyObject; use crate::function::FuncArgs; @@ -18,35 +18,7 @@ macro_rules! impl_node { #[repr(transparent)] $vis struct $name($base); - #[pyclass(flags(HAS_DICT, BASETYPE))] - impl $name { - #[extend_class] - fn extend_class_with_fields(ctx: &Context, class: &'static Py) { - class.set_attr( - identifier!(ctx, _fields), - ctx.new_tuple(vec![ - $( - ctx.new_str(ascii!($field)).into() - ),* - ]).into(), - ); - - class.set_attr( - identifier!(ctx, _attributes), - ctx.new_list(vec![ - $( - ctx.new_str(ascii!($attr)).into() - ),* - ]).into(), - ); - - // Signal that this is a built-in AST node with field defaults - class.set_attr( - ctx.intern_str("_field_types"), - ctx.new_dict().into(), - ); - } - } + impl_base_node!($name, fields: [$($field),*], attributes: [$($attr),*]); }; // Without attributes ( @@ -88,11 +60,85 @@ macro_rules! impl_node { }; } +macro_rules! impl_base_node { + // Base node without fields/attributes (e.g. NodeMod, NodeExpr) + ($name:ident) => { + #[pyclass(flags(HAS_DICT, BASETYPE))] + impl $name { + #[pymethod] + fn __reduce__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + super::python::_ast::ast_reduce(zelf, vm) + } + + #[pymethod] + fn __replace__(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + super::python::_ast::ast_replace(zelf, args, vm) + } + + #[extend_class] + fn extend_class(_ctx: &Context, _class: &'static Py) {} + } + }; + // Leaf node with fields and attributes + ($name:ident, fields: [$($field:expr),*], attributes: [$($attr:expr),*]) => { + #[pyclass(flags(HAS_DICT, BASETYPE))] + impl $name { + #[pymethod] + fn __reduce__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + super::python::_ast::ast_reduce(zelf, vm) + } + + #[pymethod] + fn __replace__(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + super::python::_ast::ast_replace(zelf, args, vm) + } + + #[extend_class] + fn extend_class_with_fields(ctx: &Context, class: &'static Py) { + class.set_attr( + identifier!(ctx, _fields), + ctx.new_tuple(vec![ + $( + ctx.new_str(ascii!($field)).into() + ),* + ]) + .into(), + ); + + class.set_str_attr( + "__match_args__", + ctx.new_tuple(vec![ + $( + ctx.new_str(ascii!($field)).into() + ),* + ]), + ctx, + ); + + class.set_attr( + identifier!(ctx, _attributes), + ctx.new_tuple(vec![ + $( + ctx.new_str(ascii!($attr)).into() + ),* + ]) + .into(), + ); + + // Signal that this is a built-in AST node with field defaults + class.set_attr( + ctx.intern_str("_field_types"), + ctx.new_dict().into(), + ); + } + } + }; +} + #[pyclass(module = "_ast", name = "mod", base = NodeAst)] pub(crate) struct NodeMod(NodeAst); -#[pyclass(flags(HAS_DICT, BASETYPE))] -impl NodeMod {} +impl_base_node!(NodeMod); impl_node!( #[pyclass(module = "_ast", name = "Module", base = NodeMod)] @@ -116,8 +162,7 @@ impl_node!( #[repr(transparent)] pub(crate) struct NodeStmt(NodeAst); -#[pyclass(flags(HAS_DICT, BASETYPE))] -impl NodeStmt {} +impl_base_node!(NodeStmt); impl_node!( #[pyclass(module = "_ast", name = "FunctionType", base = NodeMod)] @@ -316,8 +361,7 @@ impl_node!( #[repr(transparent)] pub(crate) struct NodeExpr(NodeAst); -#[pyclass(flags(HAS_DICT, BASETYPE))] -impl NodeExpr {} +impl_base_node!(NodeExpr); impl_node!( #[pyclass(module = "_ast", name = "Continue", base = NodeStmt)] @@ -490,9 +534,18 @@ impl NodeExprConstant { .into(), ); + class.set_str_attr( + "__match_args__", + ctx.new_tuple(vec![ + ctx.new_str(ascii!("value")).into(), + ctx.new_str(ascii!("kind")).into(), + ]), + ctx, + ); + class.set_attr( identifier!(ctx, _attributes), - ctx.new_list(vec![ + ctx.new_tuple(vec![ ctx.new_str(ascii!("lineno")).into(), ctx.new_str(ascii!("col_offset")).into(), ctx.new_str(ascii!("end_lineno")).into(), @@ -567,8 +620,7 @@ impl_node!( #[repr(transparent)] pub(crate) struct NodeExprContext(NodeAst); -#[pyclass(flags(HAS_DICT, BASETYPE))] -impl NodeExprContext {} +impl_base_node!(NodeExprContext); impl_node!( #[pyclass(module = "_ast", name = "Slice", base = NodeExpr)] @@ -591,8 +643,7 @@ impl_node!( #[repr(transparent)] pub(crate) struct NodeBoolOp(NodeAst); -#[pyclass(flags(HAS_DICT, BASETYPE))] -impl NodeBoolOp {} +impl_base_node!(NodeBoolOp); impl_node!( #[pyclass(module = "_ast", name = "Del", base = NodeExprContext)] @@ -608,8 +659,7 @@ impl_node!( #[repr(transparent)] pub(crate) struct NodeOperator(NodeAst); -#[pyclass(flags(HAS_DICT, BASETYPE))] -impl NodeOperator {} +impl_base_node!(NodeOperator); impl_node!( #[pyclass(module = "_ast", name = "Or", base = NodeBoolOp)] @@ -680,8 +730,7 @@ impl_node!( #[repr(transparent)] pub(crate) struct NodeUnaryOp(NodeAst); -#[pyclass(flags(HAS_DICT, BASETYPE))] -impl NodeUnaryOp {} +impl_base_node!(NodeUnaryOp); impl_node!( #[pyclass(module = "_ast", name = "FloorDiv", base = NodeOperator)] @@ -707,8 +756,7 @@ impl_node!( #[repr(transparent)] pub(crate) struct NodeCmpOp(NodeAst); -#[pyclass(flags(HAS_DICT, BASETYPE))] -impl NodeCmpOp {} +impl_base_node!(NodeCmpOp); impl_node!( #[pyclass(module = "_ast", name = "USub", base = NodeUnaryOp)] @@ -769,8 +817,7 @@ impl_node!( #[repr(transparent)] pub(crate) struct NodeExceptHandler(NodeAst); -#[pyclass(flags(HAS_DICT, BASETYPE))] -impl NodeExceptHandler {} +impl_base_node!(NodeExceptHandler); impl_node!( #[pyclass(module = "_ast", name = "comprehension", base = NodeAst)] @@ -822,8 +869,7 @@ impl_node!( #[repr(transparent)] pub(crate) struct NodePattern(NodeAst); -#[pyclass(flags(HAS_DICT, BASETYPE))] -impl NodePattern {} +impl_base_node!(NodePattern); impl_node!( #[pyclass(module = "_ast", name = "match_case", base = NodeAst)] @@ -884,8 +930,7 @@ impl_node!( #[repr(transparent)] pub(crate) struct NodeTypeIgnore(NodeAst); -#[pyclass(flags(HAS_DICT, BASETYPE))] -impl NodeTypeIgnore {} +impl_base_node!(NodeTypeIgnore); impl_node!( #[pyclass(module = "_ast", name = "MatchOr", base = NodePattern)] @@ -898,8 +943,7 @@ impl_node!( #[repr(transparent)] pub(crate) struct NodeTypeParam(NodeAst); -#[pyclass(flags(HAS_DICT, BASETYPE))] -impl NodeTypeParam {} +impl_base_node!(NodeTypeParam); impl_node!( #[pyclass(module = "_ast", name = "TypeIgnore", base = NodeTypeIgnore)] @@ -1453,6 +1497,7 @@ const FIELD_TYPES: &[(&str, &[(&str, FieldType)])] = &[ pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) { extend_module!(vm, module, { + "AST" => NodeAst::make_class(&vm.ctx), "mod" => NodeMod::make_class(&vm.ctx), "Module" => NodeModModule::make_class(&vm.ctx), "Interactive" => NodeModInteractive::make_class(&vm.ctx), @@ -1582,6 +1627,9 @@ pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) { // Populate _field_types with real Python type objects populate_field_types(vm, module); + populate_singletons(vm, module); + force_ast_module_name(vm, module); + populate_match_args_and_attributes(vm, module); } fn populate_field_types(vm: &VirtualMachine, module: &Py) { @@ -1607,6 +1655,10 @@ fn populate_field_types(vm: &VirtualMachine, module: &Py) { .unwrap_or_else(|_| panic!("AST node type '{name}' not found in module")) }; + let field_types_attr = vm.ctx.intern_str("_field_types"); + let annotations_attr = vm.ctx.intern_str("__annotations__"); + let empty_dict: PyObjectRef = vm.ctx.new_dict().into(); + for &(class_name, fields) in FIELD_TYPES { if fields.is_empty() { continue; @@ -1648,10 +1700,8 @@ fn populate_field_types(vm: &VirtualMachine, module: &Py) { let dict_obj: PyObjectRef = dict.into(); if let Some(type_obj) = class.downcast_ref::() { - type_obj.set_attr(vm.ctx.intern_str("_field_types"), dict_obj); - // NOTE: CPython also sets __annotations__ = _field_types, but - // RustPython AST types are not heap types so __annotations__ - // is not accessible via the type descriptor. + type_obj.set_attr(field_types_attr, dict_obj.clone()); + type_obj.set_attr(annotations_attr, dict_obj); // Set None as class-level default for optional fields. // When ast_type_init skips optional fields, the instance @@ -1667,4 +1717,109 @@ fn populate_field_types(vm: &VirtualMachine, module: &Py) { } } } + + // CPython sets __annotations__ for all built-in AST node classes, even + // when _field_types is an empty dict (e.g., operators, Load/Store/Del). + for (_name, value) in &module.dict() { + let Some(type_obj) = value.downcast_ref::() else { + continue; + }; + if let Some(field_types) = type_obj.get_attr(field_types_attr) { + type_obj.set_attr(annotations_attr, field_types); + } + } + + // Base AST classes (e.g., expr, stmt) should still expose __annotations__. + const BASE_AST_TYPES: &[&str] = &[ + "mod", + "stmt", + "expr", + "expr_context", + "boolop", + "operator", + "unaryop", + "cmpop", + "excepthandler", + "pattern", + "type_ignore", + "type_param", + ]; + for &class_name in BASE_AST_TYPES { + let class = module + .get_attr(class_name, vm) + .unwrap_or_else(|_| panic!("AST class '{class_name}' not found in module")); + let Some(type_obj) = class.downcast_ref::() else { + continue; + }; + if type_obj.get_attr(field_types_attr).is_none() { + type_obj.set_attr(field_types_attr, empty_dict.clone()); + } + if type_obj.get_attr(annotations_attr).is_none() { + type_obj.set_attr(annotations_attr, empty_dict.clone()); + } + } +} + +fn populate_singletons(vm: &VirtualMachine, module: &Py) { + let instance_attr = vm.ctx.intern_str("_instance"); + const SINGLETON_TYPES: &[&str] = &[ + // expr_context + "Load", "Store", "Del", // boolop + "And", "Or", // operator + "Add", "Sub", "Mult", "MatMult", "Div", "Mod", "Pow", "LShift", "RShift", "BitOr", + "BitXor", "BitAnd", "FloorDiv", // unaryop + "Invert", "Not", "UAdd", "USub", // cmpop + "Eq", "NotEq", "Lt", "LtE", "Gt", "GtE", "Is", "IsNot", "In", "NotIn", + ]; + + for &class_name in SINGLETON_TYPES { + let class = module + .get_attr(class_name, vm) + .unwrap_or_else(|_| panic!("AST class '{class_name}' not found in module")); + let Some(type_obj) = class.downcast_ref::() else { + continue; + }; + let instance = vm + .ctx + .new_base_object(type_obj.to_owned(), Some(vm.ctx.new_dict())); + type_obj.set_attr(instance_attr, instance); + } +} + +fn force_ast_module_name(vm: &VirtualMachine, module: &Py) { + let ast_name = vm.ctx.new_str("ast"); + for (_name, value) in &module.dict() { + let Some(type_obj) = value.downcast_ref::() else { + continue; + }; + type_obj.set_attr(identifier!(vm, __module__), ast_name.clone().into()); + } +} + +fn populate_match_args_and_attributes(vm: &VirtualMachine, module: &Py) { + let fields_attr = vm.ctx.intern_str("_fields"); + let match_args_attr = vm.ctx.intern_str("__match_args__"); + let attributes_attr = vm.ctx.intern_str("_attributes"); + let empty_tuple: PyObjectRef = vm.ctx.empty_tuple.clone().into(); + + for (_name, value) in &module.dict() { + let Some(type_obj) = value.downcast_ref::() else { + continue; + }; + + type_obj + .slots + .repr + .store(Some(super::python::_ast::ast_repr)); + + if type_obj.get_attr(match_args_attr).is_none() { + if let Some(fields) = type_obj.get_attr(fields_attr) { + type_obj.set_attr(match_args_attr, fields); + } + } + + if type_obj.get_attr(attributes_attr).is_none() { + type_obj.set_attr(attributes_attr, empty_tuple.clone()); + } + } } diff --git a/crates/vm/src/stdlib/ast/python.rs b/crates/vm/src/stdlib/ast/python.rs index a2993ef1c10..772240451e5 100644 --- a/crates/vm/src/stdlib/ast/python.rs +++ b/crates/vm/src/stdlib/ast/python.rs @@ -1,14 +1,21 @@ -use super::{PY_CF_OPTIMIZED_AST, PY_CF_TYPE_COMMENTS, PY_COMPILE_FLAG_AST_ONLY}; +use super::{ + PY_CF_ALLOW_INCOMPLETE_INPUT, PY_CF_ALLOW_TOP_LEVEL_AWAIT, PY_CF_DONT_IMPLY_DEDENT, + PY_CF_IGNORE_COOKIE, PY_CF_OPTIMIZED_AST, PY_CF_SOURCE_IS_UTF8, PY_CF_TYPE_COMMENTS, + PY_COMPILE_FLAG_AST_ONLY, +}; #[pymodule] pub(crate) mod _ast { use crate::{ AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyStrRef, PyTupleRef, PyType, PyTypeRef}, - class::PyClassImpl, - function::FuncArgs, + builtins::{PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef}, + class::{PyClassImpl, StaticType}, + function::{FuncArgs, KwArgs, PyMethodDef, PyMethodFlags}, + stdlib::ast::repr, types::{Constructor, Initializer}, + warn, }; + use indexmap::IndexMap; #[pyattr] #[pyclass(module = "_ast", name = "AST")] #[derive(Debug, PyPayload)] @@ -16,16 +23,225 @@ pub(crate) mod _ast { #[pyclass(with(Constructor, Initializer), flags(BASETYPE, HAS_DICT))] impl NodeAst { + #[extend_class] + fn extend_class(ctx: &Context, class: &'static Py) { + let empty_tuple = ctx.empty_tuple.clone(); + class.set_str_attr("_fields", empty_tuple.clone(), ctx); + class.set_str_attr("_attributes", empty_tuple.clone(), ctx); + class.set_str_attr("__match_args__", empty_tuple.clone(), ctx); + + const AST_REDUCE: PyMethodDef = PyMethodDef::new_const( + "__reduce__", + |zelf: PyObjectRef, vm: &VirtualMachine| -> PyResult { + ast_reduce(zelf, vm) + }, + PyMethodFlags::METHOD, + None, + ); + const AST_REPLACE: PyMethodDef = PyMethodDef::new_const( + "__replace__", + |zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine| -> PyResult { + ast_replace(zelf, args, vm) + }, + PyMethodFlags::METHOD, + None, + ); + + class.set_str_attr("__reduce__", AST_REDUCE.to_proper_method(class, ctx), ctx); + class.set_str_attr("__replace__", AST_REPLACE.to_proper_method(class, ctx), ctx); + class.slots.repr.store(Some(ast_repr)); + } + #[pyattr] fn _fields(ctx: &Context) -> PyTupleRef { ctx.empty_tuple.clone() } + + #[pyattr] + fn _attributes(ctx: &Context) -> PyTupleRef { + ctx.empty_tuple.clone() + } + + #[pyattr] + fn __match_args__(ctx: &Context) -> PyTupleRef { + ctx.empty_tuple.clone() + } + + #[pymethod] + fn __reduce__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + ast_reduce(zelf, vm) + } + + #[pymethod] + fn __replace__(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + ast_replace(zelf, args, vm) + } + } + + pub(crate) fn ast_reduce(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let dict = zelf.as_object().dict(); + let cls = zelf.class(); + let type_obj: PyObjectRef = cls.to_owned().into(); + + let Some(dict) = dict else { + return Ok(vm.ctx.new_tuple(vec![type_obj])); + }; + + let fields = cls.get_attr(vm.ctx.intern_str("_fields")); + if let Some(fields) = fields { + let fields: Vec = fields.try_to_value(vm)?; + let mut positional: Vec = Vec::new(); + for field in fields { + if let Some(value) = dict.get_item_opt::(field.as_str(), vm)? { + positional.push(vm.ctx.none()); + drop(value); + } else { + break; + } + } + let args: PyObjectRef = vm.ctx.new_tuple(positional).into(); + let dict_obj: PyObjectRef = dict.into(); + return Ok(vm.ctx.new_tuple(vec![type_obj, args, dict_obj])); + } + + Ok(vm + .ctx + .new_tuple(vec![type_obj, vm.ctx.new_tuple(vec![]).into(), dict.into()])) + } + + pub(crate) fn ast_replace(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + if !args.args.is_empty() { + return Err(vm.new_type_error("__replace__() takes no positional arguments".to_owned())); + } + + let cls = zelf.class(); + let fields = cls.get_attr(vm.ctx.intern_str("_fields")); + let attributes = cls.get_attr(vm.ctx.intern_str("_attributes")); + let dict = zelf.as_object().dict(); + + let mut expecting: std::collections::HashSet = std::collections::HashSet::new(); + if let Some(fields) = fields.clone() { + let fields: Vec = fields.try_to_value(vm)?; + for field in fields { + expecting.insert(field.as_str().to_owned()); + } + } + if let Some(attributes) = attributes.clone() { + let attributes: Vec = attributes.try_to_value(vm)?; + for attr in attributes { + expecting.insert(attr.as_str().to_owned()); + } + } + + for (key, _value) in &args.kwargs { + if !expecting.remove(key) { + return Err(vm.new_type_error(format!( + "{}.__replace__ got an unexpected keyword argument '{}'.", + cls.name(), + key + ))); + } + } + + if let Some(dict) = dict.as_ref() { + for (key, _value) in dict.items_vec() { + if let Ok(key) = key.downcast::() { + expecting.remove(key.as_str()); + } + } + if let Some(attributes) = attributes.clone() { + let attributes: Vec = attributes.try_to_value(vm)?; + for attr in attributes { + expecting.remove(attr.as_str()); + } + } + } + + // Discard optional fields (T | None). + if let Some(field_types) = cls.get_attr(vm.ctx.intern_str("_field_types")) + && let Ok(field_types) = field_types.downcast::() + { + for (key, value) in field_types.items_vec() { + let Ok(key) = key.downcast::() else { + continue; + }; + if value.fast_isinstance(vm.ctx.types.union_type) { + expecting.remove(key.as_str()); + } + } + } + + if !expecting.is_empty() { + let mut names: Vec = expecting + .into_iter() + .map(|name| format!("{name:?}")) + .collect(); + names.sort(); + let missing = names.join(", "); + let count = names.len(); + return Err(vm.new_type_error(format!( + "{}.__replace__ missing {} keyword argument{}: {}.", + cls.name(), + count, + if count == 1 { "" } else { "s" }, + missing + ))); + } + + let payload = vm.ctx.new_dict(); + if let Some(dict) = dict { + if let Some(fields) = fields.clone() { + let fields: Vec = fields.try_to_value(vm)?; + for field in fields { + if let Some(value) = dict.get_item_opt::(field.as_str(), vm)? { + payload.set_item(field.as_object(), value, vm)?; + } + } + } + if let Some(attributes) = attributes.clone() { + let attributes: Vec = attributes.try_to_value(vm)?; + for attr in attributes { + if let Some(value) = dict.get_item_opt::(attr.as_str(), vm)? { + payload.set_item(attr.as_object(), value, vm)?; + } + } + } + } + for (key, value) in args.kwargs { + payload.set_item(vm.ctx.intern_str(key), value, vm)?; + } + + let type_obj: PyObjectRef = cls.to_owned().into(); + let kwargs = payload + .items_vec() + .into_iter() + .map(|(key, value)| { + let key = key + .downcast::() + .map_err(|_| vm.new_type_error("keywords must be strings".to_owned()))?; + Ok((key.as_str().to_owned(), value)) + }) + .collect::>>()?; + let result = type_obj.call(FuncArgs::new(vec![], KwArgs::new(kwargs)), vm)?; + Ok(result) + } + + pub(crate) fn ast_repr(zelf: &crate::PyObject, vm: &VirtualMachine) -> PyResult> { + let repr = repr::repr_ast_node(vm, &zelf.to_owned(), 3)?; + Ok(vm.ctx.new_str(repr)) } impl Constructor for NodeAst { type Args = FuncArgs; fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + if args.args.is_empty() + && args.kwargs.is_empty() + && let Some(instance) = cls.get_attr(vm.ctx.intern_str("_instance")) + { + return Ok(instance); + } + // AST nodes accept extra arguments (unlike object.__new__) // This matches CPython's behavior where AST has its own tp_new let dict = if cls @@ -55,7 +271,21 @@ pub(crate) mod _ast { type Args = FuncArgs; fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - let fields = zelf.get_attr("_fields", vm)?; + let fields = zelf + .class() + .get_attr(vm.ctx.intern_str("_fields")) + .ok_or_else(|| { + let module = zelf + .class() + .get_attr(vm.ctx.intern_str("__module__")) + .and_then(|obj| obj.try_to_value::(vm).ok()) + .unwrap_or_else(|| "ast".to_owned()); + vm.new_attribute_error(format!( + "type object '{}.{}' has no attribute '_fields'", + module, + zelf.class().name() + )) + })?; let fields: Vec = fields.try_to_value(vm)?; let n_args = args.args.len(); if n_args > fields.len() { @@ -69,6 +299,7 @@ pub(crate) mod _ast { // Track which fields were set let mut set_fields = std::collections::HashSet::new(); + let mut attributes: Option> = None; for (name, arg) in fields.iter().zip(args.args) { zelf.set_attr(name, arg, vm)?; @@ -84,6 +315,36 @@ pub(crate) mod _ast { key ))); } + + if fields.iter().all(|field| field.as_str() != key) { + let attrs = if let Some(attrs) = &attributes { + attrs + } else { + let attrs = zelf + .class() + .get_attr(vm.ctx.intern_str("_attributes")) + .and_then(|attr| attr.try_to_value::>(vm).ok()) + .unwrap_or_default(); + attributes = Some(attrs); + attributes.as_ref().unwrap() + }; + if attrs.iter().all(|attr| attr.as_str() != key) { + let message = vm.ctx.new_str(format!( + "{}.__init__ got an unexpected keyword argument '{}'. \ +Support for arbitrary keyword arguments is deprecated and will be removed in Python 3.15.", + zelf.class().name(), + key + )); + warn::warn( + message, + Some(vm.ctx.exceptions.deprecation_warning.to_owned()), + 1, + None, + vm, + )?; + } + } + set_fields.insert(key.clone()); zelf.set_attr(vm.ctx.intern_str(key), value, vm)?; } @@ -112,11 +373,27 @@ pub(crate) mod _ast { // expr_context — default to Load() let load_type = super::super::pyast::NodeExprContextLoad::make_class(&vm.ctx); - let load_instance = - vm.ctx.new_base_object(load_type, Some(vm.ctx.new_dict())); + let load_instance = load_type + .get_attr(vm.ctx.intern_str("_instance")) + .unwrap_or_else(|| { + vm.ctx.new_base_object(load_type, Some(vm.ctx.new_dict())) + }); zelf.set_attr(vm.ctx.intern_str(field.as_str()), load_instance, vm)?; + } else { + // Required field missing: emit DeprecationWarning (CPython behavior). + let message = vm.ctx.new_str(format!( + "{}.__init__ missing 1 required positional argument: '{}'", + zelf.class().name(), + field.as_str() + )); + warn::warn( + message, + Some(vm.ctx.exceptions.deprecation_warning.to_owned()), + 1, + None, + vm, + )?; } - // else: required field, no default set } } } @@ -129,21 +406,110 @@ pub(crate) mod _ast { } } + #[pyattr(name = "PyCF_SOURCE_IS_UTF8")] + use super::PY_CF_SOURCE_IS_UTF8; + + #[pyattr(name = "PyCF_DONT_IMPLY_DEDENT")] + use super::PY_CF_DONT_IMPLY_DEDENT; + #[pyattr(name = "PyCF_ONLY_AST")] use super::PY_COMPILE_FLAG_AST_ONLY; - #[pyattr(name = "PyCF_OPTIMIZED_AST")] - use super::PY_CF_OPTIMIZED_AST; + #[pyattr(name = "PyCF_IGNORE_COOKIE")] + use super::PY_CF_IGNORE_COOKIE; #[pyattr(name = "PyCF_TYPE_COMMENTS")] use super::PY_CF_TYPE_COMMENTS; + #[pyattr(name = "PyCF_ALLOW_TOP_LEVEL_AWAIT")] + use super::PY_CF_ALLOW_TOP_LEVEL_AWAIT; + + #[pyattr(name = "PyCF_ALLOW_INCOMPLETE_INPUT")] + use super::PY_CF_ALLOW_INCOMPLETE_INPUT; + + #[pyattr(name = "PyCF_OPTIMIZED_AST")] + use super::PY_CF_OPTIMIZED_AST; + pub(crate) fn module_exec( vm: &VirtualMachine, module: &Py, ) -> PyResult<()> { __module_exec(vm, module); super::super::pyast::extend_module_nodes(vm, module); + + let ast_type = module + .get_attr("AST", vm)? + .downcast::() + .map_err(|_| vm.new_type_error("AST is not a type".to_owned()))?; + let ctx = &vm.ctx; + let empty_tuple = ctx.empty_tuple.clone(); + ast_type.set_str_attr("_fields", empty_tuple.clone(), ctx); + ast_type.set_str_attr("_attributes", empty_tuple.clone(), ctx); + ast_type.set_str_attr("__match_args__", empty_tuple.clone(), ctx); + + const AST_REDUCE: PyMethodDef = PyMethodDef::new_const( + "__reduce__", + |zelf: PyObjectRef, vm: &VirtualMachine| -> PyResult { + ast_reduce(zelf, vm) + }, + PyMethodFlags::METHOD, + None, + ); + const AST_REPLACE: PyMethodDef = PyMethodDef::new_const( + "__replace__", + |zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine| -> PyResult { + ast_replace(zelf, args, vm) + }, + PyMethodFlags::METHOD, + None, + ); + let base_type = NodeAst::static_type(); + ast_type.set_str_attr( + "__reduce__", + AST_REDUCE.to_proper_method(base_type, ctx), + ctx, + ); + ast_type.set_str_attr( + "__replace__", + AST_REPLACE.to_proper_method(base_type, ctx), + ctx, + ); + ast_type.slots.repr.store(Some(ast_repr)); + + const EXPR_DOC: &str = "expr = BoolOp(boolop op, expr* values)\n\ + | NamedExpr(expr target, expr value)\n\ + | BinOp(expr left, operator op, expr right)\n\ + | UnaryOp(unaryop op, expr operand)\n\ + | Lambda(arguments args, expr body)\n\ + | IfExp(expr test, expr body, expr orelse)\n\ + | Dict(expr?* keys, expr* values)\n\ + | Set(expr* elts)\n\ + | ListComp(expr elt, comprehension* generators)\n\ + | SetComp(expr elt, comprehension* generators)\n\ + | DictComp(expr key, expr value, comprehension* generators)\n\ + | GeneratorExp(expr elt, comprehension* generators)\n\ + | Await(expr value)\n\ + | Yield(expr? value)\n\ + | YieldFrom(expr value)\n\ + | Compare(expr left, cmpop* ops, expr* comparators)\n\ + | Call(expr func, expr* args, keyword* keywords)\n\ + | FormattedValue(expr value, int conversion, expr? format_spec)\n\ + | Interpolation(expr value, constant str, int conversion, expr? format_spec)\n\ + | JoinedStr(expr* values)\n\ + | TemplateStr(expr* values)\n\ + | Constant(constant value, string? kind)\n\ + | Attribute(expr value, identifier attr, expr_context ctx)\n\ + | Subscript(expr value, expr slice, expr_context ctx)\n\ + | Starred(expr value, expr_context ctx)\n\ + | Name(identifier id, expr_context ctx)\n\ + | List(expr* elts, expr_context ctx)\n\ + | Tuple(expr* elts, expr_context ctx)\n\ + | Slice(expr? lower, expr? upper, expr? step)"; + let expr_type = super::super::pyast::NodeExpr::static_type(); + expr_type.set_attr( + identifier!(vm.ctx, __doc__), + vm.ctx.new_str(EXPR_DOC).into(), + ); Ok(()) } } diff --git a/crates/vm/src/stdlib/ast/repr.rs b/crates/vm/src/stdlib/ast/repr.rs new file mode 100644 index 00000000000..0810814cd06 --- /dev/null +++ b/crates/vm/src/stdlib/ast/repr.rs @@ -0,0 +1,147 @@ +use crate::{ + AsObject, PyObjectRef, PyResult, VirtualMachine, + builtins::{PyList, PyTuple}, + class::PyClassImpl, + stdlib::ast::NodeAst, +}; + +fn repr_ast_list(vm: &VirtualMachine, items: Vec, depth: usize) -> PyResult { + if items.is_empty() { + let empty_list: PyObjectRef = vm.ctx.new_list(vec![]).into(); + return Ok(empty_list.repr(vm)?.to_string()); + } + + let mut parts: Vec = Vec::new(); + let first = &items[0]; + let last = items.last().unwrap(); + + for (idx, item) in [first, last].iter().enumerate() { + if idx == 1 && items.len() == 1 { + break; + } + let repr = if item.fast_isinstance(&NodeAst::make_class(&vm.ctx)) { + repr_ast_node(vm, item, depth.saturating_sub(1))? + } else { + item.repr(vm)?.to_string() + }; + parts.push(repr); + } + + let mut rendered = String::from("["); + if !parts.is_empty() { + rendered.push_str(&parts[0]); + } + if items.len() > 2 { + if !parts[0].is_empty() { + rendered.push_str(", ..."); + } + if parts.len() > 1 { + rendered.push_str(", "); + rendered.push_str(&parts[1]); + } + } else if parts.len() > 1 { + rendered.push_str(", "); + rendered.push_str(&parts[1]); + } + rendered.push(']'); + Ok(rendered) +} + +fn repr_ast_tuple(vm: &VirtualMachine, items: Vec, depth: usize) -> PyResult { + if items.is_empty() { + let empty_tuple: PyObjectRef = vm.ctx.empty_tuple.clone().into(); + return Ok(empty_tuple.repr(vm)?.to_string()); + } + + let mut parts: Vec = Vec::new(); + let first = &items[0]; + let last = items.last().unwrap(); + + for (idx, item) in [first, last].iter().enumerate() { + if idx == 1 && items.len() == 1 { + break; + } + let repr = if item.fast_isinstance(&NodeAst::make_class(&vm.ctx)) { + repr_ast_node(vm, item, depth.saturating_sub(1))? + } else { + item.repr(vm)?.to_string() + }; + parts.push(repr); + } + + let mut rendered = String::from("("); + if !parts.is_empty() { + rendered.push_str(&parts[0]); + } + if items.len() > 2 { + if !parts[0].is_empty() { + rendered.push_str(", ..."); + } + if parts.len() > 1 { + rendered.push_str(", "); + rendered.push_str(&parts[1]); + } + } else if parts.len() > 1 { + rendered.push_str(", "); + rendered.push_str(&parts[1]); + } + if items.len() == 1 { + rendered.push(','); + } + rendered.push(')'); + Ok(rendered) +} + +pub(crate) fn repr_ast_node( + vm: &VirtualMachine, + obj: &PyObjectRef, + depth: usize, +) -> PyResult { + let cls = obj.class(); + if depth == 0 { + return Ok(format!("{}(...)", cls.name())); + } + + let fields = cls.get_attr(vm.ctx.intern_str("_fields")); + let fields = match fields { + Some(fields) => fields.try_to_value::>(vm)?, + None => return Ok(format!("{}(...)", cls.name())), + }; + + if fields.is_empty() { + return Ok(format!("{}()", cls.name())); + } + + let mut rendered = String::new(); + rendered.push_str(&cls.name()); + rendered.push('('); + + for (idx, field) in fields.iter().enumerate() { + let value = obj.get_attr(field, vm)?; + let value_repr = if value.fast_isinstance(vm.ctx.types.list_type) { + let list = value + .downcast::() + .expect("list type should downcast"); + repr_ast_list(vm, list.borrow_vec().to_vec(), depth)? + } else if value.fast_isinstance(vm.ctx.types.tuple_type) { + let tuple = value + .downcast::() + .expect("tuple type should downcast"); + repr_ast_tuple(vm, tuple.as_slice().to_vec(), depth)? + } else if value.fast_isinstance(&NodeAst::make_class(&vm.ctx)) { + repr_ast_node(vm, &value, depth.saturating_sub(1))? + } else { + value.repr(vm)?.to_string() + }; + + if idx > 0 { + rendered.push_str(", "); + } + rendered.push_str(field.as_str()); + rendered.push('='); + rendered.push_str(&value_repr); + } + + rendered.push(')'); + Ok(rendered) +} diff --git a/crates/vm/src/stdlib/ast/statement.rs b/crates/vm/src/stdlib/ast/statement.rs index 1d8f1cbcf00..8b6ceb490a1 100644 --- a/crates/vm/src/stdlib/ast/statement.rs +++ b/crates/vm/src/stdlib/ast/statement.rs @@ -159,6 +159,9 @@ impl Node for ast::StmtFunctionDef { is_async, range: _range, } = self; + let source_code = source_file.to_source_code(); + let def_line = source_code.line_index(name.range.start()); + let range = TextRange::new(source_code.line_start(def_line), _range.end()); let cls = if !is_async { pyast::NodeStmtFunctionDef::static_type().to_owned() @@ -192,7 +195,7 @@ impl Node for ast::StmtFunctionDef { vm, ) .unwrap(); - node_add_location(&dict, _range, vm, source_file); + node_add_location(&dict, range, vm, source_file); node.into() } fn ast_from_object( @@ -202,6 +205,7 @@ impl Node for ast::StmtFunctionDef { ) -> PyResult { let _cls = _object.class(); let is_async = _cls.is(pyast::NodeStmtAsyncFunctionDef::static_type()); + let range = range_from_object(_vm, source_file, _object.clone(), "FunctionDef")?; Ok(Self { node_index: Default::default(), name: Node::ast_from_object( @@ -234,9 +238,10 @@ impl Node for ast::StmtFunctionDef { type_params: Node::ast_from_object( _vm, source_file, - get_node_field_opt(_vm, &_object, "type_params")?.unwrap_or_else(|| _vm.ctx.none()), + get_node_field_opt(_vm, &_object, "type_params")? + .unwrap_or_else(|| _vm.ctx.new_list(Vec::new()).into()), )?, - range: range_from_object(_vm, source_file, _object, "FunctionDef")?, + range, is_async, }) } @@ -255,6 +260,9 @@ impl Node for ast::StmtClassDef { range: _range, } = self; let (bases, keywords) = split_class_def_args(arguments); + let source_code = source_file.to_source_code(); + let class_line = source_code.line_index(name.range.start()); + let range = TextRange::new(source_code.line_start(class_line), _range.end()); let node = NodeAst .into_ref_with_type(_vm, pyast::NodeStmtClassDef::static_type().to_owned()) .unwrap(); @@ -293,7 +301,7 @@ impl Node for ast::StmtClassDef { _vm, ) .unwrap(); - node_add_location(&dict, _range, _vm, source_file); + node_add_location(&dict, range, _vm, source_file); node.into() } fn ast_from_object( @@ -332,7 +340,8 @@ impl Node for ast::StmtClassDef { type_params: Node::ast_from_object( _vm, source_file, - get_node_field_opt(_vm, &_object, "type_params")?.unwrap_or_else(|| _vm.ctx.none()), + get_node_field_opt(_vm, &_object, "type_params")? + .unwrap_or_else(|| _vm.ctx.new_list(Vec::new()).into()), )?, range: range_from_object(_vm, source_file, _object, "ClassDef")?, }) @@ -469,7 +478,9 @@ impl Node for ast::StmtTypeAlias { .unwrap(); dict.set_item( "type_params", - type_params.ast_to_object(_vm, source_file), + type_params + .map(|tp| tp.ast_to_object(_vm, source_file)) + .unwrap_or_else(|| _vm.ctx.new_list(Vec::new()).into()), _vm, ) .unwrap(); @@ -1099,7 +1110,13 @@ impl Node for ast::StmtImportFrom { level: get_node_field_opt(vm, &_object, "level")? .map(|obj| -> PyResult { let int: PyRef = obj.try_into_value(vm)?; - int.try_to_primitive(vm) + let value: i64 = int.try_to_primitive(vm)?; + if value < 0 { + return Err(vm.new_value_error("Negative ImportFrom level".to_owned())); + } + u32::try_from(value).map_err(|_| { + vm.new_overflow_error("ImportFrom level out of range".to_owned()) + }) }) .transpose()? .unwrap_or(0), @@ -1217,7 +1234,28 @@ impl Node for ast::StmtPass { .into_ref_with_type(_vm, pyast::NodeStmtPass::static_type().to_owned()) .unwrap(); let dict = node.as_object().dict().unwrap(); - node_add_location(&dict, _range, _vm, source_file); + let location = super::text_range_to_source_range(source_file, _range); + let start_row = location.start.row.get(); + let start_col = location.start.column.get(); + let mut end_row = location.end.row.get(); + let mut end_col = location.end.column.get(); + + // Align with CPython: when docstring optimization replaces a lone + // docstring with `pass`, the end position is on the same line even if + // it extends past the physical line length. + if end_row != start_row && _range.len() == TextSize::from(4) { + end_row = start_row; + end_col = start_col + 4; + } + + dict.set_item("lineno", _vm.ctx.new_int(start_row).into(), _vm) + .unwrap(); + dict.set_item("col_offset", _vm.ctx.new_int(start_col).into(), _vm) + .unwrap(); + dict.set_item("end_lineno", _vm.ctx.new_int(end_row).into(), _vm) + .unwrap(); + dict.set_item("end_col_offset", _vm.ctx.new_int(end_col).into(), _vm) + .unwrap(); node.into() } fn ast_from_object( diff --git a/crates/vm/src/stdlib/ast/string.rs b/crates/vm/src/stdlib/ast/string.rs index 2533fb8c6b9..bfeaad82f9c 100644 --- a/crates/vm/src/stdlib/ast/string.rs +++ b/crates/vm/src/stdlib/ast/string.rs @@ -1,5 +1,7 @@ use super::constant::{Constant, ConstantLiteral}; use super::*; +use crate::warn; +use ast::str_prefix::StringLiteralPrefix; fn ruff_fstring_element_into_iter( mut fstring_element: ast::InterpolatedStringElements, @@ -45,6 +47,193 @@ fn ruff_fstring_element_to_joined_str_part( } } +fn push_joined_str_literal( + output: &mut Vec, + pending: &mut Option<(String, StringLiteralPrefix, TextRange)>, +) { + if let Some((value, prefix, range)) = pending.take() + && !value.is_empty() + { + output.push(JoinedStrPart::Constant(Constant::new_str( + value, prefix, range, + ))); + } +} + +fn normalize_joined_str_parts(values: Vec) -> Vec { + let mut output = Vec::with_capacity(values.len()); + let mut pending: Option<(String, StringLiteralPrefix, TextRange)> = None; + + for part in values { + match part { + JoinedStrPart::Constant(constant) => { + let ConstantLiteral::Str { value, prefix } = constant.value else { + push_joined_str_literal(&mut output, &mut pending); + output.push(JoinedStrPart::Constant(constant)); + continue; + }; + let value: String = value.into(); + if let Some((pending_value, _, _)) = pending.as_mut() { + pending_value.push_str(&value); + } else { + pending = Some((value, prefix, constant.range)); + } + } + JoinedStrPart::FormattedValue(value) => { + push_joined_str_literal(&mut output, &mut pending); + output.push(JoinedStrPart::FormattedValue(value)); + } + } + } + + push_joined_str_literal(&mut output, &mut pending); + output +} + +fn push_template_str_literal( + output: &mut Vec, + pending: &mut Option<(String, StringLiteralPrefix, TextRange)>, +) { + if let Some((value, prefix, range)) = pending.take() + && !value.is_empty() + { + output.push(TemplateStrPart::Constant(Constant::new_str( + value, prefix, range, + ))); + } +} + +fn normalize_template_str_parts(values: Vec) -> Vec { + let mut output = Vec::with_capacity(values.len()); + let mut pending: Option<(String, StringLiteralPrefix, TextRange)> = None; + + for part in values { + match part { + TemplateStrPart::Constant(constant) => { + let ConstantLiteral::Str { value, prefix } = constant.value else { + push_template_str_literal(&mut output, &mut pending); + output.push(TemplateStrPart::Constant(constant)); + continue; + }; + let value: String = value.into(); + if let Some((pending_value, _, _)) = pending.as_mut() { + pending_value.push_str(&value); + } else { + pending = Some((value, prefix, constant.range)); + } + } + TemplateStrPart::Interpolation(value) => { + push_template_str_literal(&mut output, &mut pending); + output.push(TemplateStrPart::Interpolation(value)); + } + } + } + + push_template_str_literal(&mut output, &mut pending); + output +} + +fn warn_invalid_escape_sequences_in_format_spec( + vm: &VirtualMachine, + source_file: &SourceFile, + range: TextRange, +) { + let source = source_file.source_text(); + let start = range.start().to_usize(); + let end = range.end().to_usize(); + if start >= end || end > source.len() { + return; + } + let mut raw = &source[start..end]; + if raw.starts_with(':') { + raw = &raw[1..]; + } + + let mut chars = raw.chars().peekable(); + while let Some(ch) = chars.next() { + if ch != '\\' { + continue; + } + let Some(next) = chars.next() else { + break; + }; + let valid = match next { + '\\' | '\'' | '"' | 'a' | 'b' | 'f' | 'n' | 'r' | 't' | 'v' => true, + '\n' => true, + '\r' => { + if let Some('\n') = chars.peek().copied() { + chars.next(); + } + true + } + '0'..='7' => { + for _ in 0..2 { + if let Some('0'..='7') = chars.peek().copied() { + chars.next(); + } else { + break; + } + } + true + } + 'x' => { + for _ in 0..2 { + if chars.peek().is_some_and(|c| c.is_ascii_hexdigit()) { + chars.next(); + } else { + break; + } + } + true + } + 'u' => { + for _ in 0..4 { + if chars.peek().is_some_and(|c| c.is_ascii_hexdigit()) { + chars.next(); + } else { + break; + } + } + true + } + 'U' => { + for _ in 0..8 { + if chars.peek().is_some_and(|c| c.is_ascii_hexdigit()) { + chars.next(); + } else { + break; + } + } + true + } + 'N' => { + if let Some('{') = chars.peek().copied() { + chars.next(); + for c in chars.by_ref() { + if c == '}' { + break; + } + } + } + true + } + _ => false, + }; + if !valid { + let message = vm.ctx.new_str(format!( + "\"\\{next}\" is an invalid escape sequence. Such sequences will not work in the future. Did you mean \"\\\\{next}\"? A raw string is also an option." + )); + let _ = warn::warn( + message, + Some(vm.ctx.exceptions.syntax_warning.to_owned()), + 1, + None, + vm, + ); + } + } +} + fn ruff_format_spec_to_joined_str( format_spec: Option>, ) -> Option> { @@ -56,10 +245,18 @@ fn ruff_format_spec_to_joined_str( elements, node_index: _, } = *format_spec; + let range = if range.start() > ruff_text_size::TextSize::from(0) { + TextRange::new( + range.start() - ruff_text_size::TextSize::from(1), + range.end(), + ) + } else { + range + }; let values: Vec<_> = ruff_fstring_element_into_iter(elements) .map(ruff_fstring_element_to_joined_str_part) .collect(); - let values = values.into_boxed_slice(); + let values = normalize_joined_str_parts(values).into_boxed_slice(); Some(Box::new(JoinedStr { range, values })) } } @@ -353,6 +550,14 @@ pub(super) fn fstring_to_object( } } } + let values = normalize_joined_str_parts(values); + for part in &values { + if let JoinedStrPart::FormattedValue(value) = part + && let Some(format_spec) = &value.format_spec + { + warn_invalid_escape_sequences_in_format_spec(vm, source_file, format_spec.range); + } + } let c = JoinedStr { range, values: values.into_boxed_slice(), @@ -384,40 +589,106 @@ fn ruff_tstring_element_to_template_str_part( format_spec, node_index: _, }) => { - // Get the expression source text for the "str" field - let expr_str = debug_text - .map(|dt| dt.leading.to_string() + &dt.trailing) - .unwrap_or_else(|| source_file.slice(expression.range()).to_string()); + let expr_range = + extend_expr_range_with_wrapping_parens(source_file, range, expression.range()) + .unwrap_or_else(|| expression.range()); + let expr_str = if let Some(debug_text) = debug_text { + let expr_source = source_file.slice(expr_range); + let mut expr_with_debug = String::with_capacity( + debug_text.leading.len() + expr_source.len() + debug_text.trailing.len(), + ); + expr_with_debug.push_str(&debug_text.leading); + expr_with_debug.push_str(expr_source); + expr_with_debug.push_str(&debug_text.trailing); + strip_interpolation_expr(&expr_with_debug) + } else { + tstring_interpolation_expr_str(source_file, range, expr_range) + }; TemplateStrPart::Interpolation(TStringInterpolation { value: expression, str: expr_str, conversion, - format_spec: ruff_format_spec_to_template_str(format_spec, source_file), + format_spec: ruff_format_spec_to_joined_str(format_spec), range, }) } } } -fn ruff_format_spec_to_template_str( - format_spec: Option>, +fn tstring_interpolation_expr_str( source_file: &SourceFile, -) -> Option> { - match format_spec { - None => None, - Some(format_spec) => { - let ast::InterpolatedStringFormatSpec { - range, - elements, - node_index: _, - } = *format_spec; - let values: Vec<_> = ruff_fstring_element_into_iter(elements) - .map(|e| ruff_tstring_element_to_template_str_part(e, source_file)) - .collect(); - let values = values.into_boxed_slice(); - Some(Box::new(TemplateStr { range, values })) + interpolation_range: TextRange, + expr_range: TextRange, +) -> String { + let expr_range = + extend_expr_range_with_wrapping_parens(source_file, interpolation_range, expr_range) + .unwrap_or(expr_range); + let start = interpolation_range.start() + TextSize::from(1); + let start = if start > expr_range.end() { + expr_range.start() + } else { + start + }; + let expr_source = source_file.slice(TextRange::new(start, expr_range.end())); + strip_interpolation_expr(expr_source) +} + +fn extend_expr_range_with_wrapping_parens( + source_file: &SourceFile, + interpolation_range: TextRange, + expr_range: TextRange, +) -> Option { + let left_slice = source_file.slice(TextRange::new( + interpolation_range.start(), + expr_range.start(), + )); + let mut left_char: Option<(usize, char)> = None; + for (idx, ch) in left_slice + .char_indices() + .collect::>() + .into_iter() + .rev() + { + if !ch.is_whitespace() { + left_char = Some((idx, ch)); + break; + } + } + let (left_idx, left_ch) = left_char?; + if left_ch != '(' { + return None; + } + + let right_slice = + source_file.slice(TextRange::new(expr_range.end(), interpolation_range.end())); + let mut right_char: Option<(usize, char)> = None; + for (idx, ch) in right_slice.char_indices() { + if !ch.is_whitespace() { + right_char = Some((idx, ch)); + break; } } + let (right_idx, right_ch) = right_char?; + if right_ch != ')' { + return None; + } + + let left_pos = interpolation_range.start() + TextSize::from(left_idx as u32); + let right_pos = expr_range.end() + TextSize::from(right_idx as u32); + Some(TextRange::new(left_pos, right_pos + TextSize::from(1))) +} + +fn strip_interpolation_expr(expr_source: &str) -> String { + let mut end = expr_source.len(); + for (idx, ch) in expr_source.char_indices().rev() { + if ch.is_whitespace() || ch == '=' { + end = idx; + continue; + } + end = idx + ch.len_utf8(); + break; + } + expr_source[..end].to_owned() } #[derive(Debug)] @@ -426,6 +697,98 @@ pub(super) struct TemplateStr { pub(super) values: Box<[TemplateStrPart]>, } +pub(super) fn template_str_to_expr( + vm: &VirtualMachine, + template: TemplateStr, +) -> PyResult { + let TemplateStr { range, values } = template; + let elements = template_parts_to_elements(vm, values)?; + let tstring = ast::TString { + range, + node_index: Default::default(), + elements, + flags: ast::TStringFlags::empty(), + }; + Ok(ast::Expr::TString(ast::ExprTString { + node_index: Default::default(), + range, + value: ast::TStringValue::single(tstring), + })) +} + +pub(super) fn interpolation_to_expr( + vm: &VirtualMachine, + interpolation: TStringInterpolation, +) -> PyResult { + let part = TemplateStrPart::Interpolation(interpolation); + let elements = template_parts_to_elements(vm, vec![part].into_boxed_slice())?; + let range = TextRange::default(); + let tstring = ast::TString { + range, + node_index: Default::default(), + elements, + flags: ast::TStringFlags::empty(), + }; + Ok(ast::Expr::TString(ast::ExprTString { + node_index: Default::default(), + range, + value: ast::TStringValue::single(tstring), + })) +} + +fn template_parts_to_elements( + vm: &VirtualMachine, + values: Box<[TemplateStrPart]>, +) -> PyResult { + let mut elements = Vec::with_capacity(values.len()); + for value in values.into_vec() { + elements.push(template_part_to_element(vm, value)?); + } + Ok(ast::InterpolatedStringElements::from(elements)) +} + +fn template_part_to_element( + vm: &VirtualMachine, + part: TemplateStrPart, +) -> PyResult { + match part { + TemplateStrPart::Constant(constant) => { + let ConstantLiteral::Str { value, .. } = constant.value else { + return Err( + vm.new_type_error("TemplateStr constant values must be strings".to_owned()) + ); + }; + Ok(ast::InterpolatedStringElement::Literal( + ast::InterpolatedStringLiteralElement { + range: constant.range, + node_index: Default::default(), + value, + }, + )) + } + TemplateStrPart::Interpolation(interpolation) => { + let TStringInterpolation { + value, + conversion, + format_spec, + range, + .. + } = interpolation; + let format_spec = joined_str_to_ruff_format_spec(format_spec); + Ok(ast::InterpolatedStringElement::Interpolation( + ast::InterpolatedElement { + range, + node_index: Default::default(), + expression: value, + debug_text: None, + conversion, + format_spec, + }, + )) + } + } +} + // constructor impl Node for TemplateStr { fn ast_to_object(self, vm: &VirtualMachine, source_file: &SourceFile) -> PyObjectRef { @@ -501,7 +864,7 @@ pub(super) struct TStringInterpolation { value: Box, str: String, conversion: ast::ConversionFlag, - format_spec: Option>, + format_spec: Option>, range: TextRange, } @@ -587,6 +950,7 @@ pub(super) fn tstring_to_object( )); } } + let values = normalize_template_str_parts(values); let c = TemplateStr { range, values: values.into_boxed_slice(), diff --git a/crates/vm/src/stdlib/ast/validate.rs b/crates/vm/src/stdlib/ast/validate.rs new file mode 100644 index 00000000000..ea5c2be840c --- /dev/null +++ b/crates/vm/src/stdlib/ast/validate.rs @@ -0,0 +1,670 @@ +// spell-checker: ignore assignlist ifexp + +use super::module::Mod; +use crate::{PyResult, VirtualMachine}; +use ruff_python_ast as ast; + +fn expr_context_name(ctx: ast::ExprContext) -> &'static str { + match ctx { + ast::ExprContext::Load => "Load", + ast::ExprContext::Store => "Store", + ast::ExprContext::Del => "Del", + ast::ExprContext::Invalid => "Invalid", + } +} + +fn validate_name(vm: &VirtualMachine, name: &ast::name::Name) -> PyResult<()> { + match name.as_str() { + "None" | "True" | "False" => Err(vm.new_value_error(format!( + "identifier field can't represent '{}' constant", + name.as_str() + ))), + _ => Ok(()), + } +} + +fn validate_comprehension(vm: &VirtualMachine, gens: &[ast::Comprehension]) -> PyResult<()> { + if gens.is_empty() { + return Err(vm.new_value_error("comprehension with no generators".to_owned())); + } + for comp in gens { + validate_expr(vm, &comp.target, ast::ExprContext::Store)?; + validate_expr(vm, &comp.iter, ast::ExprContext::Load)?; + validate_exprs(vm, &comp.ifs, ast::ExprContext::Load, false)?; + } + Ok(()) +} + +fn validate_keywords(vm: &VirtualMachine, keywords: &[ast::Keyword]) -> PyResult<()> { + for keyword in keywords { + validate_expr(vm, &keyword.value, ast::ExprContext::Load)?; + } + Ok(()) +} + +fn validate_parameters(vm: &VirtualMachine, params: &ast::Parameters) -> PyResult<()> { + for param in params + .posonlyargs + .iter() + .chain(¶ms.args) + .chain(¶ms.kwonlyargs) + { + if let Some(annotation) = ¶m.parameter.annotation { + validate_expr(vm, annotation, ast::ExprContext::Load)?; + } + if let Some(default) = ¶m.default { + validate_expr(vm, default, ast::ExprContext::Load)?; + } + } + if let Some(vararg) = ¶ms.vararg + && let Some(annotation) = &vararg.annotation + { + validate_expr(vm, annotation, ast::ExprContext::Load)?; + } + if let Some(kwarg) = ¶ms.kwarg + && let Some(annotation) = &kwarg.annotation + { + validate_expr(vm, annotation, ast::ExprContext::Load)?; + } + Ok(()) +} + +fn validate_nonempty_seq( + vm: &VirtualMachine, + len: usize, + what: &'static str, + owner: &'static str, +) -> PyResult<()> { + if len == 0 { + return Err(vm.new_value_error(format!("empty {what} on {owner}"))); + } + Ok(()) +} + +fn validate_assignlist( + vm: &VirtualMachine, + targets: &[ast::Expr], + ctx: ast::ExprContext, +) -> PyResult<()> { + validate_nonempty_seq( + vm, + targets.len(), + "targets", + if ctx == ast::ExprContext::Del { + "Delete" + } else { + "Assign" + }, + )?; + validate_exprs(vm, targets, ctx, false) +} + +fn validate_body(vm: &VirtualMachine, body: &[ast::Stmt], owner: &'static str) -> PyResult<()> { + validate_nonempty_seq(vm, body.len(), "body", owner)?; + validate_stmts(vm, body) +} + +fn validate_interpolated_elements<'a>( + vm: &VirtualMachine, + elements: impl IntoIterator>, +) -> PyResult<()> { + for element in elements { + if let ast::InterpolatedStringElementRef::Interpolation(interpolation) = element { + validate_expr(vm, &interpolation.expression, ast::ExprContext::Load)?; + if let Some(format_spec) = &interpolation.format_spec { + for spec_element in &format_spec.elements { + if let ast::InterpolatedStringElement::Interpolation(spec_interp) = spec_element + { + validate_expr(vm, &spec_interp.expression, ast::ExprContext::Load)?; + } + } + } + } + } + Ok(()) +} + +fn validate_pattern_match_value(vm: &VirtualMachine, expr: &ast::Expr) -> PyResult<()> { + validate_expr(vm, expr, ast::ExprContext::Load)?; + match expr { + ast::Expr::NumberLiteral(_) | ast::Expr::StringLiteral(_) | ast::Expr::BytesLiteral(_) => { + Ok(()) + } + ast::Expr::Attribute(_) => Ok(()), + ast::Expr::UnaryOp(op) => match &*op.operand { + ast::Expr::NumberLiteral(_) => Ok(()), + _ => Err(vm.new_value_error( + "patterns may only match literals and attribute lookups".to_owned(), + )), + }, + ast::Expr::BinOp(bin) => match (&*bin.left, &*bin.right) { + (ast::Expr::NumberLiteral(_), ast::Expr::NumberLiteral(_)) => Ok(()), + _ => Err(vm.new_value_error( + "patterns may only match literals and attribute lookups".to_owned(), + )), + }, + ast::Expr::FString(_) | ast::Expr::TString(_) => Ok(()), + ast::Expr::BooleanLiteral(_) + | ast::Expr::NoneLiteral(_) + | ast::Expr::EllipsisLiteral(_) => { + Err(vm.new_value_error("unexpected constant inside of a literal pattern".to_owned())) + } + _ => Err( + vm.new_value_error("patterns may only match literals and attribute lookups".to_owned()) + ), + } +} + +fn validate_capture(vm: &VirtualMachine, name: &ast::Identifier) -> PyResult<()> { + if name.as_str() == "_" { + return Err(vm.new_value_error("can't capture name '_' in patterns".to_owned())); + } + validate_name(vm, name.id()) +} + +fn validate_pattern(vm: &VirtualMachine, pattern: &ast::Pattern, star_ok: bool) -> PyResult<()> { + match pattern { + ast::Pattern::MatchValue(value) => validate_pattern_match_value(vm, &value.value), + ast::Pattern::MatchSingleton(singleton) => match singleton.value { + ast::Singleton::None | ast::Singleton::True | ast::Singleton::False => Ok(()), + }, + ast::Pattern::MatchSequence(seq) => validate_patterns(vm, &seq.patterns, true), + ast::Pattern::MatchMapping(mapping) => { + if mapping.keys.len() != mapping.patterns.len() { + return Err(vm.new_value_error( + "MatchMapping doesn't have the same number of keys as patterns".to_owned(), + )); + } + if let Some(rest) = &mapping.rest { + validate_capture(vm, rest)?; + } + for key in &mapping.keys { + if let ast::Expr::BooleanLiteral(_) | ast::Expr::NoneLiteral(_) = key { + continue; + } + validate_pattern_match_value(vm, key)?; + } + validate_patterns(vm, &mapping.patterns, false) + } + ast::Pattern::MatchClass(match_class) => { + validate_expr(vm, &match_class.cls, ast::ExprContext::Load)?; + let mut cls = match_class.cls.as_ref(); + loop { + match cls { + ast::Expr::Name(_) => break, + ast::Expr::Attribute(attr) => { + cls = &attr.value; + } + _ => { + return Err(vm.new_value_error( + "MatchClass cls field can only contain Name or Attribute nodes." + .to_owned(), + )); + } + } + } + for keyword in &match_class.arguments.keywords { + validate_name(vm, keyword.attr.id())?; + } + validate_patterns(vm, &match_class.arguments.patterns, false)?; + for keyword in &match_class.arguments.keywords { + validate_pattern(vm, &keyword.pattern, false)?; + } + Ok(()) + } + ast::Pattern::MatchStar(star) => { + if !star_ok { + return Err(vm.new_value_error("can't use MatchStar here".to_owned())); + } + if let Some(name) = &star.name { + validate_capture(vm, name)?; + } + Ok(()) + } + ast::Pattern::MatchAs(match_as) => { + if let Some(name) = &match_as.name { + validate_capture(vm, name)?; + } + match &match_as.pattern { + None => Ok(()), + Some(pattern) => { + if match_as.name.is_none() { + return Err(vm.new_value_error( + "MatchAs must specify a target name if a pattern is given".to_owned(), + )); + } + validate_pattern(vm, pattern, false) + } + } + } + ast::Pattern::MatchOr(match_or) => { + if match_or.patterns.len() < 2 { + return Err(vm.new_value_error("MatchOr requires at least 2 patterns".to_owned())); + } + validate_patterns(vm, &match_or.patterns, false) + } + } +} + +fn validate_patterns( + vm: &VirtualMachine, + patterns: &[ast::Pattern], + star_ok: bool, +) -> PyResult<()> { + for pattern in patterns { + validate_pattern(vm, pattern, star_ok)?; + } + Ok(()) +} + +fn validate_typeparam(vm: &VirtualMachine, tp: &ast::TypeParam) -> PyResult<()> { + match tp { + ast::TypeParam::TypeVar(tp) => { + validate_name(vm, tp.name.id())?; + if let Some(bound) = &tp.bound { + validate_expr(vm, bound, ast::ExprContext::Load)?; + } + if let Some(default) = &tp.default { + validate_expr(vm, default, ast::ExprContext::Load)?; + } + } + ast::TypeParam::ParamSpec(tp) => { + validate_name(vm, tp.name.id())?; + if let Some(default) = &tp.default { + validate_expr(vm, default, ast::ExprContext::Load)?; + } + } + ast::TypeParam::TypeVarTuple(tp) => { + validate_name(vm, tp.name.id())?; + if let Some(default) = &tp.default { + validate_expr(vm, default, ast::ExprContext::Load)?; + } + } + } + Ok(()) +} + +fn validate_type_params( + vm: &VirtualMachine, + type_params: &Option>, +) -> PyResult<()> { + if let Some(type_params) = type_params { + for tp in &type_params.type_params { + validate_typeparam(vm, tp)?; + } + } + Ok(()) +} + +fn validate_exprs( + vm: &VirtualMachine, + exprs: &[ast::Expr], + ctx: ast::ExprContext, + _null_ok: bool, +) -> PyResult<()> { + for expr in exprs { + validate_expr(vm, expr, ctx)?; + } + Ok(()) +} + +fn validate_expr(vm: &VirtualMachine, expr: &ast::Expr, ctx: ast::ExprContext) -> PyResult<()> { + let mut check_ctx = true; + let actual_ctx = match expr { + ast::Expr::Attribute(attr) => attr.ctx, + ast::Expr::Subscript(sub) => sub.ctx, + ast::Expr::Starred(star) => star.ctx, + ast::Expr::Name(name) => { + validate_name(vm, name.id())?; + name.ctx + } + ast::Expr::List(list) => list.ctx, + ast::Expr::Tuple(tuple) => tuple.ctx, + _ => { + if ctx != ast::ExprContext::Load { + return Err(vm.new_value_error(format!( + "expression which can't be assigned to in {} context", + expr_context_name(ctx) + ))); + } + check_ctx = false; + ast::ExprContext::Invalid + } + }; + if check_ctx && actual_ctx != ctx { + return Err(vm.new_value_error(format!( + "expression must have {} context but has {} instead", + expr_context_name(ctx), + expr_context_name(actual_ctx) + ))); + } + + match expr { + ast::Expr::BoolOp(op) => { + if op.values.len() < 2 { + return Err(vm.new_value_error("BoolOp with less than 2 values".to_owned())); + } + validate_exprs(vm, &op.values, ast::ExprContext::Load, false) + } + ast::Expr::Named(named) => { + if !matches!(&*named.target, ast::Expr::Name(_)) { + return Err(vm.new_type_error("NamedExpr target must be a Name".to_owned())); + } + validate_expr(vm, &named.value, ast::ExprContext::Load) + } + ast::Expr::BinOp(bin) => { + validate_expr(vm, &bin.left, ast::ExprContext::Load)?; + validate_expr(vm, &bin.right, ast::ExprContext::Load) + } + ast::Expr::UnaryOp(unary) => validate_expr(vm, &unary.operand, ast::ExprContext::Load), + ast::Expr::Lambda(lambda) => { + if let Some(parameters) = &lambda.parameters { + validate_parameters(vm, parameters)?; + } + validate_expr(vm, &lambda.body, ast::ExprContext::Load) + } + ast::Expr::If(ifexp) => { + validate_expr(vm, &ifexp.test, ast::ExprContext::Load)?; + validate_expr(vm, &ifexp.body, ast::ExprContext::Load)?; + validate_expr(vm, &ifexp.orelse, ast::ExprContext::Load) + } + ast::Expr::Dict(dict) => { + for item in &dict.items { + if let Some(key) = &item.key { + validate_expr(vm, key, ast::ExprContext::Load)?; + } + validate_expr(vm, &item.value, ast::ExprContext::Load)?; + } + Ok(()) + } + ast::Expr::Set(set) => validate_exprs(vm, &set.elts, ast::ExprContext::Load, false), + ast::Expr::ListComp(list) => { + validate_comprehension(vm, &list.generators)?; + validate_expr(vm, &list.elt, ast::ExprContext::Load) + } + ast::Expr::SetComp(set) => { + validate_comprehension(vm, &set.generators)?; + validate_expr(vm, &set.elt, ast::ExprContext::Load) + } + ast::Expr::DictComp(dict) => { + validate_comprehension(vm, &dict.generators)?; + validate_expr(vm, &dict.key, ast::ExprContext::Load)?; + validate_expr(vm, &dict.value, ast::ExprContext::Load) + } + ast::Expr::Generator(generator) => { + validate_comprehension(vm, &generator.generators)?; + validate_expr(vm, &generator.elt, ast::ExprContext::Load) + } + ast::Expr::Yield(yield_expr) => { + if let Some(value) = &yield_expr.value { + validate_expr(vm, value, ast::ExprContext::Load)?; + } + Ok(()) + } + ast::Expr::YieldFrom(yield_expr) => { + validate_expr(vm, &yield_expr.value, ast::ExprContext::Load) + } + ast::Expr::Await(await_expr) => { + validate_expr(vm, &await_expr.value, ast::ExprContext::Load) + } + ast::Expr::Compare(compare) => { + if compare.comparators.is_empty() { + return Err(vm.new_value_error("Compare with no comparators".to_owned())); + } + if compare.comparators.len() != compare.ops.len() { + return Err(vm.new_value_error( + "Compare has a different number of comparators and operands".to_owned(), + )); + } + validate_exprs(vm, &compare.comparators, ast::ExprContext::Load, false)?; + validate_expr(vm, &compare.left, ast::ExprContext::Load) + } + ast::Expr::Call(call) => { + validate_expr(vm, &call.func, ast::ExprContext::Load)?; + validate_exprs(vm, &call.arguments.args, ast::ExprContext::Load, false)?; + validate_keywords(vm, &call.arguments.keywords) + } + ast::Expr::FString(fstring) => validate_interpolated_elements( + vm, + fstring + .value + .elements() + .map(ast::InterpolatedStringElementRef::from), + ), + ast::Expr::TString(tstring) => validate_interpolated_elements( + vm, + tstring + .value + .elements() + .map(ast::InterpolatedStringElementRef::from), + ), + ast::Expr::StringLiteral(_) + | ast::Expr::BytesLiteral(_) + | ast::Expr::NumberLiteral(_) + | ast::Expr::BooleanLiteral(_) + | ast::Expr::NoneLiteral(_) + | ast::Expr::EllipsisLiteral(_) => Ok(()), + ast::Expr::Attribute(attr) => validate_expr(vm, &attr.value, ast::ExprContext::Load), + ast::Expr::Subscript(sub) => { + validate_expr(vm, &sub.slice, ast::ExprContext::Load)?; + validate_expr(vm, &sub.value, ast::ExprContext::Load) + } + ast::Expr::Starred(star) => validate_expr(vm, &star.value, ctx), + ast::Expr::Name(_) => Ok(()), + ast::Expr::List(list) => validate_exprs(vm, &list.elts, ctx, false), + ast::Expr::Tuple(tuple) => validate_exprs(vm, &tuple.elts, ctx, false), + ast::Expr::Slice(slice) => { + if let Some(lower) = &slice.lower { + validate_expr(vm, lower, ast::ExprContext::Load)?; + } + if let Some(upper) = &slice.upper { + validate_expr(vm, upper, ast::ExprContext::Load)?; + } + if let Some(step) = &slice.step { + validate_expr(vm, step, ast::ExprContext::Load)?; + } + Ok(()) + } + ast::Expr::IpyEscapeCommand(_) => Ok(()), + } +} + +fn validate_decorators(vm: &VirtualMachine, decorators: &[ast::Decorator]) -> PyResult<()> { + for decorator in decorators { + validate_expr(vm, &decorator.expression, ast::ExprContext::Load)?; + } + Ok(()) +} + +fn validate_stmt(vm: &VirtualMachine, stmt: &ast::Stmt) -> PyResult<()> { + match stmt { + ast::Stmt::FunctionDef(func) => { + let owner = if func.is_async { + "AsyncFunctionDef" + } else { + "FunctionDef" + }; + validate_body(vm, &func.body, owner)?; + validate_type_params(vm, &func.type_params)?; + validate_parameters(vm, &func.parameters)?; + validate_decorators(vm, &func.decorator_list)?; + if let Some(returns) = &func.returns { + validate_expr(vm, returns, ast::ExprContext::Load)?; + } + Ok(()) + } + ast::Stmt::ClassDef(class_def) => { + validate_body(vm, &class_def.body, "ClassDef")?; + validate_type_params(vm, &class_def.type_params)?; + if let Some(arguments) = &class_def.arguments { + validate_exprs(vm, &arguments.args, ast::ExprContext::Load, false)?; + validate_keywords(vm, &arguments.keywords)?; + } + validate_decorators(vm, &class_def.decorator_list) + } + ast::Stmt::Return(ret) => { + if let Some(value) = &ret.value { + validate_expr(vm, value, ast::ExprContext::Load)?; + } + Ok(()) + } + ast::Stmt::Delete(del) => validate_assignlist(vm, &del.targets, ast::ExprContext::Del), + ast::Stmt::Assign(assign) => { + validate_assignlist(vm, &assign.targets, ast::ExprContext::Store)?; + validate_expr(vm, &assign.value, ast::ExprContext::Load) + } + ast::Stmt::AugAssign(assign) => { + validate_expr(vm, &assign.target, ast::ExprContext::Store)?; + validate_expr(vm, &assign.value, ast::ExprContext::Load) + } + ast::Stmt::AnnAssign(assign) => { + if assign.simple && !matches!(&*assign.target, ast::Expr::Name(_)) { + return Err(vm.new_type_error("AnnAssign with simple non-Name target".to_owned())); + } + validate_expr(vm, &assign.target, ast::ExprContext::Store)?; + if let Some(value) = &assign.value { + validate_expr(vm, value, ast::ExprContext::Load)?; + } + validate_expr(vm, &assign.annotation, ast::ExprContext::Load) + } + ast::Stmt::TypeAlias(alias) => { + if !matches!(&*alias.name, ast::Expr::Name(_)) { + return Err(vm.new_type_error("TypeAlias with non-Name name".to_owned())); + } + validate_expr(vm, &alias.name, ast::ExprContext::Store)?; + validate_type_params(vm, &alias.type_params)?; + validate_expr(vm, &alias.value, ast::ExprContext::Load) + } + ast::Stmt::For(for_stmt) => { + let owner = if for_stmt.is_async { "AsyncFor" } else { "For" }; + validate_expr(vm, &for_stmt.target, ast::ExprContext::Store)?; + validate_expr(vm, &for_stmt.iter, ast::ExprContext::Load)?; + validate_body(vm, &for_stmt.body, owner)?; + validate_stmts(vm, &for_stmt.orelse) + } + ast::Stmt::While(while_stmt) => { + validate_expr(vm, &while_stmt.test, ast::ExprContext::Load)?; + validate_body(vm, &while_stmt.body, "While")?; + validate_stmts(vm, &while_stmt.orelse) + } + ast::Stmt::If(if_stmt) => { + validate_expr(vm, &if_stmt.test, ast::ExprContext::Load)?; + validate_body(vm, &if_stmt.body, "If")?; + for clause in &if_stmt.elif_else_clauses { + if let Some(test) = &clause.test { + validate_expr(vm, test, ast::ExprContext::Load)?; + } + validate_body(vm, &clause.body, "If")?; + } + Ok(()) + } + ast::Stmt::With(with_stmt) => { + let owner = if with_stmt.is_async { + "AsyncWith" + } else { + "With" + }; + validate_nonempty_seq(vm, with_stmt.items.len(), "items", owner)?; + for item in &with_stmt.items { + validate_expr(vm, &item.context_expr, ast::ExprContext::Load)?; + if let Some(optional_vars) = &item.optional_vars { + validate_expr(vm, optional_vars, ast::ExprContext::Store)?; + } + } + validate_body(vm, &with_stmt.body, owner) + } + ast::Stmt::Match(match_stmt) => { + validate_expr(vm, &match_stmt.subject, ast::ExprContext::Load)?; + validate_nonempty_seq(vm, match_stmt.cases.len(), "cases", "Match")?; + for case in &match_stmt.cases { + validate_pattern(vm, &case.pattern, false)?; + if let Some(guard) = &case.guard { + validate_expr(vm, guard, ast::ExprContext::Load)?; + } + validate_body(vm, &case.body, "match_case")?; + } + Ok(()) + } + ast::Stmt::Raise(raise) => { + if let Some(exc) = &raise.exc { + validate_expr(vm, exc, ast::ExprContext::Load)?; + if let Some(cause) = &raise.cause { + validate_expr(vm, cause, ast::ExprContext::Load)?; + } + } else if raise.cause.is_some() { + return Err(vm.new_value_error("Raise with cause but no exception".to_owned())); + } + Ok(()) + } + ast::Stmt::Try(try_stmt) => { + let owner = if try_stmt.is_star { "TryStar" } else { "Try" }; + validate_body(vm, &try_stmt.body, owner)?; + if try_stmt.handlers.is_empty() && try_stmt.finalbody.is_empty() { + return Err(vm.new_value_error(format!( + "{owner} has neither except handlers nor finalbody" + ))); + } + if try_stmt.handlers.is_empty() && !try_stmt.orelse.is_empty() { + return Err( + vm.new_value_error(format!("{owner} has orelse but no except handlers")) + ); + } + for handler in &try_stmt.handlers { + let ast::ExceptHandler::ExceptHandler(handler) = handler; + if let Some(type_expr) = &handler.type_ { + validate_expr(vm, type_expr, ast::ExprContext::Load)?; + } + validate_body(vm, &handler.body, "ExceptHandler")?; + } + validate_stmts(vm, &try_stmt.finalbody)?; + validate_stmts(vm, &try_stmt.orelse) + } + ast::Stmt::Assert(assert_stmt) => { + validate_expr(vm, &assert_stmt.test, ast::ExprContext::Load)?; + if let Some(msg) = &assert_stmt.msg { + validate_expr(vm, msg, ast::ExprContext::Load)?; + } + Ok(()) + } + ast::Stmt::Import(import) => { + validate_nonempty_seq(vm, import.names.len(), "names", "Import")?; + Ok(()) + } + ast::Stmt::ImportFrom(import) => { + validate_nonempty_seq(vm, import.names.len(), "names", "ImportFrom")?; + Ok(()) + } + ast::Stmt::Global(global) => { + validate_nonempty_seq(vm, global.names.len(), "names", "Global")?; + Ok(()) + } + ast::Stmt::Nonlocal(nonlocal) => { + validate_nonempty_seq(vm, nonlocal.names.len(), "names", "Nonlocal")?; + Ok(()) + } + ast::Stmt::Expr(expr) => validate_expr(vm, &expr.value, ast::ExprContext::Load), + ast::Stmt::Pass(_) + | ast::Stmt::Break(_) + | ast::Stmt::Continue(_) + | ast::Stmt::IpyEscapeCommand(_) => Ok(()), + } +} + +fn validate_stmts(vm: &VirtualMachine, stmts: &[ast::Stmt]) -> PyResult<()> { + for stmt in stmts { + validate_stmt(vm, stmt)?; + } + Ok(()) +} + +pub(super) fn validate_mod(vm: &VirtualMachine, module: &Mod) -> PyResult<()> { + match module { + Mod::Module(module) => validate_stmts(vm, &module.body), + Mod::Interactive(module) => validate_stmts(vm, &module.body), + Mod::Expression(expr) => validate_expr(vm, &expr.body, ast::ExprContext::Load), + Mod::FunctionType(func_type) => { + validate_exprs(vm, &func_type.argtypes, ast::ExprContext::Load, false)?; + validate_expr(vm, &func_type.returns, ast::ExprContext::Load) + } + } +} diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 95e5b4d45a9..4f880cc7b92 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -122,9 +122,7 @@ mod builtins { { use crate::{class::PyClassImpl, stdlib::ast}; - if args._feature_version.is_present() { - // TODO: add support for _feature_version - } + let feature_version = feature_version_from_arg(args._feature_version, vm)?; let mode_str = args.mode.as_str(); @@ -168,6 +166,7 @@ mod builtins { args.source.class().name() ))); } + ast::validate_ast_object(vm, args.source.clone())?; return Ok(args.source); } @@ -215,6 +214,9 @@ mod builtins { } let allow_incomplete = !(flags & ast::PY_CF_ALLOW_INCOMPLETE_INPUT).is_zero(); + let type_comments = !(flags & ast::PY_CF_TYPE_COMMENTS).is_zero(); + + let optimize_level = optimize; if (flags & ast::PY_COMPILE_FLAG_AST_ONLY).is_zero() { #[cfg(not(feature = "compiler"))] @@ -223,6 +225,21 @@ mod builtins { } #[cfg(feature = "compiler")] { + if let Some(feature_version) = feature_version { + let mode = mode_str + .parse::() + .map_err(|err| vm.new_value_error(err.to_string()))?; + let _ = ast::parse( + vm, + source, + mode, + optimize_level, + Some(feature_version), + type_comments, + ) + .map_err(|e| (e, Some(source), allow_incomplete).to_pyexception(vm))?; + } + let mode = mode_str .parse::() .map_err(|err| vm.new_value_error(err.to_string()))?; @@ -243,16 +260,53 @@ mod builtins { Ok(code.into()) } } else { + if mode_str == "func_type" { + return ast::parse_func_type(vm, source, optimize_level, feature_version) + .map_err(|e| (e, Some(source), allow_incomplete).to_pyexception(vm)); + } + let mode = mode_str .parse::() .map_err(|err| vm.new_value_error(err.to_string()))?; - ast::parse(vm, source, mode) - .map_err(|e| (e, Some(source), allow_incomplete).to_pyexception(vm)) + let parsed = ast::parse( + vm, + source, + mode, + optimize_level, + feature_version, + type_comments, + ) + .map_err(|e| (e, Some(source), allow_incomplete).to_pyexception(vm))?; + + if mode_str == "single" { + return ast::wrap_interactive(vm, parsed); + } + + Ok(parsed) } } } } + #[cfg(feature = "ast")] + fn feature_version_from_arg( + feature_version: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult> { + let minor = match feature_version.into_option() { + Some(minor) => minor, + None => return Ok(None), + }; + + if minor < 0 { + return Ok(None); + } + + let minor = u8::try_from(minor) + .map_err(|_| vm.new_value_error("compile() _feature_version out of range"))?; + Ok(Some(ruff_python_ast::PythonVersion { major: 3, minor })) + } + #[pyfunction] fn delattr(obj: PyObjectRef, attr: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { let attr = attr.try_to_ref::(vm).map_err(|_e| { diff --git a/crates/vm/src/stdlib/ctypes/function.rs b/crates/vm/src/stdlib/ctypes/function.rs index 3ea166f9871..8383f7460a4 100644 --- a/crates/vm/src/stdlib/ctypes/function.rs +++ b/crates/vm/src/stdlib/ctypes/function.rs @@ -645,7 +645,7 @@ fn wstring_at_impl(ptr: usize, size: isize, vm: &VirtualMachine) -> PyResult { { let s: String = wchars .iter() - .filter_map(|&c| char::from_u32(c as u32)) + .filter_map(|&c| u32::try_from(c).ok().and_then(char::from_u32)) .collect(); Ok(vm.ctx.new_str(s).into()) } @@ -1938,7 +1938,7 @@ fn ffi_to_python(ty: &Py, ptr: *const c_void, vm: &VirtualMachine) -> Py { let s: String = slice .iter() - .filter_map(|&c| char::from_u32(c as u32)) + .filter_map(|&c| u32::try_from(c).ok().and_then(char::from_u32)) .collect(); vm.ctx.new_str(s).into() } diff --git a/crates/vm/src/stdlib/ctypes/simple.rs b/crates/vm/src/stdlib/ctypes/simple.rs index 410628b5039..46228494d5e 100644 --- a/crates/vm/src/stdlib/ctypes/simple.rs +++ b/crates/vm/src/stdlib/ctypes/simple.rs @@ -1181,7 +1181,7 @@ impl PyCSimple { { let s: String = wchars .iter() - .filter_map(|&c| char::from_u32(c as u32)) + .filter_map(|&c| u32::try_from(c).ok().and_then(char::from_u32)) .collect(); return Ok(vm.ctx.new_str(s).into()); } diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index c2630f7f8f3..37c7ecea0e8 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -1813,7 +1813,7 @@ pub(super) mod _os { } else { let encoding = unsafe { let encoding = libc::nl_langinfo(libc::CODESET); - if encoding.is_null() || encoding.read() == '\0' as libc::c_char { + if encoding.is_null() || encoding.read() == b'\0' as libc::c_char { "UTF-8".to_owned() } else { core::ffi::CStr::from_ptr(encoding).to_string_lossy().into_owned() diff --git a/crates/vm/src/suggestion.rs b/crates/vm/src/suggestion.rs index 55326d1d3f0..c23e56d2126 100644 --- a/crates/vm/src/suggestion.rs +++ b/crates/vm/src/suggestion.rs @@ -68,7 +68,7 @@ pub fn offer_suggestions(exc: &Py, vm: &VirtualMachine) -> Opti return Some(suggestions); }; - let builtins: Vec<_> = tb.frame.builtins.as_object().try_to_value(vm).ok()?; + let builtins: Vec<_> = tb.frame.builtins.try_to_value(vm).ok()?; calculate_suggestions(builtins.iter(), &name) } else { None diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 9e75ab2f181..5ea333a5760 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -477,14 +477,29 @@ impl VirtualMachine { } pub fn run_code_obj(&self, code: PyRef, scope: Scope) -> PyResult { - use crate::builtins::PyFunction; + use crate::builtins::{PyFunction, PyModule}; // Create a function object for module code, similar to CPython's PyEval_EvalCode let func = PyFunction::new(code.clone(), scope.globals.clone(), self)?; let func_obj = func.into_ref(&self.ctx).into(); - let frame = Frame::new(code, scope, self.builtins.dict(), &[], Some(func_obj), self) - .into_ref(&self.ctx); + // Extract builtins from globals["__builtins__"], like PyEval_EvalCode + let builtins = match scope + .globals + .get_item_opt(identifier!(self, __builtins__), self)? + { + Some(b) => { + if let Some(module) = b.downcast_ref::() { + module.dict().into() + } else { + b + } + } + None => self.builtins.dict().into(), + }; + + let frame = + Frame::new(code, scope, builtins, &[], Some(func_obj), self).into_ref(&self.ctx); self.run_frame(frame) } diff --git a/extra_tests/snippets/stdlib_types.py b/extra_tests/snippets/stdlib_types.py index 14028268f0e..cdecf12dd2b 100644 --- a/extra_tests/snippets/stdlib_types.py +++ b/extra_tests/snippets/stdlib_types.py @@ -22,19 +22,15 @@ def _run_missing_type_params_regression(): kwarg=None, defaults=[], ) - fn = _ast.FunctionDef("f", args, [], [], None, None) + pass_stmt = _ast.Pass(lineno=1, col_offset=4, end_lineno=1, end_col_offset=8) + fn = _ast.FunctionDef("f", args, [pass_stmt], [], None, None) fn.lineno = 1 fn.col_offset = 0 fn.end_lineno = 1 - fn.end_col_offset = 0 + fn.end_col_offset = 8 mod = _ast.Module([fn], []) - mod.lineno = 1 - mod.col_offset = 0 - mod.end_lineno = 1 - mod.end_col_offset = 0 compiled = compile(mod, "", "exec") exec(compiled, {}) -if platform.python_implementation() == "RustPython": - _run_missing_type_params_regression() +_run_missing_type_params_regression()