use super::expr::*;
use super::helpers::*;
use super::MirBuilder;
use super::BindingMetadata;
use crate::mir::types::*;
use shape_ast::ast::{self, Expr, Span, Spanned, Statement};
pub(super) fn lower_statements(
builder: &mut MirBuilder,
stmts: &[Statement],
exit_block: BasicBlockId,
) {
for (idx, stmt) in stmts.iter().enumerate() {
lower_statement(builder, stmt, exit_block, idx + 1 == stmts.len());
}
}
pub(super) fn lower_statement(
builder: &mut MirBuilder,
stmt: &Statement,
exit_block: BasicBlockId,
is_last: bool,
) {
match stmt {
Statement::VariableDecl(decl, span) => {
lower_var_decl(builder, decl, *span);
}
Statement::Assignment(assign, span) => {
lower_assignment(builder, assign, *span);
}
Statement::Return(value, span) => {
lower_return_control_flow(builder, value.as_ref(), *span);
}
Statement::Expression(expr, span) => {
if is_last {
lower_return_control_flow(builder, Some(expr), *span);
} else {
let _slot = lower_expr_to_temp(builder, expr);
let _ = span; }
}
Statement::Break(span) => {
lower_break_control_flow(builder, None, *span);
}
Statement::Continue(span) => {
lower_continue_control_flow(builder, *span);
}
Statement::If(if_stmt, span) => {
lower_if(builder, if_stmt, *span, exit_block);
}
Statement::While(while_loop, span) => {
lower_while(
builder,
&while_loop.condition,
&while_loop.body,
*span,
exit_block,
);
}
Statement::For(for_loop, span) => {
lower_for_loop(builder, for_loop, *span, exit_block);
}
Statement::Extend(_, span)
| Statement::RemoveTarget(span)
| Statement::SetParamType { span, .. }
| Statement::SetReturnType { span, .. }
| Statement::ReplaceBody { span, .. } => {
builder.push_stmt(StatementKind::Nop, *span);
}
Statement::SetParamValue {
expression, span, ..
}
| Statement::SetReturnExpr { expression, span }
| Statement::ReplaceBodyExpr { expression, span }
| Statement::ReplaceModuleExpr { expression, span } => {
let _ = lower_expr_to_temp(builder, expression);
builder.push_stmt(StatementKind::Nop, *span);
}
}
}
pub(super) fn lower_var_decl(builder: &mut MirBuilder, decl: &ast::VariableDecl, span: Span) {
let binding_metadata = match decl.kind {
ast::VarKind::Const => {
Some(super::immutable_binding_metadata(span, false, true))
}
ast::VarKind::Let if !decl.is_mut => {
Some(super::immutable_binding_metadata(span, true, false))
}
_ => None,
};
if let Some(name) = decl.pattern.as_identifier() {
let type_info = decl
.value
.as_ref()
.map(infer_local_type_from_expr)
.unwrap_or(LocalTypeInfo::Unknown);
let slot = builder.alloc_local_with_binding_deferred(
name.to_string(),
type_info,
binding_metadata.clone(),
);
let annotated_array_elem: Option<shape_value::v2::ConcreteType> =
decl.type_annotation.as_ref().and_then(|annotation| {
crate::compiler::v2_map_emission::concrete_type_from_annotation(annotation)
.and_then(|ct| match ct {
shape_value::v2::ConcreteType::Array(elem) => Some(*elem),
_ => None,
})
});
let empty_array_elem: Option<shape_value::v2::ConcreteType> =
if let (Some(_annotation), Some(Expr::Array(items, _))) =
(decl.type_annotation.as_ref(), decl.value.as_ref())
{
if items.is_empty() {
annotated_array_elem.clone()
} else {
None
}
} else {
None
};
if let Some(elem) = annotated_array_elem.clone() {
builder.record_local_typed_array_element_type(slot, elem);
}
if let Some(decl_ct) = decl
.type_annotation
.as_ref()
.and_then(crate::compiler::v2_map_emission::concrete_type_from_annotation)
{
if matches!(
decl_ct,
shape_value::v2::ConcreteType::I8
| shape_value::v2::ConcreteType::I16
| shape_value::v2::ConcreteType::I32
| shape_value::v2::ConcreteType::U8
| shape_value::v2::ConcreteType::U16
| shape_value::v2::ConcreteType::U32
| shape_value::v2::ConcreteType::U64
) {
builder.record_local_declared_scalar_type(slot, decl_ct);
}
}
if let Some(init_expr) = &decl.value {
if let Expr::FunctionCall { name: ctor_name, .. } = init_expr {
if let Some(kind) =
crate::compiler::mutation_writeback::ContainerKind::from_ctor_name(
ctor_name,
)
{
builder.record_mut_self_container_local(slot, kind);
}
}
let operand = match decl.ownership {
ast::OwnershipModifier::Move => {
lower_expr_to_explicit_move_operand(builder, init_expr)
}
ast::OwnershipModifier::Clone => {
lower_expr_to_operand(builder, init_expr, false)
}
ast::OwnershipModifier::Inferred => {
lower_expr_to_operand(builder, init_expr, true)
}
};
if let Some(elem) = empty_array_elem.clone() {
use crate::mir::types::{Place as MirPlace, Operand as MirOperand};
if let MirOperand::Move(MirPlace::Local(temp))
| MirOperand::Copy(MirPlace::Local(temp))
| MirOperand::MoveExplicit(MirPlace::Local(temp)) = &operand
{
builder.record_local_typed_array_element_type(*temp, elem);
}
}
let rvalue = match decl.ownership {
ast::OwnershipModifier::Clone => Rvalue::Clone(operand),
_ => Rvalue::Use(operand),
};
let point =
builder.push_stmt(StatementKind::Assign(Place::Local(slot), rvalue), span);
if binding_metadata.is_some() {
builder.record_binding_initialization(slot, point);
}
}
builder.bind_named_local_pub(name.to_string(), slot);
return;
}
let source_place = decl.value.as_ref().map(|init_expr| {
let type_info = infer_local_type_from_expr(init_expr);
let source_slot = builder.alloc_temp(type_info);
let operand = match decl.ownership {
ast::OwnershipModifier::Move => {
lower_expr_to_explicit_move_operand(builder, init_expr)
}
ast::OwnershipModifier::Clone => lower_expr_to_operand(builder, init_expr, false),
ast::OwnershipModifier::Inferred => lower_expr_to_operand(builder, init_expr, true),
};
let rvalue = match decl.ownership {
ast::OwnershipModifier::Clone => Rvalue::Clone(operand),
_ => Rvalue::Use(operand),
};
builder.push_stmt(
StatementKind::Assign(Place::Local(source_slot), rvalue),
span,
);
Place::Local(source_slot)
});
lower_destructure_bindings_from_place_opt(
builder,
&decl.pattern,
source_place.as_ref(),
span,
binding_metadata,
);
}
pub(super) fn lower_assignment(builder: &mut MirBuilder, assign: &ast::Assignment, span: Span) {
if let Some(name) = assign.pattern.as_identifier() {
let Some(slot) = builder.lookup_local(name) else {
builder.mark_fallback();
builder.push_stmt(StatementKind::Nop, span);
return;
};
let value = lower_expr_to_operand(builder, &assign.value, true);
builder.push_stmt(
StatementKind::Assign(Place::Local(slot), Rvalue::Use(value)),
span,
);
return;
}
let source_slot = lower_expr_to_temp(builder, &assign.value);
let source_place = Place::Local(source_slot);
lower_destructure_assignment_from_place(builder, &assign.pattern, &source_place, span);
}
pub(super) fn lower_return_control_flow(
builder: &mut MirBuilder,
value: Option<&Expr>,
span: Span,
) {
if let Some(expr) = value {
let result = lower_expr_to_operand(builder, expr, true);
builder.push_stmt(
StatementKind::Assign(Place::Local(builder.return_slot()), Rvalue::Use(result)),
expr.span(),
);
} else {
builder.push_stmt(
StatementKind::Assign(
Place::Local(builder.return_slot()),
Rvalue::Use(Operand::Constant(MirConstant::None)),
),
span,
);
}
builder.finish_block(TerminatorKind::Return, span);
start_dead_block(builder);
}
pub(super) fn lower_break_control_flow(
builder: &mut MirBuilder,
value: Option<&Expr>,
span: Span,
) {
let Some(loop_ctx) = builder.current_loop() else {
builder.mark_fallback();
builder.push_stmt(StatementKind::Nop, span);
return;
};
if let Some(result_slot) = loop_ctx.break_value_slot {
let rvalue = if let Some(expr) = value {
Rvalue::Use(lower_expr_to_operand(builder, expr, true))
} else {
Rvalue::Use(Operand::Constant(MirConstant::None))
};
builder.push_stmt(
StatementKind::Assign(Place::Local(result_slot), rvalue),
span,
);
} else if let Some(expr) = value {
let _ = lower_expr_to_temp(builder, expr);
}
builder.finish_block(TerminatorKind::Goto(loop_ctx.break_block), span);
start_dead_block(builder);
}
pub(super) fn lower_continue_control_flow(builder: &mut MirBuilder, span: Span) {
let Some(loop_ctx) = builder.current_loop() else {
builder.mark_fallback();
builder.push_stmt(StatementKind::Nop, span);
return;
};
builder.finish_block(TerminatorKind::Goto(loop_ctx.continue_block), span);
start_dead_block(builder);
}
fn lower_if(
builder: &mut MirBuilder,
if_stmt: &ast::IfStatement,
span: Span,
exit_block: BasicBlockId,
) {
let cond_slot = lower_expr_to_temp(builder, &if_stmt.condition);
let then_block = builder.new_block();
let else_block = builder.new_block();
let merge_block = builder.new_block();
builder.finish_block(
TerminatorKind::SwitchBool {
operand: Operand::Copy(Place::Local(cond_slot)),
true_bb: then_block,
false_bb: if if_stmt.else_body.is_some() {
else_block
} else {
merge_block
},
},
span,
);
builder.start_block(then_block);
builder.push_scope();
lower_statements(builder, &if_stmt.then_body, exit_block);
builder.pop_scope();
builder.finish_block(TerminatorKind::Goto(merge_block), span);
if let Some(else_body) = &if_stmt.else_body {
builder.start_block(else_block);
builder.push_scope();
lower_statements(builder, else_body, exit_block);
builder.pop_scope();
builder.finish_block(TerminatorKind::Goto(merge_block), span);
}
builder.start_block(merge_block);
}
fn lower_while(
builder: &mut MirBuilder,
cond: &Expr,
body: &[Statement],
span: Span,
exit_block: BasicBlockId,
) {
let header = builder.new_block();
let body_block = builder.new_block();
let after = builder.new_block();
builder.finish_block(TerminatorKind::Goto(header), span);
builder.start_block(header);
let cond_slot = lower_expr_to_temp(builder, cond);
builder.finish_block(
TerminatorKind::SwitchBool {
operand: Operand::Copy(Place::Local(cond_slot)),
true_bb: body_block,
false_bb: after,
},
span,
);
builder.start_block(body_block);
builder.push_loop(after, header, None);
builder.push_scope();
lower_statements(builder, body, exit_block);
builder.pop_scope();
builder.pop_loop();
builder.finish_block(TerminatorKind::Goto(header), span);
builder.start_block(after);
}
fn lower_for_loop(
builder: &mut MirBuilder,
for_loop: &ast::ForLoop,
span: Span,
exit_block: BasicBlockId,
) {
match &for_loop.init {
ast::ForInit::ForIn { pattern, iter } => {
builder.push_scope();
let iter_slot = lower_expr_to_temp(builder, iter);
let idx_slot = builder.alloc_temp(LocalTypeInfo::Copy);
let len_slot = builder.alloc_temp(LocalTypeInfo::Copy);
builder.push_stmt(
StatementKind::Assign(
Place::Local(idx_slot),
Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
),
span,
);
let len_call_func =
Operand::Constant(MirConstant::Method("len".to_string()));
builder.emit_call(
len_call_func,
vec![Operand::Copy(Place::Local(iter_slot))],
Place::Local(len_slot),
span,
);
let header = builder.new_block();
let body_block = builder.new_block();
let continue_target = builder.new_block();
let after = builder.new_block();
builder.finish_block(TerminatorKind::Goto(header), span);
builder.start_block(header);
let cond_slot = builder.alloc_temp(LocalTypeInfo::Copy);
builder.push_stmt(
StatementKind::Assign(
Place::Local(cond_slot),
Rvalue::BinaryOp(
BinOp::Lt,
Operand::Copy(Place::Local(idx_slot)),
Operand::Copy(Place::Local(len_slot)),
),
),
span,
);
builder.finish_block(
TerminatorKind::SwitchBool {
operand: Operand::Copy(Place::Local(cond_slot)),
true_bb: body_block,
false_bb: after,
},
span,
);
builder.start_block(body_block);
let pattern_slot = match pattern {
ast::DestructurePattern::Identifier(name, _) => {
builder.alloc_local(name.clone(), LocalTypeInfo::Unknown)
}
_ => builder.alloc_temp(LocalTypeInfo::Unknown),
};
let index_place = Place::Index(
Box::new(Place::Local(iter_slot)),
Box::new(Operand::Copy(Place::Local(idx_slot))),
);
builder.push_stmt(
StatementKind::Assign(
Place::Local(pattern_slot),
Rvalue::Use(Operand::Copy(index_place)),
),
span,
);
if !matches!(pattern, ast::DestructurePattern::Identifier(_, _)) {
lower_destructure_bindings_from_place(
builder,
pattern,
&Place::Local(pattern_slot),
span,
None,
);
}
builder.push_loop(after, continue_target, None);
builder.push_scope();
lower_statements(builder, &for_loop.body, exit_block);
builder.pop_scope();
builder.pop_loop();
builder.finish_block(TerminatorKind::Goto(continue_target), span);
builder.start_block(continue_target);
builder.push_stmt(
StatementKind::Assign(
Place::Local(idx_slot),
Rvalue::BinaryOp(
BinOp::Add,
Operand::Copy(Place::Local(idx_slot)),
Operand::Constant(MirConstant::Int(1)),
),
),
span,
);
builder.finish_block(TerminatorKind::Goto(header), span);
builder.start_block(after);
builder.pop_scope();
}
ast::ForInit::ForC {
init,
condition,
update,
} => {
builder.push_scope();
lower_statement(builder, init, exit_block, false);
let header = builder.new_block();
let body_block = builder.new_block();
let update_block = builder.new_block();
let after = builder.new_block();
builder.finish_block(TerminatorKind::Goto(header), span);
builder.start_block(header);
let cond_slot = lower_expr_to_temp(builder, condition);
builder.finish_block(
TerminatorKind::SwitchBool {
operand: Operand::Copy(Place::Local(cond_slot)),
true_bb: body_block,
false_bb: after,
},
span,
);
builder.start_block(body_block);
builder.push_loop(after, update_block, None);
builder.push_scope();
lower_statements(builder, &for_loop.body, exit_block);
builder.pop_scope();
builder.pop_loop();
builder.finish_block(TerminatorKind::Goto(update_block), span);
builder.start_block(update_block);
let _ = lower_expr_to_temp(builder, update);
builder.finish_block(TerminatorKind::Goto(header), span);
builder.start_block(after);
builder.pop_scope();
}
}
}
pub(super) fn pattern_has_bindings(pattern: &ast::Pattern) -> bool {
match pattern {
ast::Pattern::Identifier(_) | ast::Pattern::Typed { .. } => true,
ast::Pattern::Array(patterns) => patterns.iter().any(pattern_has_bindings),
ast::Pattern::Object(fields) => fields
.iter()
.any(|(_, pattern)| pattern_has_bindings(pattern)),
ast::Pattern::Constructor { fields, .. } => match fields {
ast::PatternConstructorFields::Unit => false,
ast::PatternConstructorFields::Tuple(patterns) => {
patterns.iter().any(pattern_has_bindings)
}
ast::PatternConstructorFields::Struct(fields) => fields
.iter()
.any(|(_, pattern)| pattern_has_bindings(pattern)),
},
ast::Pattern::Literal(_) | ast::Pattern::Wildcard => false,
}
}
fn lower_constructor_bindings_from_place_opt(
builder: &mut MirBuilder,
fields: &ast::PatternConstructorFields,
source_place: Option<&Place>,
span: Span,
binding_metadata: Option<BindingMetadata>,
) {
match fields {
ast::PatternConstructorFields::Unit => {}
ast::PatternConstructorFields::Tuple(patterns) => {
for (index, pattern) in patterns.iter().enumerate() {
let projected_place =
source_place.map(|source_place| projected_index_place(source_place, index));
lower_pattern_bindings_from_place_opt(
builder,
pattern,
projected_place.as_ref(),
span,
binding_metadata,
);
}
}
ast::PatternConstructorFields::Struct(fields) => {
for (field_name, pattern) in fields {
let projected_place = source_place
.map(|source_place| projected_field_place(builder, source_place, field_name));
lower_pattern_bindings_from_place_opt(
builder,
pattern,
projected_place.as_ref(),
span,
binding_metadata,
);
}
}
}
}
pub(super) fn lower_destructure_bindings_from_place_opt(
builder: &mut MirBuilder,
pattern: &ast::DestructurePattern,
source_place: Option<&Place>,
span: Span,
binding_metadata: Option<BindingMetadata>,
) {
match pattern {
ast::DestructurePattern::Identifier(name, _) => {
let slot = if let Some(binding_metadata) = binding_metadata {
builder.alloc_local_binding(name.clone(), LocalTypeInfo::Unknown, binding_metadata)
} else {
builder.alloc_local(name.clone(), LocalTypeInfo::Unknown)
};
if let Some(source_place) = source_place {
let point = builder.push_stmt(
StatementKind::Assign(
Place::Local(slot),
Rvalue::Use(Operand::Copy(source_place.clone())),
),
span,
);
if binding_metadata.is_some() {
builder.record_binding_initialization(slot, point);
}
}
}
ast::DestructurePattern::Array(patterns) => {
for (index, pattern) in patterns.iter().enumerate() {
let projected_place = source_place.map(|source_place| {
if matches!(pattern, ast::DestructurePattern::Rest(_)) {
rest_slice_place(builder, source_place, index, span)
} else {
projected_index_place(source_place, index)
}
});
lower_destructure_bindings_from_place_opt(
builder,
pattern,
projected_place.as_ref(),
span,
binding_metadata,
);
}
}
ast::DestructurePattern::Object(fields) => {
for field in fields {
let projected_place = source_place
.map(|source_place| projected_field_place(builder, source_place, &field.key));
lower_destructure_bindings_from_place_opt(
builder,
&field.pattern,
projected_place.as_ref(),
span,
binding_metadata,
);
}
}
ast::DestructurePattern::Rest(pattern) => {
lower_destructure_bindings_from_place_opt(
builder,
pattern,
source_place,
span,
binding_metadata,
);
}
ast::DestructurePattern::Decomposition(bindings) => {
for binding in bindings {
let slot = if let Some(binding_metadata) = binding_metadata {
builder.alloc_local_binding(
binding.name.clone(),
LocalTypeInfo::Unknown,
binding_metadata,
)
} else {
builder.alloc_local(binding.name.clone(), LocalTypeInfo::Unknown)
};
if let Some(source_place) = source_place {
let point = builder.push_stmt(
StatementKind::Assign(
Place::Local(slot),
Rvalue::Use(Operand::Copy(source_place.clone())),
),
span,
);
if binding_metadata.is_some() {
builder.record_binding_initialization(slot, point);
}
}
}
}
}
}
pub(super) fn lower_destructure_bindings_from_place(
builder: &mut MirBuilder,
pattern: &ast::DestructurePattern,
source_place: &Place,
span: Span,
binding_metadata: Option<BindingMetadata>,
) {
lower_destructure_bindings_from_place_opt(
builder,
pattern,
Some(source_place),
span,
binding_metadata,
);
}
pub(super) fn lower_pattern_bindings_from_place_opt(
builder: &mut MirBuilder,
pattern: &ast::Pattern,
source_place: Option<&Place>,
span: Span,
binding_metadata: Option<BindingMetadata>,
) {
match pattern {
ast::Pattern::Identifier(name) | ast::Pattern::Typed { name, .. } => {
let slot = if let Some(binding_metadata) = binding_metadata {
builder.alloc_local_binding(name.clone(), LocalTypeInfo::Unknown, binding_metadata)
} else {
builder.alloc_local(name.clone(), LocalTypeInfo::Unknown)
};
if let Some(source_place) = source_place {
let point = builder.push_stmt(
StatementKind::Assign(
Place::Local(slot),
Rvalue::Use(Operand::Copy(source_place.clone())),
),
span,
);
if binding_metadata.is_some() {
builder.record_binding_initialization(slot, point);
}
}
}
ast::Pattern::Array(patterns) => {
for (index, pattern) in patterns.iter().enumerate() {
let projected_place =
source_place.map(|source_place| projected_index_place(source_place, index));
lower_pattern_bindings_from_place_opt(
builder,
pattern,
projected_place.as_ref(),
span,
binding_metadata,
);
}
}
ast::Pattern::Object(fields) => {
for (field_name, pattern) in fields {
let projected_place = source_place
.map(|source_place| projected_field_place(builder, source_place, field_name));
lower_pattern_bindings_from_place_opt(
builder,
pattern,
projected_place.as_ref(),
span,
binding_metadata,
);
}
}
ast::Pattern::Constructor { variant, fields, .. } => {
if let Some(tag) = VariantTag::from_name(variant) {
if matches!(tag, VariantTag::None_) {
return;
}
if let Some(source_place) = source_place {
match fields {
ast::PatternConstructorFields::Tuple(patterns) if patterns.len() == 1 => {
let payload_slot = builder.alloc_temp(LocalTypeInfo::Unknown);
builder.push_stmt(
StatementKind::Assign(
Place::Local(payload_slot),
Rvalue::EnumPayload {
operand: Operand::Copy(source_place.clone()),
variant: tag,
},
),
span,
);
lower_pattern_bindings_from_place_opt(
builder,
&patterns[0],
Some(&Place::Local(payload_slot)),
span,
binding_metadata,
);
return;
}
ast::PatternConstructorFields::Unit => {
return;
}
_ => {
}
}
}
}
lower_constructor_bindings_from_place_opt(
builder,
fields,
source_place,
span,
binding_metadata,
);
}
ast::Pattern::Wildcard => {}
ast::Pattern::Literal(_) => {}
}
}
pub(super) fn lower_pattern_bindings_from_place(
builder: &mut MirBuilder,
pattern: &ast::Pattern,
source_place: &Place,
span: Span,
binding_metadata: Option<BindingMetadata>,
) {
lower_pattern_bindings_from_place_opt(
builder,
pattern,
Some(source_place),
span,
binding_metadata,
);
}
fn lower_destructure_assignment_from_place(
builder: &mut MirBuilder,
pattern: &ast::DestructurePattern,
source_place: &Place,
span: Span,
) {
match pattern {
ast::DestructurePattern::Identifier(name, _) => {
let Some(slot) = builder.lookup_local(name) else {
builder.mark_fallback();
return;
};
builder.push_stmt(
StatementKind::Assign(
Place::Local(slot),
Rvalue::Use(Operand::Copy(source_place.clone())),
),
span,
);
}
ast::DestructurePattern::Array(patterns) => {
for (index, pattern) in patterns.iter().enumerate() {
let projected_place =
if matches!(pattern, ast::DestructurePattern::Rest(_)) {
rest_slice_place(builder, source_place, index, span)
} else {
projected_index_place(source_place, index)
};
lower_destructure_assignment_from_place(builder, pattern, &projected_place, span);
}
}
ast::DestructurePattern::Object(fields) => {
for field in fields {
let projected_place = projected_field_place(builder, source_place, &field.key);
lower_destructure_assignment_from_place(
builder,
&field.pattern,
&projected_place,
span,
);
}
}
ast::DestructurePattern::Rest(pattern) => {
lower_destructure_assignment_from_place(builder, pattern, source_place, span);
}
ast::DestructurePattern::Decomposition(bindings) => {
for binding in bindings {
let Some(slot) = builder.lookup_local(&binding.name) else {
builder.mark_fallback();
return;
};
builder.push_stmt(
StatementKind::Assign(
Place::Local(slot),
Rvalue::Use(Operand::Copy(source_place.clone())),
),
span,
);
}
}
}
}
fn rest_slice_place(
builder: &mut MirBuilder,
source_place: &Place,
rest_index: usize,
span: Span,
) -> Place {
let slice_slot = builder.alloc_temp(LocalTypeInfo::Unknown);
builder.push_stmt(
StatementKind::Assign(
Place::Local(slice_slot),
Rvalue::Aggregate(vec![
Operand::Copy(source_place.clone()),
Operand::Constant(MirConstant::Int(rest_index as i64)),
]),
),
span,
);
Place::Local(slice_slot)
}
pub(super) trait StatementSpan {
fn span(&self) -> Option<Span>;
}
impl StatementSpan for Statement {
fn span(&self) -> Option<Span> {
match self {
Statement::VariableDecl(_, span) => Some(*span),
Statement::Assignment(_, span) => Some(*span),
Statement::Expression(_, span) => Some(*span),
Statement::Return(_, span) => Some(*span),
Statement::If(_, span) => Some(*span),
Statement::While(_, span) => Some(*span),
Statement::For(_, span) => Some(*span),
_ => None,
}
}
}