diff --git a/debugger/cli/src/main.rs b/debugger/cli/src/main.rs index d5e38cc7..e90cf431 100644 --- a/debugger/cli/src/main.rs +++ b/debugger/cli/src/main.rs @@ -125,7 +125,7 @@ fn expr_to_artifact_value(expr: &Expr<'_>) -> Result { ExprKind::Bool(value) => Ok(ArtifactValue::Bool(*value)), ExprKind::Byte(value) => Ok(ArtifactValue::Byte(*value)), ExprKind::String(value) => Ok(ArtifactValue::Text(value.clone())), - ExprKind::Array { type_ref, values } if matches!(type_ref.base, TypeBase::Byte) && type_ref.array_dims.len() == 1 => { + ExprKind::Array { type_ref, values, .. } if matches!(type_ref.base, TypeBase::Byte) && type_ref.array_dims.len() == 1 => { let bytes = values .iter() .map(|value| match value.kind { diff --git a/debugger/session/src/session.rs b/debugger/session/src/session.rs index 5337fdbe..e97d9199 100644 --- a/debugger/session/src/session.rs +++ b/debugger/session/src/session.rs @@ -2178,8 +2178,12 @@ where }, span, )), - ExprKind::Array { type_ref, values } => Ok(Expr::new( - ExprKind::Array { type_ref: type_ref.clone(), values: values.iter().map(&mut *map_child).collect::, _>>()? }, + ExprKind::Array { type_ref, values, type_span } => Ok(Expr::new( + ExprKind::Array { + type_ref: type_ref.clone(), + values: values.iter().map(&mut *map_child).collect::, _>>()?, + type_span: *type_span, + }, span, )), ExprKind::StructLiteral { name, fields, name_span } => Ok(Expr::new( diff --git a/silverscript-lang/src/ast/mod.rs b/silverscript-lang/src/ast/mod.rs index f0be7d60..cf74ae03 100644 --- a/silverscript-lang/src/ast/mod.rs +++ b/silverscript-lang/src/ast/mod.rs @@ -8,7 +8,7 @@ use crate::checked_arithmetic::{checked_mul, checked_pow}; use crate::errors::CompilerError; use crate::parser::{ Rule, parse_expression as parse_expression_rule, parse_function as parse_function_rule, parse_source_file, - parse_type_name as parse_type_name_rule, + parse_statement as parse_statement_rule, parse_type_name as parse_type_name_rule, }; pub use crate::span::{Span, SpanUtils}; @@ -444,6 +444,8 @@ pub enum Statement<'i> { #[serde(skip_deserializing)] span: Span<'i>, #[serde(skip_deserializing)] + target_struct_span: Span<'i>, + #[serde(skip_deserializing)] name_span: Span<'i>, }, StructDestructure { @@ -452,6 +454,8 @@ pub enum Statement<'i> { expr: Expr<'i>, #[serde(skip_deserializing)] span: Span<'i>, + #[serde(skip_deserializing)] + struct_name_span: Span<'i>, }, Assign { name: String, @@ -621,7 +625,7 @@ impl<'i> Expr<'i> { if matches!(type_ref.array_dims.last(), Some(ArrayDim::Inferred)) { *type_ref.array_dims.last_mut().unwrap() = ArrayDim::Fixed(values.len()); } - Self::new(ExprKind::Array { type_ref, values }, Span::default()) + Self::new(ExprKind::Array { type_ref, values, type_span: Span::default() }, Span::default()) } pub fn inferred_array(values: Vec>) -> Option { @@ -726,6 +730,8 @@ pub enum ExprKind<'i> { Array { type_ref: TypeRef, values: Vec>, + #[serde(skip_deserializing)] + type_span: Span<'i>, }, Call { name: String, @@ -960,6 +966,10 @@ impl SourceFormatter { } fn write_function(&mut self, function: &FunctionAst<'_>) { + for attribute in &function.attributes { + self.line(&format_function_attribute(attribute)); + } + let mut signature = String::new(); if function.entrypoint { signature.push_str("entry "); @@ -1108,10 +1118,12 @@ impl SourceFormatter { } fn ordered_contract_items<'a, 'i>(contract: &'a ContractAst<'i>) -> Vec> { - let has_real_spans = contract.structs.iter().any(|item| !item.span.is_empty()) - || contract.fields.iter().any(|field| !field.span.is_empty()) - || contract.constants.iter().any(|constant| !constant.span.is_empty()) - || contract.functions.iter().any(|function| !function.span.is_empty()); + // Require all items to be spanned: sorting a mixed AST would move synthetic + // zero-span items before parsed items and could change semantic vector order. + let all_spanned = contract.structs.iter().all(|item| !item.span.is_empty()) + && contract.fields.iter().all(|field| !field.span.is_empty()) + && contract.constants.iter().all(|constant| !constant.span.is_empty()) + && contract.functions.iter().all(|function| !function.span.is_empty()); let mut items = Vec::with_capacity(contract.structs.len() + contract.fields.len() + contract.constants.len() + contract.functions.len()); @@ -1128,13 +1140,23 @@ fn ordered_contract_items<'a, 'i>(contract: &'a ContractAst<'i>) -> Vec) -> String { + let path = attribute.path.join("."); + if attribute.args.is_empty() { + return format!("#[{path}]"); + } + + let args = attribute.args.iter().map(|arg| format!("{} = {}", arg.name, format_expr(&arg.expr))).collect::>().join(", "); + format!("#[{path}({args})]") +} + fn format_params(params: &[ParamAst<'_>]) -> String { params.iter().map(|param| format!("{} {}", param.type_ref.type_name(), param.name)).collect::>().join(", ") } @@ -1169,7 +1191,7 @@ fn format_expr_with_prec(expr: &Expr<'_>, parent_prec: u8, right_child: bool) -> ExprKind::String(value) => format_string_literal(value), ExprKind::DateLiteral(value) => format!("temporal({value})"), ExprKind::Identifier(value) => value.clone(), - ExprKind::Array { type_ref, values } => format_array(type_ref, values), + ExprKind::Array { type_ref, values, .. } => format_array(type_ref, values), ExprKind::Call { name, args, .. } => { if let (Some(type_ref), [source]) = (as_cast_type(name), args.as_slice()) { format!("{} as {}", format_expr_with_prec(source, PREC_POSTFIX, false), type_ref.type_name()) @@ -1489,6 +1511,14 @@ pub fn parse_function_ast<'i>(source: &'i str) -> Result, Compil parse_function_definition(function_pair) } +/// Parses exactly one standalone statement. +pub fn parse_statement_ast<'i>(source: &'i str) -> Result, CompilerError> { + let mut pairs = parse_statement_rule(source)?; + let source_pair = pairs.next().ok_or_else(|| CompilerError::Unsupported("empty statement source".to_string()))?; + let statement_pair = source_pair.into_inner().next().ok_or_else(|| CompilerError::Unsupported("no statement".to_string()))?; + parse_statement(statement_pair) +} + pub fn parse_expression_ast<'i>(source: &'i str) -> Result, CompilerError> { let mut pairs = parse_expression_rule(source)?; let expr_pair = pairs.next().ok_or_else(|| CompilerError::Unsupported("empty expression".to_string()))?; @@ -1837,7 +1867,7 @@ fn parse_statement<'i>(pair: Pair<'i, Rule>) -> Result, CompilerEr let struct_pair = inner .next() .ok_or_else(|| CompilerError::Unsupported("missing destructuring struct name".to_string()).with_span(&span))?; - let Identifier { name: target_struct, .. } = parse_identifier(struct_pair)?; + let Identifier { name: target_struct, span: target_struct_span } = parse_identifier(struct_pair)?; let mut bindings = Vec::new(); while let Some(p) = inner.peek() { if p.as_rule() != Rule::state_typed_binding { @@ -1850,14 +1880,14 @@ fn parse_statement<'i>(pair: Pair<'i, Rule>) -> Result, CompilerEr inner.next().ok_or_else(|| CompilerError::Unsupported("missing function call".to_string()).with_span(&span))?; let (Identifier { name, span: name_span }, args) = parse_function_call_parts(call_pair).map_err(|err| err.with_span(&span))?; - Ok(Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, name_span }) + Ok(Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, target_struct_span, name_span }) } Rule::struct_destructure_assignment => { let mut inner = pair.into_inner(); let struct_pair = inner .next() .ok_or_else(|| CompilerError::Unsupported("missing destructuring struct name".to_string()).with_span(&span))?; - let Identifier { name: struct_name, .. } = parse_identifier(struct_pair)?; + let Identifier { name: struct_name, span: struct_name_span } = parse_identifier(struct_pair)?; let mut bindings = Vec::new(); while let Some(p) = inner.peek() { if p.as_rule() != Rule::state_typed_binding { @@ -1870,7 +1900,7 @@ fn parse_statement<'i>(pair: Pair<'i, Rule>) -> Result, CompilerEr .next() .ok_or_else(|| CompilerError::Unsupported("missing destructuring expression".to_string()).with_span(&span))?; let expr = parse_expression(expr_pair).map_err(|err| err.with_span(&span))?; - Ok(Statement::StructDestructure { struct_name, bindings, expr, span }) + Ok(Statement::StructDestructure { struct_name, bindings, expr, span, struct_name_span }) } Rule::call_statement => { let mut inner = pair.into_inner(); @@ -2159,15 +2189,16 @@ fn parse_unary<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { fn parse_postfix<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { let mut inner = pair.into_inner(); let primary = inner.next().ok_or_else(|| CompilerError::Unsupported("missing primary in postfix".to_string()))?; + let mut source_span = Span::from(primary.as_span()); let mut expr = parse_primary(primary)?; for postfix in inner { let postfix_span = Span::from(postfix.as_span()); + let span = source_span.join(&postfix_span); match postfix.as_rule() { Rule::split_call => { let mut split_inner = postfix.into_inner(); let index_expr = split_inner.next().ok_or_else(|| CompilerError::Unsupported("missing split index".to_string()))?; let index = Box::new(parse_expression(index_expr)?); - let span = expr.span.join(&postfix_span); expr = Expr::new(ExprKind::Split { source: Box::new(expr), index, part: SplitPart::Left, span: postfix_span }, span); } Rule::slice_call => { @@ -2176,7 +2207,6 @@ fn parse_postfix<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { let end_expr = slice_inner.next().ok_or_else(|| CompilerError::Unsupported("missing slice end".to_string()))?; let start = Box::new(parse_expression(start_expr)?); let end = Box::new(parse_expression(end_expr)?); - let span = expr.span.join(&postfix_span); expr = Expr::new(ExprKind::Slice { source: Box::new(expr), start, end, span: postfix_span }, span); } Rule::append_call => { @@ -2189,14 +2219,12 @@ fn parse_postfix<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { CompilerError::Unsupported("append requires at least one expression".to_string()).with_span(&postfix_span) ); } - let span = expr.span.join(&postfix_span); expr = Expr::new(ExprKind::Append { source: Box::new(expr), args, span: postfix_span }, span); } Rule::tuple_index => { let mut index_inner = postfix.into_inner(); let index_pair = index_inner.next().ok_or_else(|| CompilerError::Unsupported("missing tuple index".to_string()))?; let index_expr = parse_expression(index_pair)?; - let span = expr.span.join(&postfix_span); if matches!(&expr.kind, ExprKind::Split { .. }) { return Err(CompilerError::Unsupported("split() results must be accessed with .0 or .1".to_string()) .with_span(&postfix_span)); @@ -2208,7 +2236,6 @@ fn parse_postfix<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { ".length" => UnarySuffixKind::Length, other => return Err(CompilerError::Unsupported(format!("unknown unary suffix '{other}'"))), }; - let span = expr.span.join(&postfix_span); expr = Expr::new(ExprKind::UnarySuffix { source: Box::new(expr), kind, span: postfix_span }, span); } Rule::tuple_field_access => { @@ -2216,7 +2243,6 @@ fn parse_postfix<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { let index = raw .parse::() .map_err(|_| CompilerError::Unsupported(format!("invalid tuple field index '{raw}'")).with_span(&postfix_span))?; - let span = expr.span.join(&postfix_span); if let ExprKind::Split { source, index: split_index, span: split_span, .. } = &expr.kind { let part = match index { 0 => SplitPart::Left, @@ -2246,7 +2272,6 @@ fn parse_postfix<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { let field_pair = postfix.into_inner().next().ok_or_else(|| CompilerError::Unsupported("missing field access name".to_string()))?; let Identifier { name: field, span: field_span } = parse_identifier(field_pair)?; - let span = expr.span.join(&postfix_span); expr = Expr::new(ExprKind::FieldAccess { source: Box::new(expr), field, field_span }, span); } Rule::as_cast => { @@ -2254,13 +2279,13 @@ fn parse_postfix<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { let type_pair = cast_inner.next().ok_or_else(|| CompilerError::Unsupported("missing type after 'as'".to_string()))?; let type_span = Span::from(type_pair.as_span()); let type_ref = parse_type_name_pair(type_pair)?; - let span = expr.span.join(&postfix_span); expr = Expr::new(ExprKind::Call { name: as_cast_call_name(&type_ref), args: vec![expr], name_span: type_span }, span); } _ => { return Err(CompilerError::Unsupported("postfix operators are not supported".to_string())); } } + source_span = span; } Ok(expr) } @@ -2418,6 +2443,7 @@ fn parse_typed_array<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError let span = Span::from(pair.as_span()); let mut inner = pair.into_inner(); let type_pair = inner.next().ok_or_else(|| CompilerError::Unsupported("missing array literal type".to_string()))?; + let type_span = Span::from(type_pair.as_span()); let mut type_ref = parse_type_name_pair(type_pair)?; let mut values = Vec::new(); for expr_pair in inner { @@ -2434,7 +2460,7 @@ fn parse_typed_array<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError } ArrayDim::Fixed(_) => {} } - Ok(Expr::new(ExprKind::Array { type_ref, values }, span)) + Ok(Expr::new(ExprKind::Array { type_ref, values, type_span }, span)) } fn parse_function_call_parts<'i>(pair: Pair<'i, Rule>) -> Result<(Identifier<'i>, Vec>), CompilerError> { @@ -2511,7 +2537,7 @@ fn parse_cast<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { if matches!(type_ref.array_size(), Some(ArrayDim::Inferred)) { type_ref.array_dims[0] = ArrayDim::Fixed(values.len()); } - return Ok(Expr::new(ExprKind::Array { type_ref, values }, span)); + return Ok(Expr::new(ExprKind::Array { type_ref, values, type_span }, span)); } if let Some(expected_len) = cast_type.base.fixed_byte_sequence_len() { if values.len() != expected_len { @@ -2525,6 +2551,7 @@ fn parse_cast<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { ExprKind::Array { type_ref: TypeRef { base: TypeBase::Byte, array_dims: vec![ArrayDim::Fixed(expected_len)] }, values, + type_span: Span::default(), }, byte_span, ); @@ -2710,14 +2737,17 @@ where { let mut inner = pair.into_inner(); let first = inner.next().ok_or_else(|| CompilerError::Unsupported("missing infix operand".to_string()))?; + let mut source_span = Span::from(first.as_span()); let mut expr = parse_operand(first)?; while let Some(op_pair) = inner.next() { let rhs = inner.next().ok_or_else(|| CompilerError::Unsupported("missing infix rhs".to_string()))?; let op = map_op(op_pair)?; + let rhs_span = Span::from(rhs.as_span()); let rhs_expr = parse_operand(rhs)?; - let span = expr.span.join(&rhs_expr.span); + let span = source_span.join(&rhs_span); expr = Expr::new(ExprKind::Binary { op, left: Box::new(expr), right: Box::new(rhs_expr) }, span); + source_span = span; } Ok(expr) diff --git a/silverscript-lang/src/ast/visit.rs b/silverscript-lang/src/ast/visit.rs index 0120c689..31d52b6e 100644 --- a/silverscript-lang/src/ast/visit.rs +++ b/silverscript-lang/src/ast/visit.rs @@ -1,12 +1,23 @@ use super::{ ConstantAst, ContractAst, ContractFieldAst, Expr, ExprKind, FunctionAst, FunctionAttributeArgAst, FunctionAttributeAst, ParamAst, - Statement, StructBindingAst, + Statement, StructAst, StructBindingAst, StructFieldAst, TypeBase, TypeRef, }; use crate::span::Span; +fn visit_type_ref<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, type_ref: &TypeRef, span: Span<'i>) { + visitor.visit_type(type_ref, span); +} + +fn visit_struct_type_name<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, name: &str, span: Span<'i>) { + let type_ref = TypeRef { base: TypeBase::Custom(name.to_string()), array_dims: Vec::new() }; + visitor.visit_type(&type_ref, span); +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NameKind { Contract, + Struct, + StructField, ContractField, Constant, Function, @@ -25,6 +36,14 @@ pub enum NameKind { pub trait AstVisitorMut<'i> { /// Visits a classified name together with its exact source span. fn visit_name(&mut self, _name: &mut String, _kind: NameKind, _span: Span<'i>) {} + /// Visits a classified type. For parsed ASTs, its base type name starts at + /// `span.start()`. + fn visit_type(&mut self, _type_ref: &TypeRef, _span: Span<'i>) {} + /// Visits a stored source span without classifying what it belongs to. + /// + /// The span is mutable so visitors can relocate or reset every span reached + /// by the walker, for example after embedding a parsed fragment at a source + /// offset. Synthetic AST nodes may carry an empty `Span::default()` value. fn visit_span(&mut self, _span: &mut Span<'i>) {} fn visit_contract(&mut self, contract: &mut ContractAst<'i>) { @@ -35,6 +54,14 @@ pub trait AstVisitorMut<'i> { walk_contract_field_mut(self, field); } + fn visit_struct(&mut self, item: &mut StructAst<'i>) { + walk_struct_mut(self, item); + } + + fn visit_struct_field(&mut self, field: &mut StructFieldAst<'i>) { + walk_struct_field_mut(self, field); + } + fn visit_constant(&mut self, constant: &mut ConstantAst<'i>) { walk_constant_mut(self, constant); } @@ -83,6 +110,9 @@ pub fn walk_contract_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, con for param in &mut contract.params { visitor.visit_param(param); } + for item in &mut contract.structs { + visitor.visit_struct(item); + } for field in &mut contract.fields { visitor.visit_contract_field(field); } @@ -94,7 +124,25 @@ pub fn walk_contract_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, con } } +pub fn walk_struct_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, item: &mut StructAst<'i>) { + visitor.visit_name(&mut item.name, NameKind::Struct, item.name_span); + visitor.visit_span(&mut item.span); + visitor.visit_span(&mut item.name_span); + for field in &mut item.fields { + visitor.visit_struct_field(field); + } +} + +pub fn walk_struct_field_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, field: &mut StructFieldAst<'i>) { + visit_type_ref(visitor, &field.type_ref, field.type_span); + visitor.visit_name(&mut field.name, NameKind::StructField, field.name_span); + visitor.visit_span(&mut field.span); + visitor.visit_span(&mut field.type_span); + visitor.visit_span(&mut field.name_span); +} + pub fn walk_contract_field_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, field: &mut ContractFieldAst<'i>) { + visit_type_ref(visitor, &field.type_ref, field.type_span); visitor.visit_name(&mut field.name, NameKind::ContractField, field.name_span); visitor.visit_span(&mut field.span); visitor.visit_span(&mut field.type_span); @@ -103,6 +151,7 @@ pub fn walk_contract_field_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut } pub fn walk_constant_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, constant: &mut ConstantAst<'i>) { + visit_type_ref(visitor, &constant.type_ref, constant.type_span); visitor.visit_name(&mut constant.name, NameKind::Constant, constant.name_span); visitor.visit_span(&mut constant.span); visitor.visit_span(&mut constant.type_span); @@ -115,6 +164,11 @@ pub fn walk_function_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, fun visitor.visit_span(&mut function.span); visitor.visit_span(&mut function.name_span); visitor.visit_span(&mut function.body_span); + for (type_ref, span) in + function.return_types.iter().zip(function.return_type_spans.iter().copied().chain(std::iter::repeat(Span::default()))) + { + visit_type_ref(visitor, type_ref, span); + } for span in &mut function.return_type_spans { visitor.visit_span(span); } @@ -131,7 +185,9 @@ pub fn walk_function_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, fun pub fn walk_function_attribute_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, attribute: &mut FunctionAttributeAst<'i>) { visitor.visit_span(&mut attribute.span); - for (segment, span) in attribute.path.iter_mut().zip(attribute.path_spans.iter().copied()) { + for (segment, span) in + attribute.path.iter_mut().zip(attribute.path_spans.iter().copied().chain(std::iter::repeat(Span::default()))) + { visitor.visit_name(segment, NameKind::AttributePathSegment, span); } for span in &mut attribute.path_spans { @@ -154,6 +210,7 @@ pub fn walk_param_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, param: } fn walk_param_with_kind_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, param: &mut ParamAst<'i>, kind: NameKind) { + visit_type_ref(visitor, ¶m.type_ref, param.type_span); visitor.visit_name(&mut param.name, kind, param.name_span); visitor.visit_span(&mut param.span); visitor.visit_span(&mut param.type_span); @@ -161,6 +218,7 @@ fn walk_param_with_kind_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, } pub fn walk_state_binding_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, binding: &mut StructBindingAst<'i>) { + visit_type_ref(visitor, &binding.type_ref, binding.type_span); visitor.visit_name(&mut binding.field_name, NameKind::StateField, binding.field_span); visitor.visit_name(&mut binding.name, NameKind::StateBinding, binding.name_span); visitor.visit_span(&mut binding.span); @@ -171,7 +229,8 @@ pub fn walk_state_binding_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V pub fn walk_statement_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, statement: &mut Statement<'i>) { match statement { - Statement::VariableDefinition { name, expr, span, type_span, modifier_spans, name_span, .. } => { + Statement::VariableDefinition { type_ref, name, expr, span, type_span, modifier_spans, name_span, .. } => { + visit_type_ref(visitor, type_ref, *type_span); visitor.visit_name(name, NameKind::LocalBinding, *name_span); visitor.visit_span(span); visitor.visit_span(type_span); @@ -185,7 +244,9 @@ pub fn walk_statement_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, st } Statement::TupleAssignment { left_name, + left_type_ref, right_name, + right_type_ref, expr, span, left_type_span, @@ -194,6 +255,8 @@ pub fn walk_statement_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, st right_name_span, .. } => { + visit_type_ref(visitor, left_type_ref, *left_type_span); + visit_type_ref(visitor, right_type_ref, *right_type_span); visitor.visit_name(left_name, NameKind::LocalBinding, *left_name_span); visitor.visit_name(right_name, NameKind::LocalBinding, *right_name_span); visitor.visit_span(span); @@ -222,9 +285,11 @@ pub fn walk_statement_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, st visitor.visit_expr(arg); } } - Statement::StateFunctionCallAssign { target_struct: _, bindings, name, args, span, name_span } => { + Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, target_struct_span, name_span } => { + visit_struct_type_name(visitor, target_struct, *target_struct_span); visitor.visit_name(name, NameKind::CallTarget, *name_span); visitor.visit_span(span); + visitor.visit_span(target_struct_span); visitor.visit_span(name_span); for binding in bindings { visitor.visit_state_binding(binding); @@ -233,8 +298,10 @@ pub fn walk_statement_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, st visitor.visit_expr(arg); } } - Statement::StructDestructure { bindings, expr, span, .. } => { + Statement::StructDestructure { struct_name, bindings, expr, span, struct_name_span } => { + visit_struct_type_name(visitor, struct_name, *struct_name_span); visitor.visit_span(span); + visitor.visit_span(struct_name_span); for binding in bindings { visitor.visit_state_binding(binding); } @@ -317,7 +384,9 @@ pub fn walk_expr_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, expr: & visitor.visit_span(&mut expr.span); match &mut expr.kind { ExprKind::Identifier(name) => visitor.visit_name(name, NameKind::IdentifierExpr, expr_span), - ExprKind::Array { values: items, .. } => { + ExprKind::Array { type_ref, values: items, type_span } => { + visit_type_ref(visitor, type_ref, *type_span); + visitor.visit_span(type_span); for item in items { visitor.visit_expr(item); } @@ -371,7 +440,8 @@ pub fn walk_expr_mut<'i, V: AstVisitorMut<'i> + ?Sized>(visitor: &mut V, expr: & visitor.visit_span(field_span); visitor.visit_expr(index); } - ExprKind::StructLiteral { fields, name_span, .. } => { + ExprKind::StructLiteral { name, fields, name_span } => { + visit_struct_type_name(visitor, name, *name_span); visitor.visit_span(name_span); for field in fields { visitor.visit_name(&mut field.name, NameKind::StateField, field.name_span); diff --git a/silverscript-lang/src/compiler/array_append.rs b/silverscript-lang/src/compiler/array_append.rs index 9880c272..2dce3a32 100644 --- a/silverscript-lang/src/compiler/array_append.rs +++ b/silverscript-lang/src/compiler/array_append.rs @@ -146,7 +146,7 @@ fn lower_statement<'i>( name_span: *name_span, }) } - Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, name_span } => { + Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, target_struct_span, name_span } => { let lowered_args = args.iter().map(|arg| lower_expr(arg, types, constants, functions)).collect::, _>>()?; for binding in bindings { types.insert(binding.name.clone(), binding.type_ref.clone()); @@ -157,10 +157,11 @@ fn lower_statement<'i>( name: name.clone(), args: lowered_args, span: *span, + target_struct_span: *target_struct_span, name_span: *name_span, }) } - Statement::StructDestructure { struct_name, bindings, expr, span } => { + Statement::StructDestructure { struct_name, bindings, expr, span, struct_name_span } => { let lowered_expr = lower_expr(expr, types, constants, functions)?; for binding in bindings { types.insert(binding.name.clone(), binding.type_ref.clone()); @@ -170,6 +171,7 @@ fn lower_statement<'i>( bindings: bindings.clone(), expr: lowered_expr, span: *span, + struct_name_span: *struct_name_span, }) } Statement::Assign { name, expr, span, name_span } => Ok(Statement::Assign { @@ -279,6 +281,7 @@ fn lower_expr<'i>( .iter() .map(|arg| lower_expr(arg, types, constants, functions)) .collect::, _>>()?, + type_span: span::Span::default(), }, span::Span::default(), )), @@ -286,10 +289,11 @@ fn lower_expr<'i>( span, )) } - ExprKind::Array { type_ref, values } => Ok(Expr::new( + ExprKind::Array { type_ref, values, type_span } => Ok(Expr::new( ExprKind::Array { type_ref: type_ref.clone(), values: values.iter().map(|value| lower_expr(value, types, constants, functions)).collect::, _>>()?, + type_span: *type_span, }, span, )), @@ -423,7 +427,7 @@ mod tests { let ExprKind::Binary { op: BinaryOp::Add, right, .. } = &expr.kind else { panic!("append should lower to addition"); }; - let ExprKind::Array { type_ref, values } = &right.kind else { + let ExprKind::Array { type_ref, values, .. } = &right.kind else { panic!("append arguments should lower to an array"); }; diff --git a/silverscript-lang/src/compiler/compile/const_eval.rs b/silverscript-lang/src/compiler/compile/const_eval.rs index ffc939ef..402aedc4 100644 --- a/silverscript-lang/src/compiler/compile/const_eval.rs +++ b/silverscript-lang/src/compiler/compile/const_eval.rs @@ -136,12 +136,12 @@ pub(crate) fn resolve_constant_references<'i>( }, span, )), - ExprKind::Array { type_ref, values } => { + ExprKind::Array { type_ref, values, type_span } => { let mut resolved = Vec::with_capacity(values.len()); for value in values { resolved.push(resolve_constant_references(value, constants, visiting)?); } - Ok(Expr::new(ExprKind::Array { type_ref: type_ref.clone(), values: resolved }, span)) + Ok(Expr::new(ExprKind::Array { type_ref: type_ref.clone(), values: resolved, type_span }, span)) } ExprKind::StructLiteral { name, fields, name_span } => { let mut resolved_fields = Vec::with_capacity(fields.len()); diff --git a/silverscript-lang/src/compiler/compile/expression.rs b/silverscript-lang/src/compiler/compile/expression.rs index 795ee44c..4b79bee5 100644 --- a/silverscript-lang/src/compiler/compile/expression.rs +++ b/silverscript-lang/src/compiler/compile/expression.rs @@ -53,7 +53,7 @@ fn compile_expr_with_context<'i>( ExprKind::Int(value) | ExprKind::Temporal(value) => compile_int_expr(ctx, *value, expected_type), ExprKind::Bool(value) => compile_bool_expr(ctx, *value), ExprKind::Byte(byte) => compile_byte_expr(ctx, *byte), - ExprKind::Array { type_ref, values } => compile_array_expr(ctx, values, Some(type_ref)), + ExprKind::Array { type_ref, values, .. } => compile_array_expr(ctx, values, Some(type_ref)), ExprKind::StructLiteral { .. } => compile_state_object_expr(), ExprKind::FieldAccess { .. } => compile_field_access_expr(), ExprKind::String(value) => compile_string_expr(ctx, value), diff --git a/silverscript-lang/src/compiler/debug_recording.rs b/silverscript-lang/src/compiler/debug_recording.rs index cf6ffa79..1f3f5e09 100644 --- a/silverscript-lang/src/compiler/debug_recording.rs +++ b/silverscript-lang/src/compiler/debug_recording.rs @@ -217,9 +217,11 @@ impl<'i> DebugRecorder<'i> { let span = expr.span; let kind = match expr.kind { ExprKind::Identifier(name) => ExprKind::Identifier(self.visible_name(&name)), - ExprKind::Array { type_ref, values } => { - ExprKind::Array { type_ref, values: values.into_iter().map(|value| self.rewrite_debug_expr(value)).collect() } - } + ExprKind::Array { type_ref, values, type_span } => ExprKind::Array { + type_ref, + values: values.into_iter().map(|value| self.rewrite_debug_expr(value)).collect(), + type_span, + }, ExprKind::Call { name, args, name_span } => { ExprKind::Call { name, args: args.into_iter().map(|arg| self.rewrite_debug_expr(arg)).collect(), name_span } } @@ -522,12 +524,13 @@ fn rewrite_debug_expr_with_function<'i>( let kind = match expr.kind { ExprKind::Identifier(name) => ExprKind::Identifier(visible_name(&name)), - ExprKind::Array { type_ref, values } => ExprKind::Array { + ExprKind::Array { type_ref, values, type_span } => ExprKind::Array { type_ref, values: values .into_iter() .map(|value| rewrite_debug_expr_with_function(value, function_name, visible_names_by_function)) .collect(), + type_span, }, ExprKind::Call { name, args, name_span } => ExprKind::Call { name, diff --git a/silverscript-lang/src/compiler/inline_functions.rs b/silverscript-lang/src/compiler/inline_functions.rs index 36c22695..5e9a1c3b 100644 --- a/silverscript-lang/src/compiler/inline_functions.rs +++ b/silverscript-lang/src/compiler/inline_functions.rs @@ -205,7 +205,7 @@ impl<'i, 'd> Inliner<'i, 'd> { ); } } - Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, name_span } => { + Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, target_struct_span, name_span } => { let (prelude, renamed_args) = self.lower_exprs(args, scope, visited_functions)?; lowered.extend(prelude); let renamed_bindings = bindings @@ -223,11 +223,12 @@ impl<'i, 'd> Inliner<'i, 'd> { name: name.clone(), args: renamed_args, span: *span, + target_struct_span: *target_struct_span, name_span: *name_span, }, ); } - Statement::StructDestructure { struct_name, bindings, expr, span } => { + Statement::StructDestructure { struct_name, bindings, expr, span, struct_name_span } => { let (prelude, renamed_expr) = self.lower_expr(expr, scope, visited_functions)?; lowered.extend(prelude); let renamed_bindings = bindings @@ -244,6 +245,7 @@ impl<'i, 'd> Inliner<'i, 'd> { bindings: renamed_bindings, expr: renamed_expr, span: *span, + struct_name_span: *struct_name_span, }, ); } @@ -506,9 +508,9 @@ impl<'i, 'd> Inliner<'i, 'd> { ExprKind::String(value) => Ok((Vec::new(), Expr::new(ExprKind::String(value.clone()), span))), ExprKind::DateLiteral(value) => Ok((Vec::new(), Expr::new(ExprKind::DateLiteral(*value), span))), ExprKind::Identifier(name) => Ok((Vec::new(), Expr::new(ExprKind::Identifier(self.rename_name(name, scope)), span))), - ExprKind::Array { type_ref, values } => { + ExprKind::Array { type_ref, values, type_span } => { let (prelude, values) = self.lower_exprs(values, scope, visited_functions)?; - Ok((prelude, Expr::new(ExprKind::Array { type_ref: type_ref.clone(), values }, span))) + Ok((prelude, Expr::new(ExprKind::Array { type_ref: type_ref.clone(), values, type_span: *type_span }, span))) } ExprKind::Call { name, args, name_span } => { let (mut prelude, args) = self.lower_exprs(args, scope, visited_functions)?; diff --git a/silverscript-lang/src/compiler/locals.rs b/silverscript-lang/src/compiler/locals.rs index b7b5ca39..257ae17a 100644 --- a/silverscript-lang/src/compiler/locals.rs +++ b/silverscript-lang/src/compiler/locals.rs @@ -104,7 +104,7 @@ fn lower_statements<'i>( name_span: *name_span, }); } - Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, name_span } => { + Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, target_struct_span, name_span } => { for binding in bindings { local_aliases.remove(&binding.name); } @@ -114,10 +114,11 @@ fn lower_statements<'i>( name: name.clone(), args: args.iter().map(|arg| substitute_expr(arg, &local_aliases)).collect::, _>>()?, span: *span, + target_struct_span: *target_struct_span, name_span: *name_span, }); } - Statement::StructDestructure { struct_name, bindings, expr, span } => { + Statement::StructDestructure { struct_name, bindings, expr, span, struct_name_span } => { for binding in bindings { local_aliases.remove(&binding.name); } @@ -126,6 +127,7 @@ fn lower_statements<'i>( bindings: bindings.clone(), expr: substitute_expr(expr, &local_aliases)?, span: *span, + struct_name_span: *struct_name_span, }); } Statement::Assign { name, expr, span, name_span } => { @@ -215,11 +217,11 @@ fn coerce_expr_for_declared_type<'i>(expr: Expr<'i>, type_ref: &TypeRef) -> Resu return Ok(Expr::new(ExprKind::Byte(byte_value), expr.span)); } if let Some(element_type) = type_ref.array_element_type() - && let ExprKind::Array { values, .. } = expr.kind + && let ExprKind::Array { values, type_span, .. } = expr.kind { let values = values.into_iter().map(|value| coerce_expr_for_declared_type(value, &element_type)).collect::, _>>()?; - return Ok(Expr::new(ExprKind::Array { type_ref: type_ref.clone(), values }, expr.span)); + return Ok(Expr::new(ExprKind::Array { type_ref: type_ref.clone(), values, type_span }, expr.span)); } Ok(expr) } @@ -245,10 +247,11 @@ fn substitute_expr<'i>(expr: &Expr<'i>, aliases: &HashMap>) -> }, span, ), - ExprKind::Array { type_ref, values } => Expr::new( + ExprKind::Array { type_ref, values, type_span } => Expr::new( ExprKind::Array { type_ref, values: values.iter().map(|value| substitute_expr(value, aliases)).collect::, _>>()?, + type_span, }, span, ), diff --git a/silverscript-lang/src/compiler/read_input_state.rs b/silverscript-lang/src/compiler/read_input_state.rs index 95d1edcf..11726727 100644 --- a/silverscript-lang/src/compiler/read_input_state.rs +++ b/silverscript-lang/src/compiler/read_input_state.rs @@ -156,21 +156,23 @@ fn lower_statement<'i>(statement: &Statement<'i>, context: &mut LoweringContext) span: *span, name_span: *name_span, }, - Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, name_span } => { + Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, target_struct_span, name_span } => { Statement::StateFunctionCallAssign { target_struct: target_struct.clone(), bindings: bindings.clone(), name: name.clone(), args: lower_call_args(args, &mut prefix, context), span: *span, + target_struct_span: *target_struct_span, name_span: *name_span, } } - Statement::StructDestructure { struct_name, bindings, expr, span } => Statement::StructDestructure { + Statement::StructDestructure { struct_name, bindings, expr, span, struct_name_span } => Statement::StructDestructure { struct_name: struct_name.clone(), bindings: bindings.clone(), expr: lower_expr(expr, &mut prefix, context), span: *span, + struct_name_span: *struct_name_span, }, Statement::Assign { name, expr, span, name_span } => { Statement::Assign { name: name.clone(), expr: lower_expr(expr, &mut prefix, context), span: *span, name_span: *name_span } @@ -255,9 +257,10 @@ fn lower_call_arg<'i>(arg: &Expr<'i>, prefix: &mut Vec>, context: fn lower_expr<'i>(expr: &Expr<'i>, prefix: &mut Vec>, context: &mut LoweringContext) -> Expr<'i> { let kind = match &expr.kind { - ExprKind::Array { type_ref, values } => ExprKind::Array { + ExprKind::Array { type_ref, values, type_span } => ExprKind::Array { type_ref: type_ref.clone(), values: values.iter().map(|value| lower_expr(value, prefix, context)).collect(), + type_span: *type_span, }, ExprKind::Call { name, args, name_span } => { ExprKind::Call { name: name.clone(), args: lower_call_args(args, prefix, context), name_span: *name_span } diff --git a/silverscript-lang/src/compiler/structs/declaration.rs b/silverscript-lang/src/compiler/structs/declaration.rs index 246c724f..8259b8d8 100644 --- a/silverscript-lang/src/compiler/structs/declaration.rs +++ b/silverscript-lang/src/compiler/structs/declaration.rs @@ -37,6 +37,7 @@ pub(super) fn lower_variable_definition<'i>( name: builtin_name.clone(), args: args.iter().map(|arg| lower_scalar_expr(arg, scope, lowerer)).collect::, _>>()?, span: *span, + target_struct_span: *type_span, name_span: *name_span, }]); } diff --git a/silverscript-lang/src/compiler/structs/scalar_expr.rs b/silverscript-lang/src/compiler/structs/scalar_expr.rs index 816d455d..46994b9b 100644 --- a/silverscript-lang/src/compiler/structs/scalar_expr.rs +++ b/silverscript-lang/src/compiler/structs/scalar_expr.rs @@ -125,10 +125,11 @@ pub(super) fn lower_scalar_expr<'i>( }, span, )), - ExprKind::Array { type_ref, values } => Ok(Expr::new( + ExprKind::Array { type_ref, values, type_span } => Ok(Expr::new( ExprKind::Array { type_ref: type_ref.clone(), values: values.iter().map(|value| lower_scalar_expr(value, scope, lowerer)).collect::, _>>()?, + type_span: *type_span, }, span, )), diff --git a/silverscript-lang/src/compiler/structs/statement.rs b/silverscript-lang/src/compiler/structs/statement.rs index 8c204a42..3ba13e80 100644 --- a/silverscript-lang/src/compiler/structs/statement.rs +++ b/silverscript-lang/src/compiler/structs/statement.rs @@ -68,7 +68,7 @@ pub(super) fn lower_statements<'i>( name_span: *name_span, }); } - Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, name_span } => { + Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, target_struct_span, name_span } => { for binding in bindings { scope.declare(binding.name.clone(), binding.type_ref.clone()); } @@ -78,10 +78,11 @@ pub(super) fn lower_statements<'i>( name: name.clone(), args: args.iter().map(|arg| lower_scalar_expr(arg, scope, lowerer)).collect::, _>>()?, span: *span, + target_struct_span: *target_struct_span, name_span: *name_span, }); } - Statement::StructDestructure { struct_name, bindings, expr, span } => { + Statement::StructDestructure { struct_name, bindings, expr, span, .. } => { for binding in bindings { scope.declare(binding.name.clone(), binding.type_ref.clone()); } diff --git a/silverscript-lang/src/compiler/ternary.rs b/silverscript-lang/src/compiler/ternary.rs index 51e2b98d..56d97fdd 100644 --- a/silverscript-lang/src/compiler/ternary.rs +++ b/silverscript-lang/src/compiler/ternary.rs @@ -131,7 +131,7 @@ impl<'a, 'i> TernaryLowerer<'a, 'i> { name_span: *name_span, }); } - Statement::StateFunctionCallAssign { bindings, name, args, span, name_span, target_struct } => { + Statement::StateFunctionCallAssign { bindings, name, args, span, target_struct_span, name_span, target_struct } => { let (prelude, args) = self.lower_exprs(args, types)?; lowered.extend(prelude); for binding in bindings { @@ -142,11 +142,12 @@ impl<'a, 'i> TernaryLowerer<'a, 'i> { name: name.clone(), args, span: *span, + target_struct_span: *target_struct_span, name_span: *name_span, target_struct: target_struct.clone(), }); } - Statement::StructDestructure { struct_name, bindings, expr, span } => { + Statement::StructDestructure { struct_name, bindings, expr, span, struct_name_span } => { let (prelude, expr) = self.lower_expr(expr, None, types)?; lowered.extend(prelude); for binding in bindings { @@ -157,6 +158,7 @@ impl<'a, 'i> TernaryLowerer<'a, 'i> { bindings: bindings.clone(), expr, span: *span, + struct_name_span: *struct_name_span, }); } Statement::FunctionCall { name, args, span, name_span } => { @@ -336,7 +338,7 @@ impl<'a, 'i> TernaryLowerer<'a, 'i> { Ok((prelude, Expr::new(ExprKind::Identifier(result_name), span))) } - ExprKind::Array { type_ref, values } => { + ExprKind::Array { type_ref, values, type_span } => { let element_type = expected.or(Some(type_ref)).and_then(TypeRef::array_element_type); let mut prelude = Vec::new(); let mut lowered_values = Vec::with_capacity(values.len()); @@ -345,7 +347,10 @@ impl<'a, 'i> TernaryLowerer<'a, 'i> { prelude.extend(more_prelude); lowered_values.push(value); } - Ok((prelude, Expr::new(ExprKind::Array { type_ref: type_ref.clone(), values: lowered_values }, span))) + Ok(( + prelude, + Expr::new(ExprKind::Array { type_ref: type_ref.clone(), values: lowered_values, type_span: *type_span }, span), + )) } ExprKind::Call { name, args, name_span } => { let (prelude, args) = self.lower_exprs(args, types)?; @@ -482,7 +487,7 @@ impl<'a, 'i> TernaryLowerer<'a, 'i> { } }; let values = (0..len).map(|_| self.default_expr(&element_type, span)).collect::, CompilerError>>()?; - return Ok(Expr::new(ExprKind::Array { type_ref: type_ref.clone(), values }, span)); + return Ok(Expr::new(ExprKind::Array { type_ref: type_ref.clone(), values, type_span: span::Span::default() }, span)); } let kind = match &type_ref.base { @@ -496,7 +501,11 @@ impl<'a, 'i> TernaryLowerer<'a, 'i> { .base .fixed_byte_sequence_len() .ok_or_else(|| CompilerError::Unsupported(format!("cannot create default {}", type_ref.type_name())))?; - ExprKind::Array { type_ref: type_ref.clone(), values: (0..len).map(|_| Expr::new(ExprKind::Byte(0), span)).collect() } + ExprKind::Array { + type_ref: type_ref.clone(), + values: (0..len).map(|_| Expr::new(ExprKind::Byte(0), span)).collect(), + type_span: span::Span::default(), + } } TypeBase::Custom(name) => { let item = self diff --git a/silverscript-lang/src/compiler/type_check.rs b/silverscript-lang/src/compiler/type_check.rs index 3707c0ed..90101b1e 100644 --- a/silverscript-lang/src/compiler/type_check.rs +++ b/silverscript-lang/src/compiler/type_check.rs @@ -46,7 +46,7 @@ pub(super) fn check_expr<'i>( ExprKind::Byte(_) => scalar_type(TypeBase::Byte), ExprKind::String(_) => scalar_type(TypeBase::String), ExprKind::Identifier(name) => ctx.types.get(name).cloned().ok_or_else(|| CompilerError::UndefinedIdentifier(name.clone()))?, - ExprKind::Array { type_ref, values } => check_typed_array_literal(values, type_ref, expected, ctx)?, + ExprKind::Array { type_ref, values, .. } => check_typed_array_literal(values, type_ref, expected, ctx)?, ExprKind::Call { name, args, .. } => check_call(name, args, expected, ctx)? .ok_or_else(|| CompilerError::Unsupported(format!("function '{name}' does not return a value")))?, ExprKind::New { name, args, .. } => check_constructor(name, args, ctx)?, diff --git a/silverscript-lang/src/compiler/validate_output_state.rs b/silverscript-lang/src/compiler/validate_output_state.rs index 07230cf6..4c0f3323 100644 --- a/silverscript-lang/src/compiler/validate_output_state.rs +++ b/silverscript-lang/src/compiler/validate_output_state.rs @@ -227,7 +227,7 @@ fn lower_statement<'i>( name_span: *name_span, }]) } - Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, name_span } => { + Statement::StateFunctionCallAssign { target_struct, bindings, name, args, span, target_struct_span, name_span } => { for binding in bindings { scope.vars.insert(binding.name.clone(), binding.type_ref.clone()); } @@ -237,10 +237,11 @@ fn lower_statement<'i>( name: name.clone(), args: args.clone(), span: *span, + target_struct_span: *target_struct_span, name_span: *name_span, }]) } - Statement::StructDestructure { struct_name, bindings, expr, span } => { + Statement::StructDestructure { struct_name, bindings, expr, span, struct_name_span } => { for binding in bindings { scope.vars.insert(binding.name.clone(), binding.type_ref.clone()); } @@ -249,6 +250,7 @@ fn lower_statement<'i>( bindings: bindings.clone(), expr: expr.clone(), span: *span, + struct_name_span: *struct_name_span, }]) } Statement::Block { body, span } => { diff --git a/silverscript-lang/src/parser.rs b/silverscript-lang/src/parser.rs index 007d3eb5..3508d8ab 100644 --- a/silverscript-lang/src/parser.rs +++ b/silverscript-lang/src/parser.rs @@ -18,6 +18,11 @@ pub fn parse_function(input: &str) -> Result, ParseDiagnostic> { SilverScriptParser::parse(Rule::function_source, input).map_err(|err| crate::diagnostic::interpret_parse_error(input, &err)) } +pub fn parse_statement(input: &str) -> Result, ParseDiagnostic> { + pest::set_error_detail(true); + SilverScriptParser::parse(Rule::statement_source, input).map_err(|err| crate::diagnostic::interpret_parse_error(input, &err)) +} + pub fn parse_expression(input: &str) -> Result, ParseDiagnostic> { pest::set_error_detail(true); SilverScriptParser::parse(Rule::expression, input).map_err(|err| crate::diagnostic::interpret_parse_error(input, &err)) diff --git a/silverscript-lang/src/silverscript.pest b/silverscript-lang/src/silverscript.pest index f4b63cc2..d4283316 100644 --- a/silverscript-lang/src/silverscript.pest +++ b/silverscript-lang/src/silverscript.pest @@ -1,5 +1,6 @@ source_file = { SOI ~ pragma_directive? ~ contract_definition ~ EOI } function_source = { SOI ~ function_definition ~ EOI } +statement_source = { SOI ~ statement ~ EOI } pragma_directive = { "pragma" ~ pragma_name ~ pragma_value ~ ";" } pragma_name = { "silverscript" } diff --git a/silverscript-lang/tests/ast_format_tests.rs b/silverscript-lang/tests/ast_format_tests.rs index fc76c58a..9776135b 100644 --- a/silverscript-lang/tests/ast_format_tests.rs +++ b/silverscript-lang/tests/ast_format_tests.rs @@ -1,5 +1,5 @@ -use silverscript_lang::ast::{format_contract_ast, parse_contract_ast}; -use silverscript_lang::compiler::{CompileOptions, compile_contract}; +use silverscript_lang::ast::{Expr, Span, format_contract_ast, parse_contract_ast}; +use silverscript_lang::compiler::{CompileOptions, compile_contract, compile_contract_ast, sil_abi_artifact_from_compiled}; fn assert_compiled_formatted_contract_preserves_ast(source: &str, options: CompileOptions) { let ast = parse_contract_ast(source).expect("parse succeeds"); @@ -173,3 +173,96 @@ fn compiled_formatted_contract_preserves_exact_ast_with_state_and_return() { CompileOptions { allow_entrypoint_return: true, ..CompileOptions::default() }, ); } + +#[test] +fn formats_function_attributes_and_preserves_compilation() { + let source = r#"contract Decls(int max_outs) { + #[covenant(binding = auth, from = 1, to = max_outs, mode = verification)] + function spend(int amount) { + require(amount >= 0); + } +} +"#; + + let ast = parse_contract_ast(source).expect("parse succeeds"); + let formatted = format_contract_ast(&ast); + let reparsed = parse_contract_ast(&formatted).expect("formatted attributes parse"); + + assert!(formatted.contains("#[covenant(binding = auth, from = 1, to = max_outs, mode = verification)]")); + assert_eq!( + serde_json::to_value(&reparsed).expect("serialize reparsed ast"), + serde_json::to_value(&ast).expect("serialize original ast") + ); + + let args = [Expr::int(3)]; + let from_source = compile_contract(source, &args, CompileOptions::default()).expect("source compiles"); + let direct = compile_contract_ast(&ast, &args, CompileOptions::default()).expect("direct AST compiles"); + let round_tripped = + compile_contract_ast(&reparsed, &args, CompileOptions::default()).expect("formatted and reparsed AST compiles"); + let from_source_artifact = sil_abi_artifact_from_compiled(&from_source, &args).expect("source artifact builds"); + let direct_artifact = sil_abi_artifact_from_compiled(&direct, &args).expect("direct AST artifact builds"); + let round_tripped_artifact = + sil_abi_artifact_from_compiled(&round_tripped, &args).expect("formatted and reparsed AST artifact builds"); + assert_eq!(from_source_artifact, direct_artifact); + assert_eq!(direct_artifact, round_tripped_artifact); +} + +#[test] +fn formats_no_arg_and_qualified_function_attributes() { + let source = r#"contract Attributes() { + #[covenant.delegate] + function delegate() { + require(true); + } + + #[covenant.allow(rule = manual_entrypoint_in_leader_contract)] + entry recover() { + require(true); + } +} +"#; + + let ast = parse_contract_ast(source).expect("parse succeeds"); + let formatted = format_contract_ast(&ast); + let reparsed = parse_contract_ast(&formatted).expect("formatted attributes parse"); + + assert!(formatted.contains("#[covenant.delegate]\n")); + assert!(formatted.contains("#[covenant.allow(rule = manual_entrypoint_in_leader_contract)]\n")); + assert_eq!( + serde_json::to_value(&reparsed).expect("serialize reparsed ast"), + serde_json::to_value(&ast).expect("serialize original ast") + ); +} + +#[test] +fn synthetic_items_preserve_ast_vector_order_after_formatting() { + let source = r#"contract Generated() { + int first = 1; + + entry spend() { + require(first == 1); + } +} +"#; + + let mut ast = parse_contract_ast(source).expect("parse succeeds"); + let mut synthetic_field = ast.fields[0].clone(); + synthetic_field.name = "second".to_string(); + synthetic_field.expr = Expr::int(2); + synthetic_field.span = Span::default(); + synthetic_field.type_span = Span::default(); + synthetic_field.name_span = Span::default(); + ast.fields.push(synthetic_field); + + let formatted = format_contract_ast(&ast); + let reparsed = parse_contract_ast(&formatted).expect("formatted mixed-source AST parses"); + assert_eq!(reparsed.fields.iter().map(|field| field.name.as_str()).collect::>(), vec!["first", "second"]); + + let direct = compile_contract_ast(&ast, &[], CompileOptions::default()).expect("direct mixed-source AST compiles"); + let round_tripped = + compile_contract_ast(&reparsed, &[], CompileOptions::default()).expect("formatted and reparsed mixed-source AST compiles"); + let direct_artifact = sil_abi_artifact_from_compiled(&direct, &[]).expect("direct mixed-source AST artifact builds"); + let round_tripped_artifact = + sil_abi_artifact_from_compiled(&round_tripped, &[]).expect("formatted and reparsed mixed-source AST artifact builds"); + assert_eq!(direct_artifact, round_tripped_artifact); +} diff --git a/silverscript-lang/tests/ast_spans_tests.rs b/silverscript-lang/tests/ast_spans_tests.rs index df99d89f..19d10637 100644 --- a/silverscript-lang/tests/ast_spans_tests.rs +++ b/silverscript-lang/tests/ast_spans_tests.rs @@ -1,5 +1,7 @@ -use silverscript_lang::ast::visit::{AstVisitorMut, NameKind, visit_function_mut}; -use silverscript_lang::ast::{ExprKind, Statement, parse_contract_ast, parse_function_ast}; +use silverscript_lang::ast::visit::{AstVisitorMut, NameKind, visit_contract_mut, visit_function_mut}; +use silverscript_lang::ast::{ + ExprKind, FunctionAst, Statement, TypeBase, parse_contract_ast, parse_expression_ast, parse_function_ast, parse_statement_ast, +}; use silverscript_lang::span::Span; fn assert_span_text(source: &str, actual: &str, expected: &str) { @@ -21,6 +23,26 @@ struct NameOccurrenceCollector { occurrences: Vec, } +struct SpanResetter; + +#[derive(Debug, PartialEq, Eq)] +struct TypeOccurrence { + type_name: String, + base_name: String, + span_prefix: String, +} + +#[derive(Default)] +struct TypeOccurrenceCollector { + occurrences: Vec, +} + +#[derive(Default)] +struct SyntheticMetadataCollector { + type_names: Vec<(String, bool)>, + attribute_path_segments: Vec<(String, bool)>, +} + impl<'i> AstVisitorMut<'i> for NameOccurrenceCollector { fn visit_name(&mut self, name: &mut String, kind: NameKind, span: Span<'i>) { self.occurrences.push(NameOccurrence { name: name.clone(), kind, source: span.as_str().to_string() }); @@ -63,6 +85,285 @@ fn parses_standalone_functions_and_visits_name_spans() { ); } +#[test] +fn parses_standalone_statements_with_original_source_spans() { + let source = " /* before */ value = (signed(value) + signed(step)) as byte; // after\n"; + let statement = parse_statement_ast(source).expect("standalone statement should parse"); + + let Statement::Assign { name, expr, span, name_span } = statement else { + panic!("expected an assignment"); + }; + assert_eq!(name, "value"); + assert_eq!(span.as_str(), "value = (signed(value) + signed(step)) as byte;"); + assert_eq!(name_span.as_str(), "value"); + assert_eq!(expr.span.as_str(), "(signed(value) + signed(step)) as byte"); + assert_eq!(span.get_input(), source); + assert_eq!(name_span.get_input(), source); + assert_eq!(expr.span.get_input(), source); +} + +#[test] +fn parses_supported_standalone_statement_shapes() { + let sources = [ + "int value = 1;", + "CounterState { value: int current } = readInputState(0);", + "{ int value = 1; value = 2; }", + "if (ready) { value = 1; } else value = 2;", + ]; + + for source in sources { + parse_statement_ast(source).unwrap_or_else(|err| panic!("`{source}` should parse as one statement: {err}")); + } +} + +#[test] +fn rejects_incomplete_or_multiple_standalone_statements() { + for source in ["", "value = 1", "value = 1; other = 2;", "value + 1"] { + assert!(parse_statement_ast(source).is_err(), "`{source}` must not parse as exactly one statement"); + } +} + +impl<'i> AstVisitorMut<'i> for SpanResetter { + fn visit_span(&mut self, span: &mut Span<'i>) { + *span = Span::default(); + } +} + +impl<'i> AstVisitorMut<'i> for TypeOccurrenceCollector { + fn visit_type(&mut self, type_ref: &silverscript_lang::ast::TypeRef, span: Span<'i>) { + let TypeBase::Custom(base_name) = &type_ref.base else { + panic!("fixture only contains custom type sites"); + }; + let span_prefix = span + .get_input() + .get(span.start()..span.start() + base_name.len()) + .expect("custom type name should fit within the source") + .to_string(); + self.occurrences.push(TypeOccurrence { type_name: type_ref.type_name(), base_name: base_name.to_string(), span_prefix }); + } + + fn visit_span(&mut self, span: &mut Span<'i>) { + *span = Span::default(); + } +} + +impl<'i> AstVisitorMut<'i> for SyntheticMetadataCollector { + fn visit_name(&mut self, name: &mut String, kind: NameKind, span: Span<'i>) { + if kind == NameKind::AttributePathSegment { + self.attribute_path_segments.push((name.clone(), span == Span::default())); + } + } + + fn visit_type(&mut self, type_ref: &silverscript_lang::ast::TypeRef, span: Span<'i>) { + self.type_names.push((type_ref.type_name(), span == Span::default())); + } +} + +#[test] +fn visits_deserialized_function_metadata_without_source_spans() { + let source = "#[covenant.singleton] function inspect() : (LeftResult, RightResult) {}"; + let function = parse_function_ast(source).expect("function metadata fixture should parse"); + let serialized = serde_json::to_string(&function).expect("function AST should serialize"); + let mut function: FunctionAst<'_> = serde_json::from_str(&serialized).expect("function AST should deserialize"); + let mut collector = SyntheticMetadataCollector::default(); + + visit_function_mut(&mut collector, &mut function); + + assert_eq!( + collector.type_names, + [("LeftResult", true), ("RightResult", true)].map(|(name, synthetic)| (name.to_string(), synthetic)) + ); + assert_eq!( + collector.attribute_path_segments, + [("covenant", true), ("singleton", true)].map(|(name, synthetic)| (name.to_string(), synthetic)) + ); +} + +#[test] +fn composed_expression_spans_remain_syntactically_complete() { + let sources = [ + "(signed(next_status) + signed(increment)) as byte", + "(value).field", + "(values)[index]", + "(values)[index].length", + "(value).length", + "(left + right) * factor", + "left + (right * factor)", + ]; + + for source in sources { + let expr = parse_expression_ast(source).unwrap_or_else(|err| panic!("`{source}` should parse: {err}")); + assert_eq!(expr.span.as_str(), source, "composite expression span should be valid and complete"); + } +} + +#[test] +fn redundant_parentheses_do_not_widen_identifier_name_spans() { + let mut expr = parse_expression_ast("((value))").expect("parenthesized identifier should parse"); + let mut collector = NameOccurrenceCollector::default(); + collector.visit_expr(&mut expr); + + assert_eq!(expr.span.as_str(), "value"); + assert_eq!( + collector.occurrences, + vec![NameOccurrence { name: "value".to_string(), kind: NameKind::IdentifierExpr, source: "value".to_string() }] + ); +} + +#[test] +fn exposes_exact_spans_for_struct_types_and_typed_arrays() { + let source = r#"function inspect() { + CounterState {value: int current} = readInputState(0); + CounterState {value: int copy} = current_state; + CounterState[] values = CounterState[] {CounterState {value: 1}}; + }"#; + let mut function = parse_function_ast(source).expect("standalone function should parse"); + + let Statement::StateFunctionCallAssign { target_struct, target_struct_span, .. } = &function.body[0] else { + panic!("expected a state function call assignment"); + }; + assert_eq!(target_struct, "CounterState"); + assert_eq!(target_struct_span.as_str(), "CounterState"); + + let Statement::StructDestructure { struct_name, struct_name_span, .. } = &function.body[1] else { + panic!("expected a struct destructure assignment"); + }; + assert_eq!(struct_name, "CounterState"); + assert_eq!(struct_name_span.as_str(), "CounterState"); + + let Statement::VariableDefinition { expr: Some(expr), .. } = &function.body[2] else { + panic!("expected a variable definition with an initializer"); + }; + let ExprKind::Array { type_span, .. } = &expr.kind else { + panic!("expected a typed array expression"); + }; + assert_eq!(type_span.as_str(), "CounterState[]"); + + visit_function_mut(&mut SpanResetter, &mut function); + let Statement::StateFunctionCallAssign { target_struct_span, .. } = &function.body[0] else { + unreachable!("statement shape was already checked"); + }; + let Statement::StructDestructure { struct_name_span, .. } = &function.body[1] else { + unreachable!("statement shape was already checked"); + }; + let Statement::VariableDefinition { expr: Some(expr), .. } = &function.body[2] else { + unreachable!("statement shape was already checked"); + }; + let ExprKind::Array { type_span, .. } = &expr.kind else { + unreachable!("expression shape was already checked"); + }; + assert!(target_struct_span.as_str().is_empty()); + assert!(struct_name_span.as_str().is_empty()); + assert!(type_span.as_str().is_empty()); +} + +#[test] +fn type_spans_start_at_the_base_type_after_leading_trivia() { + let source = r#"function inspect( + /* before scalar */ ScalarType /* before name */ scalar, + /* before array */ ArrayType /* before suffix */ [] /* before name */ values + ) {}"#; + let function = parse_function_ast(source).expect("type-span fixture should parse"); + + for param in &function.params { + let TypeBase::Custom(name) = ¶m.type_ref.base else { + panic!("fixture should use custom types"); + }; + let start = param.type_span.start(); + let authored_name = source.get(start..start + name.len()).expect("type name should fit within its source"); + + assert_eq!(authored_name, name); + } +} + +#[test] +fn contract_traversal_classifies_struct_names() { + let source = "contract Container() { struct Item { int value; } }"; + let mut contract = parse_contract_ast(source).expect("struct-name fixture should parse"); + let mut collector = NameOccurrenceCollector::default(); + + visit_contract_mut(&mut collector, &mut contract); + + assert_eq!( + collector.occurrences, + [("Container", NameKind::Contract), ("Item", NameKind::Struct), ("value", NameKind::StructField),] + .into_iter() + .map(|(name, kind)| NameOccurrence { name: name.to_string(), kind, source: name.to_string() }) + .collect::>() + ); +} + +#[test] +fn visits_every_classified_type_site_before_mutating_its_span() { + let source = r#"contract Inspect(/* leading */ ContractParam /* separator */ ctor) { + struct Wrapper { + StructFieldType field; + } + + ContractFieldType stored = ContractFieldCtor { count: 0 }; + ConstantType constant initial = ConstantCtor { count: 0 }; + + function inspect(FunctionParam param) : ReturnType { + NestedResult nested = wrap( + flag + ? OuterCtor { items: NestedArray[]{ InnerCtor { count: 1 } } } + : FallbackCtor { count: 0 } + ); + VariableType local = VariableCtor { count: 1 }; + TupleLeft left, TupleRight right = pair(); + (CallBinding output) = identity(local); + StateOwner { count: StateBinding from_call } = readInputState(0); + DestructureOwner { count: DestructureBinding copy } = local; + DeclaredArray[] values = LiteralArray[]{ ArrayElementCtor { count: 1 } }; + return(local); + } + }"#; + let mut contract = parse_contract_ast(source).expect("type-site fixture should parse"); + let mut collector = TypeOccurrenceCollector::default(); + + visit_contract_mut(&mut collector, &mut contract); + + let expected = [ + ("ContractParam", "ContractParam", "ContractParam"), + ("StructFieldType", "StructFieldType", "StructFieldType"), + ("ContractFieldType", "ContractFieldType", "ContractFieldType"), + ("ContractFieldCtor", "ContractFieldCtor", "ContractFieldCtor"), + ("ConstantType", "ConstantType", "ConstantType"), + ("ConstantCtor", "ConstantCtor", "ConstantCtor"), + ("ReturnType", "ReturnType", "ReturnType"), + ("FunctionParam", "FunctionParam", "FunctionParam"), + ("NestedResult", "NestedResult", "NestedResult"), + ("OuterCtor", "OuterCtor", "OuterCtor"), + ("NestedArray[]", "NestedArray", "NestedArray"), + ("InnerCtor", "InnerCtor", "InnerCtor"), + ("FallbackCtor", "FallbackCtor", "FallbackCtor"), + ("VariableType", "VariableType", "VariableType"), + ("VariableCtor", "VariableCtor", "VariableCtor"), + ("TupleLeft", "TupleLeft", "TupleLeft"), + ("TupleRight", "TupleRight", "TupleRight"), + ("CallBinding", "CallBinding", "CallBinding"), + ("StateOwner", "StateOwner", "StateOwner"), + ("StateBinding", "StateBinding", "StateBinding"), + ("DestructureOwner", "DestructureOwner", "DestructureOwner"), + ("DestructureBinding", "DestructureBinding", "DestructureBinding"), + ("DeclaredArray[]", "DeclaredArray", "DeclaredArray"), + ("LiteralArray[]", "LiteralArray", "LiteralArray"), + ("ArrayElementCtor", "ArrayElementCtor", "ArrayElementCtor"), + ]; + assert_eq!( + collector.occurrences, + expected + .into_iter() + .map(|(type_name, base_name, span_prefix)| TypeOccurrence { + type_name: type_name.to_string(), + base_name: base_name.to_string(), + span_prefix: span_prefix.to_string(), + }) + .collect::>() + ); + assert!(contract.structs[0].fields[0].type_span.as_str().is_empty(), "contract traversal must visit struct-field spans"); +} + #[test] fn populates_contract_function_and_statement_spans() { let source = r#" diff --git a/silverscript-lang/tests/parser_tests.rs b/silverscript-lang/tests/parser_tests.rs index dfabbb45..04ba6ac5 100644 --- a/silverscript-lang/tests/parser_tests.rs +++ b/silverscript-lang/tests/parser_tests.rs @@ -99,7 +99,7 @@ fn scalar_byte_cast_remains_scalar_in_the_ast() { #[test] fn try_from_expr_vec_infers_a_fixed_array_type() { let expr = Expr::try_from(vec![Expr::int(1), Expr::int(2)]).expect("homogeneous array type should be inferred"); - let ExprKind::Array { type_ref, values } = expr.kind else { + let ExprKind::Array { type_ref, values, .. } = expr.kind else { panic!("expected an array expression"); }; @@ -135,7 +135,7 @@ fn array_constructor_resolves_inferred_dimension_to_fixed() { #[test] fn try_from_nested_byte_vec_requires_equal_nonempty_elements() { let expr = Expr::try_from(vec![vec![1u8, 2], vec![3, 4]]).expect("uniform nested byte array should be inferred"); - let ExprKind::Array { type_ref, values } = expr.kind else { + let ExprKind::Array { type_ref, values, .. } = expr.kind else { panic!("expected an array expression"); }; @@ -253,7 +253,7 @@ fn typed_array_literal_stores_its_declared_type_on_the_array_expr() { let Statement::VariableDefinition { expr: Some(expr), .. } = &contract.functions[0].body[0] else { panic!("expected a variable definition with an initializer"); }; - let ExprKind::Array { type_ref, values } = &expr.kind else { + let ExprKind::Array { type_ref, values, .. } = &expr.kind else { panic!("expected a typed array expression"); }; @@ -311,7 +311,7 @@ fn byte_array_hex_cast_becomes_a_typed_array_expr() { let Statement::VariableDefinition { expr: Some(expr), .. } = &contract.functions[0].body[0] else { panic!("expected a variable definition with an initializer"); }; - let ExprKind::Array { type_ref, values } = &expr.kind else { + let ExprKind::Array { type_ref, values, .. } = &expr.kind else { panic!("expected the hex cast to lower directly to a typed array expression"); }; @@ -333,7 +333,7 @@ fn fixed_byte_sequence_hex_casts_contain_fixed_byte_array_exprs() { panic!("expected a scalar cast call"); }; assert_eq!(name, type_name); - let [Expr { kind: ExprKind::Array { type_ref, values }, .. }] = args.as_slice() else { + let [Expr { kind: ExprKind::Array { type_ref, values, .. }, .. }] = args.as_slice() else { panic!("expected a single typed byte-array argument"); }; assert_eq!(type_ref, &parse_type_ref(&format!("byte[{size}]")).unwrap());