use super::module::Mod;
use crate::{PyResult, VirtualMachine, compiler::CompileError};
use ruff_python_ast as ast;
use rustpython_codegen::error::{CodegenError, CodegenErrorType};
use rustpython_compiler_core::bytecode::ConstantData;
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 invalid_syntax_error(vm: &VirtualMachine) -> crate::builtins::PyBaseExceptionRef {
vm.new_syntax_error(
&CompileError::Codegen(CodegenError {
location: None,
end_location: None,
error: CodegenErrorType::SyntaxError("invalid syntax".to_owned()),
source_path: "<unknown>".to_owned(),
}),
None,
)
}
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"));
}
for comp in gens {
validate_expr(vm, &comp.target, ast::ExprContext::Store)?;
validate_expr(vm, &comp.iter, ast::ExprContext::Load)?;
validate_runtime_expr_list_slots(vm, comp.runtime_ifs.as_ref(), 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(())
}
pub(super) fn validate_parameter_annotation(
vm: &VirtualMachine,
parameter: &ast::Parameter,
) -> PyResult<()> {
if let Some(annotation) = ¶meter.annotation {
validate_expr(vm, annotation, ast::ExprContext::Load)?;
}
Ok(())
}
fn validate_parameters(vm: &VirtualMachine, params: &ast::Parameters) -> PyResult<()> {
for param in params.posonlyargs.iter().chain(¶ms.args) {
validate_parameter_annotation(vm, ¶m.parameter)?;
}
if let Some(vararg) = ¶ms.vararg
&& let Some(annotation) = &vararg.annotation
{
validate_expr(vm, annotation, ast::ExprContext::Load)?;
}
for param in ¶ms.kwonlyargs {
validate_parameter_annotation(vm, ¶m.parameter)?;
}
if let Some(kwarg) = ¶ms.kwarg
&& let Some(annotation) = &kwarg.annotation
{
validate_expr(vm, annotation, ast::ExprContext::Load)?;
}
if let Some(defaults) = params.runtime_defaults.as_ref() {
for default in defaults {
let Some(default) = default else {
return Err(vm.new_value_error("None disallowed in expression list"));
};
validate_expr(vm, default, ast::ExprContext::Load)?;
}
} else {
for param in params.posonlyargs.iter().chain(¶ms.args) {
if let Some(default) = ¶m.default {
validate_expr(vm, default, ast::ExprContext::Load)?;
}
}
}
for param in ¶ms.kwonlyargs {
if let Some(default) = ¶m.default {
validate_expr(vm, default, 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],
metadata: Option<&Vec<Option<ast::Stmt>>>,
owner: &'static str,
) -> PyResult<()> {
validate_nonempty_seq(vm, body.len(), "body", owner)?;
validate_runtime_stmt_list_slots(vm, metadata)?;
validate_stmts(vm, body)
}
fn validate_interpolated_elements<'a>(
vm: &VirtualMachine,
elements: impl IntoIterator<Item = ast::InterpolatedStringElementRef<'a>>,
) -> 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.runtime_formatted_value_format_spec.as_deref()
{
validate_expr(vm, format_spec, ast::ExprContext::Load)?;
} else if let Some(format_spec) =
interpolation.runtime_interpolation_format_spec.as_deref()
{
validate_expr(vm, format_spec, ast::ExprContext::Load)?;
} else if let Some(format_spec) = &interpolation.format_spec {
validate_interpolated_elements(
vm,
format_spec
.elements
.iter()
.map(ast::InterpolatedStringElementRef::from),
)?;
}
}
}
Ok(())
}
fn ensure_literal_number(expr: &ast::Expr, allow_real: bool, allow_imaginary: bool) -> bool {
let ast::Expr::NumberLiteral(number) = expr else {
return false;
};
match number.value {
ast::Number::Int(_) | ast::Number::Float(_) => allow_real,
ast::Number::Complex { .. } => allow_imaginary,
}
}
fn ensure_literal_negative(expr: &ast::Expr, allow_real: bool, allow_imaginary: bool) -> bool {
let ast::Expr::UnaryOp(unary) = expr else {
return false;
};
if unary.op != ast::UnaryOp::USub {
return false;
}
ensure_literal_number(&unary.operand, allow_real, allow_imaginary)
}
fn ensure_literal_complex(expr: &ast::Expr) -> bool {
let ast::Expr::BinOp(bin) = expr else {
return false;
};
if !matches!(bin.op, ast::Operator::Add | ast::Operator::Sub) {
return false;
}
let real_left = ensure_literal_number(&bin.left, true, false)
|| ensure_literal_negative(&bin.left, true, false);
real_left && ensure_literal_number(&bin.right, false, true)
}
fn ast_constant_value(expr: &ast::Expr) -> Option<ConstantData> {
expr.as_constant_expr()
.map(|expr| super::constant::ast_constant_value_to_constant_data(expr.value.clone()))
}
fn validate_pattern_match_value(vm: &VirtualMachine, expr: &ast::Expr) -> PyResult<()> {
validate_expr(vm, expr, ast::ExprContext::Load)?;
if let Some(constant) = ast_constant_value(expr) {
return match &constant {
ConstantData::Integer { .. }
| ConstantData::Float { .. }
| ConstantData::Bytes { .. }
| ConstantData::Complex { .. }
| ConstantData::Str { .. } => Ok(()),
_ => Err(vm.new_value_error("unexpected constant inside of a literal pattern")),
};
}
match expr {
ast::Expr::NumberLiteral(_) | ast::Expr::StringLiteral(_) | ast::Expr::BytesLiteral(_) => {
Ok(())
}
ast::Expr::Attribute(_) => Ok(()),
ast::Expr::UnaryOp(_) if ensure_literal_negative(expr, true, true) => Ok(()),
ast::Expr::BinOp(_) if ensure_literal_complex(expr) => Ok(()),
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"))
}
_ => Err(vm.new_value_error("patterns may only match literals and attribute lookups")),
}
}
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"));
}
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_runtime_pattern_list_slots(vm, seq.runtime_patterns.as_ref())?;
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",
));
}
if let Some(rest) = &mapping.rest {
validate_capture(vm, rest)?;
}
validate_runtime_expr_option_list_slots(vm, mapping.runtime_keys.as_ref())?;
for key in &mapping.keys {
if matches!(
key,
ast::Expr::BooleanLiteral(_)
| ast::Expr::NoneLiteral(_)
| ast::Expr::Constant(ast::ExprConstant {
value: ast::ConstantValue::Boolean(_) | ast::ConstantValue::None,
..
})
) {
continue;
}
validate_pattern_match_value(vm, key)?;
}
validate_runtime_pattern_list_slots(vm, mapping.runtime_patterns.as_ref())?;
validate_patterns(vm, &mapping.patterns, false)
}
ast::Pattern::MatchClass(match_class) => {
if let (Some(kwd_attrs), Some(kwd_patterns)) = (
match_class.runtime_kwd_attrs.as_ref(),
match_class.runtime_kwd_patterns.as_ref(),
) && kwd_attrs.len() != kwd_patterns.len()
{
return Err(vm.new_value_error(
"MatchClass doesn't have the same number of keyword attributes as patterns",
));
}
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.",
));
}
}
}
for keyword in &match_class.arguments.keywords {
validate_name(vm, keyword.attr.id())?;
}
if let Some(patterns) = &match_class.runtime_patterns {
validate_runtime_nullable_patterns(vm, patterns)?;
}
validate_patterns(vm, &match_class.arguments.patterns, false)?;
if let Some(kwd_patterns) = &match_class.runtime_kwd_patterns {
validate_runtime_nullable_patterns(vm, kwd_patterns)?;
}
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"));
}
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",
));
}
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"));
}
validate_runtime_pattern_list_slots(vm, match_or.runtime_patterns.as_ref())?;
validate_patterns(vm, &match_or.patterns, false)
}
}
}
fn validate_runtime_pattern_list_slots(
vm: &VirtualMachine,
values: Option<&Vec<Option<ast::Pattern>>>,
) -> PyResult<()> {
if values.is_some_and(|values| values.iter().any(Option::is_none)) {
return Err(vm.new_value_error("unexpected pattern"));
}
Ok(())
}
fn validate_runtime_expr_option_list_slots(
vm: &VirtualMachine,
values: Option<&Vec<Option<ast::Expr>>>,
) -> PyResult<()> {
if values.is_some_and(|values| values.iter().any(Option::is_none)) {
return Err(vm.new_value_error("None disallowed in expression list"));
}
Ok(())
}
fn validate_runtime_expr_list_slots(
vm: &VirtualMachine,
values: Option<&Vec<Option<ast::Expr>>>,
ctx: ast::ExprContext,
) -> PyResult<()> {
if let Some(values) = values {
for value in values {
let Some(value) = value else {
return Err(vm.new_value_error("None disallowed in expression list"));
};
validate_expr(vm, value, ctx)?;
}
}
Ok(())
}
fn validate_runtime_stmt_list_slots(
vm: &VirtualMachine,
values: Option<&Vec<Option<ast::Stmt>>>,
) -> PyResult<()> {
if let Some(values) = values
&& values.iter().any(Option::is_none)
{
return Err(vm.new_value_error("None disallowed in statement list"));
}
Ok(())
}
fn validate_runtime_except_handler_list_slots(
vm: &VirtualMachine,
values: Option<&Vec<Option<ast::ExceptHandler>>>,
) -> PyResult<()> {
if values.is_some_and(|values| values.iter().any(Option::is_none)) {
return Err(vm.new_value_error("unexpected excepthandler"));
}
Ok(())
}
fn validate_runtime_nullable_patterns(
vm: &VirtualMachine,
patterns: &[Option<ast::Pattern>],
) -> PyResult<()> {
if patterns.iter().any(Option::is_none) {
return Err(vm.new_value_error("unexpected pattern"));
}
Ok(())
}
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<&ast::TypeParams>,
) -> PyResult<()> {
if let Some(type_params) = type_params {
if let Some(values) = type_params.runtime_type_params.as_ref() {
for tp in values.iter().flatten() {
validate_typeparam(vm, tp)?;
}
return Ok(());
}
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"));
}
validate_runtime_expr_list_slots(
vm,
op.runtime_values.as_ref(),
ast::ExprContext::Load,
)?;
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"));
}
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) => {
validate_runtime_expr_list_slots(
vm,
dict.runtime_values.as_ref(),
ast::ExprContext::Load,
)?;
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_runtime_expr_list_slots(
vm,
set.runtime_elts.as_ref(),
ast::ExprContext::Load,
)?;
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)?;
let key = dict.key.as_deref().ok_or_else(|| {
vm.new_value_error("field 'key' is required for DictComp".to_owned())
})?;
validate_expr(vm, 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"));
}
if compare.comparators.len() != compare.ops.len() {
return Err(vm.new_value_error(
"Compare has a different number of comparators and operands",
));
}
validate_runtime_expr_list_slots(
vm,
compare.runtime_comparators.as_ref(),
ast::ExprContext::Load,
)?;
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_runtime_expr_list_slots(
vm,
call.arguments.runtime_args.as_ref(),
ast::ExprContext::Load,
)?;
validate_exprs(vm, &call.arguments.args, ast::ExprContext::Load, false)?;
validate_keywords(vm, &call.arguments.keywords)
}
ast::Expr::FString(fstring) => {
validate_runtime_expr_list_slots(
vm,
fstring.runtime_values.as_ref(),
ast::ExprContext::Load,
)?;
if let Some(joined_str) = fstring.runtime_joined_str.as_ref() {
validate_exprs(vm, joined_str, ast::ExprContext::Load, false)
} else {
validate_interpolated_elements(
vm,
fstring
.value
.elements()
.map(ast::InterpolatedStringElementRef::from),
)
}
}
ast::Expr::TString(tstring) => {
validate_runtime_expr_list_slots(
vm,
tstring.runtime_values.as_ref(),
ast::ExprContext::Load,
)?;
if let Some(template_str) = tstring.runtime_template_str.as_ref() {
validate_exprs(vm, template_str, ast::ExprContext::Load, false)
} else {
validate_interpolated_elements(
vm,
tstring
.value
.elements()
.map(ast::InterpolatedStringElementRef::from),
)
}
}
ast::Expr::StringLiteral(_)
| ast::Expr::BytesLiteral(_)
| ast::Expr::NumberLiteral(_)
| ast::Expr::Constant(_)
| ast::Expr::BooleanLiteral(_)
| ast::Expr::NoneLiteral(_)
| ast::Expr::EllipsisLiteral(_) => {
if let Some(invalid_type) = super::constant::invalid_constant_type(expr) {
Err(vm.new_type_error(format!("got an invalid type in Constant: {invalid_type}")))
} else {
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_runtime_expr_list_slots(vm, list.runtime_elts.as_ref(), ctx)?;
validate_exprs(vm, &list.elts, ctx, false)
}
ast::Expr::Tuple(tuple) => {
validate_runtime_expr_list_slots(vm, tuple.runtime_elts.as_ref(), ctx)?;
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(_) => Err(invalid_syntax_error(vm)),
}
}
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, func.runtime_body.as_ref(), owner)?;
validate_type_params(vm, func.type_params.as_deref())?;
validate_parameters(vm, &func.parameters)?;
validate_runtime_expr_list_slots(
vm,
func.runtime_decorator_list.as_ref(),
ast::ExprContext::Load,
)?;
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,
class_def.runtime_body.as_ref(),
"ClassDef",
)?;
validate_type_params(vm, class_def.type_params.as_deref())?;
if let Some(arguments) = &class_def.arguments {
validate_runtime_expr_list_slots(
vm,
arguments.runtime_bases.as_ref(),
ast::ExprContext::Load,
)?;
validate_exprs(vm, &arguments.args, ast::ExprContext::Load, false)?;
validate_keywords(vm, &arguments.keywords)?;
}
validate_runtime_expr_list_slots(
vm,
class_def.runtime_decorator_list.as_ref(),
ast::ExprContext::Load,
)?;
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_runtime_expr_list_slots(
vm,
del.runtime_targets.as_ref(),
ast::ExprContext::Del,
)?;
validate_assignlist(vm, &del.targets, ast::ExprContext::Del)
}
ast::Stmt::Assign(assign) => {
validate_runtime_expr_list_slots(
vm,
assign.runtime_targets.as_ref(),
ast::ExprContext::Store,
)?;
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"));
}
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"));
}
validate_expr(vm, &alias.name, ast::ExprContext::Store)?;
validate_type_params(vm, alias.type_params.as_deref())?;
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, for_stmt.runtime_body.as_ref(), owner)?;
validate_runtime_stmt_list_slots(vm, for_stmt.runtime_orelse.as_ref())?;
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_stmt.runtime_body.as_ref(),
"While",
)?;
validate_runtime_stmt_list_slots(vm, while_stmt.runtime_orelse.as_ref())?;
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_stmt.runtime_body.as_ref(), "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, clause.runtime_body.as_ref(), "If")?;
validate_runtime_stmt_list_slots(vm, clause.runtime_orelse.as_ref())?;
}
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, with_stmt.runtime_body.as_ref(), 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, case.runtime_body.as_ref(), "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"));
}
Ok(())
}
ast::Stmt::Try(try_stmt) => {
let owner = if try_stmt.is_star { "TryStar" } else { "Try" };
validate_body(vm, &try_stmt.body, try_stmt.runtime_body.as_ref(), 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"))
);
}
validate_runtime_except_handler_list_slots(vm, try_stmt.runtime_handlers.as_ref())?;
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,
handler.runtime_body.as_ref(),
"ExceptHandler",
)?;
}
validate_runtime_stmt_list_slots(vm, try_stmt.runtime_finalbody.as_ref())?;
validate_stmts(vm, &try_stmt.finalbody)?;
validate_runtime_stmt_list_slots(vm, try_stmt.runtime_orelse.as_ref())?;
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) => {
if let Some(level) = import.runtime_level
&& level < 0
{
return Err(vm.new_value_error("Negative ImportFrom level"));
}
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(_) => Ok(()),
ast::Stmt::IpyEscapeCommand(_) => Err(invalid_syntax_error(vm)),
}
}
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_runtime_stmt_list_slots(vm, module.module.runtime_body.as_ref())?;
validate_stmts(vm, &module.module.body)
}
Mod::Interactive(module) => {
validate_runtime_stmt_list_slots(vm, module.runtime_body.as_ref())?;
validate_stmts(vm, &module.body)
}
Mod::Expression(expr) => validate_expr(vm, &expr.body, ast::ExprContext::Load),
Mod::FunctionType(func_type) => {
validate_runtime_expr_option_list_slots(vm, func_type.runtime_argtypes.as_ref())?;
validate_exprs(vm, &func_type.argtypes, ast::ExprContext::Load, false)?;
validate_expr(vm, &func_type.returns, ast::ExprContext::Load)
}
}
}