use crate::frontend::ast::{Attribute, Expr, ExprKind, Span};
use crate::frontend::lexer::Token;
use crate::frontend::parser::{bail, ParserState, Result};
use super::identifiers;
pub(in crate::frontend::parser) fn parse_pub_token(
state: &mut ParserState,
_span: Span,
) -> Result<Expr> {
state.tokens.advance(); let visibility_args = capture_visibility_scope(state)?;
let mut expr = parse_pub_target_expression(state, visibility_args)?;
mark_expression_as_public(&mut expr);
Ok(expr)
}
fn capture_visibility_scope(state: &mut ParserState) -> Result<Vec<String>> {
if !matches!(state.tokens.peek(), Some((Token::LeftParen, _))) {
return Ok(vec![]);
}
state.tokens.advance(); let args = match state.tokens.peek() {
Some((Token::Crate, _)) => {
state.tokens.advance();
state.tokens.expect(&Token::RightParen)?;
vec!["crate".to_string()]
}
Some((Token::Super, _)) => {
state.tokens.advance();
state.tokens.expect(&Token::RightParen)?;
vec!["super".to_string()]
}
Some((Token::In, _)) => {
state.tokens.advance(); parse_visibility_path(state)?;
state.tokens.expect(&Token::RightParen)?;
vec![]
}
_ => bail!("Expected 'crate', 'super', or 'in' after 'pub('"),
};
Ok(args)
}
fn parse_visibility_path(state: &mut ParserState) -> Result<()> {
if matches!(state.tokens.peek(), Some((Token::ColonColon, _))) {
state.tokens.advance(); }
match state.tokens.peek() {
Some((Token::Crate, _)) => {
state.tokens.advance();
let _ = identifiers::parse_module_path_segments(state, "crate".to_string())?;
}
Some((Token::Super, _)) => {
state.tokens.advance();
let _ = identifiers::parse_module_path_segments(state, "super".to_string())?;
}
Some((Token::Self_, _)) => {
state.tokens.advance();
let _ = identifiers::parse_module_path_segments(state, "self".to_string())?;
}
Some((Token::Identifier(name), _)) => {
let name = name.clone();
state.tokens.advance();
let _ = identifiers::parse_module_path_segments(state, name)?;
}
_ => bail!("Expected path after 'pub(in'"),
}
Ok(())
}
fn parse_pub_target_expression(
state: &mut ParserState,
visibility_args: Vec<String>,
) -> Result<Expr> {
match state.tokens.peek() {
Some((Token::Use, _)) => parse_pub_use_statement(state, visibility_args),
Some((Token::Const, _)) => parse_pub_const_function(state),
Some((Token::Unsafe, _)) => parse_pub_unsafe_function(state),
Some((Token::Mod | Token::Module, _)) => {
parse_pub_module_declaration(state, visibility_args)
}
_ => super::super::parse_prefix(state),
}
}
fn parse_pub_use_statement(state: &mut ParserState, visibility_args: Vec<String>) -> Result<Expr> {
let mut expr = super::super::parse_use_statement(state)?;
expr.attributes.push(Attribute {
name: "pub".to_string(),
args: visibility_args,
span: expr.span,
});
Ok(expr)
}
fn parse_pub_module_declaration(
state: &mut ParserState,
visibility_args: Vec<String>,
) -> Result<Expr> {
let mut expr = super::modules::parse_module_declaration(state)?;
expr.attributes.push(Attribute {
name: "pub".to_string(),
args: visibility_args,
span: expr.span,
});
Ok(expr)
}
fn parse_pub_const_function(state: &mut ParserState) -> Result<Expr> {
state.tokens.advance(); if !matches!(state.tokens.peek(), Some((Token::Fun | Token::Fn, _))) {
bail!("Expected 'fun' or 'fn' after 'pub const'");
}
let mut expr = super::super::parse_prefix(state)?;
if let ExprKind::Function { .. } = &expr.kind {
expr.attributes.push(Attribute {
name: "const".to_string(),
args: vec![],
span: expr.span,
});
}
Ok(expr)
}
fn parse_pub_unsafe_function(state: &mut ParserState) -> Result<Expr> {
state.tokens.advance(); if !matches!(state.tokens.peek(), Some((Token::Fun | Token::Fn, _))) {
bail!("Expected 'fun' or 'fn' after 'pub unsafe'");
}
let mut expr = super::super::parse_prefix(state)?;
if let ExprKind::Function { .. } = &expr.kind {
expr.attributes.push(Attribute {
name: "unsafe".to_string(),
args: vec![],
span: expr.span,
});
}
Ok(expr)
}
fn mark_expression_as_public(expr: &mut Expr) {
match &mut expr.kind {
ExprKind::Function { is_pub, .. }
| ExprKind::Struct { is_pub, .. }
| ExprKind::TupleStruct { is_pub, .. }
| ExprKind::Class { is_pub, .. }
| ExprKind::Trait { is_pub, .. }
| ExprKind::Impl { is_pub, .. } => *is_pub = true,
_ => {}
}
}
pub(in crate::frontend::parser) fn parse_const_token(
state: &mut ParserState,
span: Span,
) -> Result<Expr> {
state.tokens.advance();
match state.tokens.peek() {
Some((Token::Fun | Token::Fn, _)) => {
let mut expr = super::super::parse_prefix(state)?;
if let ExprKind::Function { .. } = &expr.kind {
expr.attributes.push(Attribute {
name: "const".to_string(),
args: vec![],
span: expr.span,
});
}
Ok(expr)
}
Some((Token::Identifier(_), _)) => {
parse_const_variable(state, span)
}
_ => bail!("Expected identifier, 'fun', or 'fn' after 'const'"),
}
}
fn parse_const_variable(state: &mut ParserState, start_span: Span) -> Result<Expr> {
use crate::frontend::ast::Literal;
use crate::frontend::parser::{parse_expr_recursive, utils};
let name = match state.tokens.peek() {
Some((Token::Identifier(n), _)) => {
let name = n.clone();
state.tokens.advance();
name
}
_ => bail!("Expected identifier after 'const'"),
};
let type_annotation = if matches!(state.tokens.peek(), Some((Token::Colon, _))) {
state.tokens.advance(); Some(utils::parse_type(state)?)
} else {
None
};
state.tokens.expect(&Token::Equal)?;
let value = Box::new(parse_expr_recursive(state)?);
let body = Box::new(Expr::new(ExprKind::Literal(Literal::Unit), value.span));
let end_span = value.span;
let mut expr = Expr::new(
ExprKind::Let {
name,
type_annotation,
value,
body,
is_mutable: false, else_block: None, },
start_span.merge(end_span),
);
expr.attributes.push(Attribute {
name: "const".to_string(),
args: vec![],
span: start_span,
});
Ok(expr)
}
pub(in crate::frontend::parser) fn parse_sealed_token(
state: &mut ParserState,
_span: Span,
) -> Result<Expr> {
state.tokens.advance();
match state.tokens.peek() {
Some((Token::Class, _)) => {
let mut expr = super::super::parse_prefix(state)?;
if let ExprKind::Class { is_sealed, .. } = &mut expr.kind {
*is_sealed = true;
}
Ok(expr)
}
_ => bail!("Expected 'class' after 'sealed'"),
}
}
pub(in crate::frontend::parser) fn parse_final_token(
state: &mut ParserState,
_span: Span,
) -> Result<Expr> {
let start = state.tokens.current_position();
state.tokens.advance();
match state.tokens.peek() {
Some((Token::Class, _)) => {
let mut expr = super::super::parse_prefix(state)?;
if let ExprKind::Class { .. } = &expr.kind {
expr.attributes.push(Attribute {
name: "final".to_string(),
args: vec![],
span: expr.span,
});
}
Ok(expr)
}
Some((Token::Fun | Token::Fn, _)) => {
let mut expr = super::super::parse_prefix(state)?;
if let ExprKind::Function { .. } = &expr.kind {
expr.attributes.push(Attribute {
name: "final".to_string(),
args: vec![],
span: expr.span,
});
}
Ok(expr)
}
_ => {
Ok(Expr::new(
ExprKind::Identifier("final".to_string()),
Span::new(start.0, start.1 + 5), ))
}
}
}
pub(in crate::frontend::parser) fn parse_abstract_token(
state: &mut ParserState,
_span: Span,
) -> Result<Expr> {
state.tokens.advance();
match state.tokens.peek() {
Some((Token::Class, _)) => {
let mut expr = super::super::parse_prefix(state)?;
if let ExprKind::Class { is_abstract, .. } = &mut expr.kind {
*is_abstract = true;
}
Ok(expr)
}
Some((Token::Fun | Token::Fn, _)) => {
let mut expr = super::super::parse_prefix(state)?;
if let ExprKind::Function { .. } = &expr.kind {
expr.attributes.push(Attribute {
name: "abstract".to_string(),
args: vec![],
span: expr.span,
});
}
Ok(expr)
}
_ => bail!("Expected 'class' or 'fn' after 'abstract'"),
}
}
pub(in crate::frontend::parser) fn parse_unsafe_token(
state: &mut ParserState,
_span: Span,
) -> Result<Expr> {
state.tokens.advance();
match state.tokens.peek() {
Some((Token::Fun | Token::Fn, _)) => {
let mut expr = super::super::parse_prefix(state)?;
if let ExprKind::Function { .. } = &expr.kind {
expr.attributes.push(Attribute {
name: "unsafe".to_string(),
args: vec![],
span: expr.span,
});
}
Ok(expr)
}
_ => bail!("Expected 'fun' or 'fn' after 'unsafe'"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::parser::Parser;
fn parse(code: &str) -> Result<Expr> {
let mut parser = Parser::new(code);
parser.parse()
}
fn get_block_exprs(expr: &Expr) -> Option<&Vec<Expr>> {
match &expr.kind {
ExprKind::Block(exprs) => Some(exprs),
_ => None,
}
}
#[test]
fn test_pub_function() {
let code = "pub fn test() { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Public function should parse successfully");
}
#[test]
fn test_pub_function_is_pub_flag() {
let expr = parse("pub fun foo() { 42 }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::Function { is_pub, .. } = &exprs[0].kind {
assert!(*is_pub, "Function should be marked as public");
}
}
}
#[test]
fn test_pub_struct() {
let result = parse("pub struct Point { x: i32, y: i32 }");
assert!(result.is_ok());
}
#[test]
fn test_pub_struct_is_pub_flag() {
let expr = parse("pub struct Foo { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::Struct { is_pub, .. } = &exprs[0].kind {
assert!(*is_pub);
}
}
}
#[test]
fn test_pub_trait() {
let result = parse("pub trait Foo { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_trait_is_pub_flag() {
let expr = parse("pub trait Bar { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::Trait { is_pub, .. } = &exprs[0].kind {
assert!(*is_pub);
}
}
}
#[test]
fn test_pub_class() {
let result = parse("pub class MyClass { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_class_is_pub_flag() {
let expr = parse("pub class MyClass { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::Class { is_pub, .. } = &exprs[0].kind {
assert!(*is_pub);
}
}
}
#[test]
fn test_pub_module() {
let code = "pub mod test_module { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Public module should parse successfully");
}
#[test]
fn test_pub_crate() {
let code = "pub(crate) fn test() { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "pub(crate) function should parse");
}
#[test]
fn test_pub_super() {
let code = "pub(super) fn test() { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "pub(super) function should parse");
}
#[test]
fn test_pub_in_path() {
let result = parse("pub(in crate::module) fn foo() { }");
assert!(result.is_ok(), "pub(in path) should parse");
}
#[test]
fn test_pub_in_super_path() {
let result = parse("pub(in super::sibling) fn foo() { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_crate_struct() {
let result = parse("pub(crate) struct Internal { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_super_trait() {
let result = parse("pub(super) trait ParentVisible { }");
assert!(result.is_ok());
}
#[test]
fn test_const_function() {
let result = parse("const fn compile_time() { 42 }");
assert!(result.is_ok());
}
#[test]
fn test_const_function_has_attribute() {
let expr = parse("const fn test() { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
let func = &exprs[0];
assert!(func.attributes.iter().any(|a| a.name == "const"));
}
}
#[test]
fn test_const_variable() {
let result = parse("const X = 42");
assert!(result.is_ok());
}
#[test]
fn test_const_variable_with_type() {
let result = parse("const MAX: i32 = 100");
assert!(result.is_ok());
}
#[test]
fn test_const_variable_with_expression() {
let result = parse("const COMPUTED = 10 * 10");
assert!(result.is_ok());
}
#[test]
fn test_const_variable_is_immutable() {
let expr = parse("const X = 42").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::Let { is_mutable, .. } = &exprs[0].kind {
assert!(!is_mutable);
}
}
}
#[test]
fn test_pub_const_fn() {
let code = "pub const fn test() { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Public const function should parse");
}
#[test]
fn test_unsafe_function() {
let result = parse("unsafe fn dangerous() { }");
assert!(result.is_ok());
}
#[test]
fn test_unsafe_function_has_attribute() {
let expr = parse("unsafe fn test() { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
let func = &exprs[0];
assert!(func.attributes.iter().any(|a| a.name == "unsafe"));
}
}
#[test]
fn test_pub_unsafe_fn() {
let code = "pub unsafe fn test() { }";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Public unsafe function should parse");
}
#[test]
fn test_unsafe_with_fun_keyword() {
let result = parse("unsafe fun test() { }");
assert!(result.is_ok());
}
#[test]
fn test_sealed_class() {
let code = "sealed class Test {}";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Sealed class should parse successfully");
}
#[test]
fn test_sealed_class_flag() {
let expr = parse("sealed class Internal { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::Class { is_sealed, .. } = &exprs[0].kind {
assert!(*is_sealed);
}
}
}
#[test]
fn test_sealed_class_with_fields() {
let result = parse("sealed class Data { value: i32 }");
assert!(result.is_ok());
}
#[test]
fn test_sealed_class_with_methods() {
let result = parse("sealed class Helper { fun work(&self) { } }");
assert!(result.is_ok());
}
#[test]
fn test_final_class() {
let code = "final class Test {}";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Final class should parse successfully");
}
#[test]
fn test_final_class_has_attribute() {
let expr = parse("final class Immutable { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
let cls = &exprs[0];
assert!(cls.attributes.iter().any(|a| a.name == "final"));
}
}
#[test]
fn test_final_function() {
let result = parse("final fn cannot_override() { }");
assert!(result.is_ok());
}
#[test]
fn test_final_function_has_attribute() {
let expr = parse("final fun test() { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
let func = &exprs[0];
assert!(func.attributes.iter().any(|a| a.name == "final"));
}
}
#[test]
fn test_final_as_identifier() {
let code = "let final = 42";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "'final' should be usable as identifier");
}
#[test]
fn test_final_as_identifier_in_expression() {
let result = parse("let x = final + 1");
assert!(result.is_ok());
}
#[test]
fn test_abstract_class() {
let code = "abstract class Test {}";
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Abstract class should parse successfully");
}
#[test]
fn test_abstract_class_flag() {
let expr = parse("abstract class Base { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
if let ExprKind::Class { is_abstract, .. } = &exprs[0].kind {
assert!(*is_abstract);
}
}
}
#[test]
fn test_abstract_function() {
let result = parse("abstract fn must_implement() { }");
assert!(result.is_ok());
}
#[test]
fn test_abstract_function_has_attribute() {
let expr = parse("abstract fun test() { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
let func = &exprs[0];
assert!(func.attributes.iter().any(|a| a.name == "abstract"));
}
}
#[test]
fn test_abstract_class_with_methods() {
let result = parse("abstract class Shape { fun area(&self) -> f64 { 0.0 } }");
assert!(result.is_ok());
}
#[test]
fn test_pub_sealed_class() {
let result = parse("pub sealed class Internal { }");
assert!(result.is_err() || result.is_ok());
}
#[test]
fn test_pub_abstract_class() {
let result = parse("pub abstract class Base { }");
assert!(result.is_err() || result.is_ok());
}
#[test]
fn test_pub_final_class() {
let result = parse("pub final class Immutable { }");
assert!(result.is_err() || result.is_ok());
}
#[test]
fn test_pub_with_generic_function() {
let result = parse("pub fn identity<T>(x: T) -> T { x }");
assert!(result.is_ok());
}
#[test]
fn test_pub_with_generic_struct() {
let result = parse("pub struct Container<T> { value: T }");
assert!(result.is_ok());
}
#[test]
fn test_const_string_literal() {
let result = parse("const NAME = \"constant\"");
assert!(result.is_ok());
}
#[test]
fn test_const_array_literal() {
let result = parse("const VALUES = [1, 2, 3]");
assert!(result.is_ok());
}
#[test]
fn test_pub_use_statement() {
let result = parse("pub use std::io");
assert!(result.is_ok());
}
#[test]
fn test_pub_crate_use_statement() {
let result = parse("pub(crate) use internal::helper");
assert!(result.is_ok());
}
#[test]
fn test_sealed_without_class() {
let result = parse("sealed fn invalid() { }");
assert!(result.is_err(), "sealed should require class");
}
#[test]
fn test_abstract_without_target() {
let result = parse("abstract 42");
assert!(result.is_err());
}
#[test]
fn test_unsafe_without_function() {
let result = parse("unsafe class Invalid { }");
assert!(result.is_err(), "unsafe should require function");
}
#[test]
fn test_pub_without_target() {
let result = parse("pub");
assert!(result.is_err());
}
#[test]
fn test_pub_fn_keyword() {
let result = parse("pub fn test() { }");
assert!(result.is_ok(), "pub fn should parse");
}
#[test]
fn test_pub_fun_keyword() {
let result = parse("pub fun test() { }");
assert!(result.is_ok(), "pub fun should parse");
}
#[test]
fn test_pub_fn_with_params() {
let result = parse("pub fn add(a: i32, b: i32) -> i32 { a + b }");
assert!(result.is_ok());
}
#[test]
fn test_pub_fn_with_generics() {
let result = parse("pub fn swap<T>(a: T, b: T) -> (T, T) { (b, a) }");
assert!(result.is_ok());
}
#[test]
fn test_pub_fn_long_name() {
let result = parse("pub fn very_long_function_name_here() { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_fn_single_char() {
let result = parse("pub fn f() { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_crate_fn() {
let result = parse("pub(crate) fn internal() { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_crate_fun() {
let result = parse("pub(crate) fun internal() { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_crate_struct_with_fields() {
let result = parse("pub(crate) struct Data { x: i32, y: i32 }");
assert!(result.is_ok());
}
#[test]
fn test_pub_crate_trait_with_methods() {
let result = parse("pub(crate) trait Internal { fn method(&self); }");
assert!(result.is_ok());
}
#[test]
fn test_pub_crate_class() {
let result = parse("pub(crate) class Helper { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_super_fn() {
let result = parse("pub(super) fn parent_visible() { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_super_fun() {
let result = parse("pub(super) fun parent_visible() { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_super_struct() {
let result = parse("pub(super) struct ParentData { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_super_class() {
let result = parse("pub(super) class ParentHelper { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_in_crate_path() {
let result = parse("pub(in crate::module::submodule) fn restricted() { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_in_super_nested_path() {
let result = parse("pub(in super::sibling::child) fn restricted() { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_in_self_path() {
let result = parse("pub(in self::child) fn restricted() { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_in_absolute_path() {
let result = parse("pub(in ::root::module) fn restricted() { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_in_identifier_path() {
let result = parse("pub(in mymodule::inner) fn restricted() { }");
assert!(result.is_ok());
}
#[test]
fn test_const_fn_with_return_type() {
let result = parse("const fn value() -> i32 { 42 }");
assert!(result.is_ok());
}
#[test]
fn test_const_fun_keyword() {
let result = parse("const fun test() { }");
assert!(result.is_ok());
}
#[test]
fn test_const_var_float() {
let result = parse("const PI: f64 = 3.14159");
assert!(result.is_ok());
}
#[test]
fn test_const_var_bool() {
let result = parse("const DEBUG = true");
assert!(result.is_ok());
}
#[test]
fn test_const_var_tuple() {
let result = parse("const ORIGIN = (0, 0)");
assert!(result.is_ok());
}
#[test]
fn test_const_var_negative() {
let result = parse("const MIN = -100");
assert!(result.is_ok());
}
#[test]
fn test_const_var_binary_op() {
let result = parse("const SIZE = 1024 * 1024");
assert!(result.is_ok());
}
#[test]
fn test_unsafe_fn_with_params() {
let result = parse("unsafe fn process(data: i32) { }");
assert!(result.is_ok());
}
#[test]
fn test_unsafe_fn_with_return() {
let result = parse("unsafe fn dangerous() -> i32 { 42 }");
assert!(result.is_ok());
}
#[test]
fn test_unsafe_fun_with_body() {
let result = parse("unsafe fun work() { let x = 1 }");
assert!(result.is_ok());
}
#[test]
fn test_pub_unsafe_fn_order() {
let result = parse("pub unsafe fn exposed_danger() { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_crate_unsafe() {
let result = parse("pub(crate) unsafe fn internal_danger() { }");
assert!(result.is_ok());
}
#[test]
fn test_sealed_class_empty() {
let result = parse("sealed class Empty { }");
assert!(result.is_ok());
}
#[test]
fn test_sealed_class_with_constructor() {
let result = parse("sealed class Data { fun new() { } }");
assert!(result.is_ok());
}
#[test]
fn test_sealed_class_multiple_fields() {
let result = parse("sealed class Point { x: i32, y: i32, z: i32 }");
assert!(result.is_ok());
}
#[test]
fn test_sealed_class_generic() {
let result = parse("sealed class Box<T> { value: T }");
assert!(result.is_ok());
}
#[test]
fn test_final_class_empty() {
let result = parse("final class Singleton { }");
assert!(result.is_ok());
}
#[test]
fn test_final_class_with_fields() {
let result = parse("final class Config { port: i32 }");
assert!(result.is_ok());
}
#[test]
fn test_final_fn_with_params() {
let result = parse("final fn compute(x: i32) -> i32 { x * 2 }");
assert!(result.is_ok());
}
#[test]
fn test_final_fun_keyword() {
let result = parse("final fun locked() { }");
assert!(result.is_ok());
}
#[test]
fn test_final_identifier_in_call() {
let result = parse("foo(final)");
assert!(result.is_ok());
}
#[test]
fn test_final_identifier_assignment() {
let result = parse("let x = final");
assert!(result.is_ok());
}
#[test]
fn test_abstract_class_empty() {
let result = parse("abstract class Base { }");
assert!(result.is_ok());
}
#[test]
fn test_abstract_class_with_field() {
let result = parse("abstract class Entity { id: i32 }");
assert!(result.is_ok());
}
#[test]
fn test_abstract_fn_with_params() {
let result = parse("abstract fn compute(x: i32) -> i32 { 0 }");
assert!(result.is_ok());
}
#[test]
fn test_abstract_fun_keyword() {
let result = parse("abstract fun process() { }");
assert!(result.is_ok());
}
#[test]
fn test_abstract_class_generic() {
let result = parse("abstract class Container<T> { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_mod_inline() {
let result = parse("pub mod utils { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_mod_external() {
let result = parse("pub mod utils;");
assert!(result.is_ok());
}
#[test]
fn test_pub_crate_mod() {
let result = parse("pub(crate) mod internal { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_super_mod() {
let result = parse("pub(super) mod sibling { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_module_keyword() {
let result = parse("pub module helpers { }");
assert!(result.is_ok());
}
#[test]
fn test_pub_use_simple() {
let result = parse("pub use std::io");
assert!(result.is_ok());
}
#[test]
fn test_pub_use_nested() {
let result = parse("pub use std::collections::HashMap");
assert!(result.is_ok());
}
#[test]
fn test_pub_crate_use() {
let result = parse("pub(crate) use internal::Helper");
assert!(result.is_ok());
}
#[test]
fn test_pub_super_use() {
let result = parse("pub(super) use sibling::Shared");
assert!(result.is_ok());
}
#[test]
fn test_pub_produces_function_exprkind() {
let expr = parse("pub fn test() { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(
&exprs[0].kind,
ExprKind::Function { is_pub: true, .. }
));
}
}
#[test]
fn test_sealed_produces_class_exprkind() {
let expr = parse("sealed class Test { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(
&exprs[0].kind,
ExprKind::Class {
is_sealed: true,
..
}
));
}
}
#[test]
fn test_abstract_produces_class_exprkind() {
let expr = parse("abstract class Test { }").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(
&exprs[0].kind,
ExprKind::Class {
is_abstract: true,
..
}
));
}
}
#[test]
fn test_const_var_produces_let_exprkind() {
let expr = parse("const X = 42").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(
&exprs[0].kind,
ExprKind::Let {
is_mutable: false,
..
}
));
}
}
#[test]
fn test_final_identifier_produces_identifier_exprkind() {
let expr = parse("final").unwrap();
if let Some(exprs) = get_block_exprs(&expr) {
assert!(matches!(&exprs[0].kind, ExprKind::Identifier(name) if name == "final"));
}
}
#[test]
fn test_const_without_value() {
let result = parse("const X");
assert!(result.is_err());
}
#[test]
fn test_pub_invalid_scope() {
let result = parse("pub(invalid) fn test() { }");
assert!(result.is_err());
}
#[test]
fn test_sealed_with_fn() {
let result = parse("sealed fn invalid() { }");
assert!(result.is_err());
}
#[test]
fn test_abstract_with_struct() {
let result = parse("abstract struct Invalid { }");
assert!(result.is_err());
}
#[test]
fn test_unsafe_with_struct() {
let result = parse("unsafe struct Invalid { }");
assert!(result.is_err());
}
#[test]
fn test_pub_incomplete() {
let result = parse("pub(");
assert!(result.is_err());
}
#[test]
fn test_pub_in_incomplete() {
let result = parse("pub(in ) fn test() { }");
assert!(result.is_err());
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
#[ignore = "Property tests run with --ignored flag"] fn prop_visibility_modifiers_never_panic(modifier in prop::sample::select(vec![
"pub", "pub(crate)", "pub(super)", "const", "sealed",
"final", "abstract", "unsafe"
])) {
let code = format!("{modifier} fn test() {{}}");
let _ = Parser::new(&code).parse(); }
}
proptest! {
#[test]
#[ignore = "Property tests run with --ignored flag"]
fn prop_pub_crate_super_equivalent_parsing(
fn_name in "[a-z][a-z0-9_]{0,10}"
) {
let code_crate = format!("pub(crate) fn {fn_name}() {{}}");
let code_super = format!("pub(super) fn {fn_name}() {{}}");
let result_crate = Parser::new(&code_crate).parse();
let result_super = Parser::new(&code_super).parse();
prop_assert_eq!(result_crate.is_ok(), result_super.is_ok());
}
}
proptest! {
#[test]
#[ignore = "Property tests run with --ignored flag"]
fn prop_final_dual_usage(use_as_modifier in prop::bool::ANY) {
let code = if use_as_modifier {
"final class Test {}"
} else {
"let final = 42"
};
let result = Parser::new(code).parse();
prop_assert!(result.is_ok(), "final should work in both contexts");
}
}
proptest! {
#[test]
#[ignore = "Property tests run with --ignored flag"]
fn prop_pub_combinations(
modifier in prop::sample::select(vec!["const", "unsafe"])
) {
let code = format!("pub {modifier} fn test() {{}}");
let result = Parser::new(&code).parse();
prop_assert!(result.is_ok(), "pub + {} should parse", modifier);
}
}
proptest! {
#[test]
#[ignore = "Property tests run with --ignored flag"]
fn prop_invalid_modifiers_fail_gracefully(
invalid in prop::sample::select(vec![
"sealed fn", "abstract struct", "final trait"
])
) {
let code = format!("{invalid} Test {{}}");
let result = Parser::new(&code).parse();
let _ = result;
}
}
proptest! {
#[test]
#[ignore = "Property tests run with --ignored flag"]
fn prop_class_modifiers_require_class(
modifier in prop::sample::select(vec!["sealed", "abstract", "final"])
) {
let valid_code = format!("{modifier} class Test {{}}");
let valid_result = Parser::new(&valid_code).parse();
let invalid_code = format!("{modifier} fn test() {{}}");
let invalid_result = Parser::new(&invalid_code).parse();
if modifier == "final" {
prop_assert!(valid_result.is_ok() && invalid_result.is_ok());
} else {
prop_assert!(valid_result.is_ok());
prop_assert!(invalid_result.is_err(),
"{} should not work with fn", modifier);
}
}
}
}
}