use cairo_lang_debug::DebugWithDb;
use cairo_lang_defs::ids::{FunctionWithBodyId, ModuleItemId, NamedLanguageElementId, VarId};
use cairo_lang_filesystem::ids::SmolStrId;
use cairo_lang_test_utils::parse_test_file::TestRunnerResult;
use cairo_lang_test_utils::verify_diagnostics_expectation;
use cairo_lang_utils::extract_matches;
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use indoc::indoc;
use pretty_assertions::assert_eq;
use salsa::Database;
use crate::expr::fmt::ExprFormatter;
use crate::items::function_with_body::FunctionWithBodySemantic;
use crate::items::module::ModuleSemantic;
use crate::semantic;
use crate::test_utils::{
SemanticDatabaseForTesting, TestFunction, setup_test_expr, setup_test_function_ex,
test_function_diagnostics,
};
cairo_lang_test_utils::test_file_test!(
expand_inline_macros,
"src/expr/expansion_test_data",
{
inline_macros: "inline_macros",
},
test_expand_expr,
["expect_diagnostics"]
);
cairo_lang_test_utils::test_file_test!(
expr_diagnostics,
"src/expr/test_data",
{
assignment: "assignment",
attributes: "attributes",
constant: "constant",
constructor: "constructor",
closure: "closure",
coupon: "coupon",
deref: "deref",
enum_: "enum",
error_propagate: "error_propagate",
for_: "for",
fixed_size_array: "fixed_size_array",
function_call: "function_call",
generics: "generics",
if_: "if",
inference: "inference",
inline_macros: "inline_macros",
let_else: "let_else",
let_statement: "let_statement",
literal: "literal",
logical_operator: "logical_operator",
loop_: "loop",
match_: "match",
method: "method",
neg_impl: "neg_impl",
operators: "operators",
pattern: "pattern",
return_: "return",
snapshot: "snapshot",
statements: "statements",
structure: "structure",
while_: "while",
impl_: "impl",
},
test_function_diagnostics,
["expect_diagnostics"]
);
cairo_lang_test_utils::test_file_test!(
expr_semantics,
"src/expr/semantic_test_data",
{
assignment: "assignment",
block: "block",
call: "call",
closure: "closure",
coupon: "coupon",
inline_macros: "inline_macros",
let_statement: "let_statement",
literals: "literals",
match_: "match",
if_: "if",
let_else: "let_else",
loop_: "loop",
operator: "operator",
structure: "structure",
tuple: "tuple",
while_: "while",
for_: "for",
range: "range",
const_: "const",
use_: "use",
repr_ptr: "repr_ptr",
},
test_expr_semantics,
["expect_diagnostics"]
);
fn test_expand_expr(
inputs: &OrderedHashMap<String, String>,
args: &OrderedHashMap<String, String>,
) -> TestRunnerResult {
let db = &SemanticDatabaseForTesting::default();
let (test_expr, diagnostics) = setup_test_expr(
db,
inputs["expr_code"].as_str(),
inputs.get("module_code").map_or("", String::as_str),
inputs.get("function_body").map_or("", String::as_str),
inputs.get("crate_settings").map(String::as_str),
)
.split();
let sdb: &dyn Database = db;
let expr = sdb.expr_semantic(test_expr.function_id, test_expr.expr_id);
let error = verify_diagnostics_expectation(args, &diagnostics);
let expanded_code = expr.stable_ptr().0.lookup(db).get_text(db);
let expanded_code = expanded_code.replace("\n ", "\n");
TestRunnerResult {
outputs: OrderedHashMap::from([
("expanded_code".into(), expanded_code),
("diagnostics".into(), diagnostics),
]),
error,
}
}
fn test_expr_semantics(
inputs: &OrderedHashMap<String, String>,
args: &OrderedHashMap<String, String>,
) -> TestRunnerResult {
let db = &SemanticDatabaseForTesting::default();
let (test_expr, diagnostics) = setup_test_expr(
db,
inputs["expr_code"].as_str(),
inputs.get("module_code").map_or("", String::as_str),
inputs.get("function_body").map_or("", String::as_str),
inputs.get("crate_settings").map(String::as_str),
)
.split();
let sdb: &dyn Database = db;
let expr = sdb.expr_semantic(test_expr.function_id, test_expr.expr_id);
let expr_formatter = ExprFormatter { db, function_id: test_expr.function_id };
let error = verify_diagnostics_expectation(args, &diagnostics);
TestRunnerResult {
outputs: OrderedHashMap::from([
("expected_semantics".into(), format!("{:#?}", expr.debug(&expr_formatter))),
("expected_diagnostics".into(), diagnostics),
]),
error,
}
}
#[test]
fn test_function_with_param() {
let db_val = SemanticDatabaseForTesting::default();
let test_function = setup_db_for_foo(&db_val, "fn foo(a: felt252) {}");
let _db = &db_val;
let signature = test_function.signature;
assert_eq!(signature.params.len(), 1);
let param = &signature.params[0];
let _param_ty = param.ty;
}
#[test]
fn test_tuple_type() {
let db_val = SemanticDatabaseForTesting::default();
let test_function = setup_db_for_foo(&db_val, "fn foo(mut a: (felt252, (), (felt252,))) {}");
let db = &db_val;
let signature = test_function.signature;
assert_eq!(signature.params.len(), 1);
let param = &signature.params[0];
assert_eq!(
format!("{:?}", param.debug(db)),
"Parameter { id: ParamId(test::a), name: \"a\", ty: (core::felt252, (), \
(core::felt252,)), mutability: Mutable }"
);
}
#[test]
fn test_function_with_return_type() {
let db_val = SemanticDatabaseForTesting::default();
let test_function = setup_db_for_foo(&db_val, "fn foo() -> felt252 { 5 }");
let _db = &db_val;
let signature = test_function.signature;
let _ret_ty = signature.return_type;
}
#[test]
fn test_expr_var() {
let db_val = SemanticDatabaseForTesting::default();
let test_function = setup_db_for_foo(
&db_val,
indoc! {"
fn foo(a: felt252) -> felt252 {
a
}
"},
);
let db = &db_val;
let sdb: &dyn Database = db;
let semantic::ExprBlock { statements: _, tail, ty: _, stable_ptr: _ } = extract_matches!(
sdb.expr_semantic(test_function.function_id, test_function.body),
crate::Expr::Block
);
let semantic::ExprVar { var: _, ty: _, stable_ptr: _ } = extract_matches!(
sdb.expr_semantic(test_function.function_id, tail.unwrap()),
crate::Expr::Var,
"Expected a variable."
);
}
#[test]
fn test_expr_call_failures() {
let db_val = SemanticDatabaseForTesting::default();
let (test_expr, diagnostics) = setup_test_expr(&db_val, "foo()", "", "", None).split();
let db = &db_val;
let expr_formatter = ExprFormatter { db, function_id: test_expr.function_id };
assert_eq!(
diagnostics,
indoc! { "
error[E0006]: Function not found.
--> lib.cairo:2:1
foo()
^^^
"}
);
let sdb: &dyn Database = db;
assert_eq!(format!("{:?}", test_expr.module_id.debug(db)), "ModuleId(test)");
assert_eq!(
format!(
"{:?}",
sdb.expr_semantic(test_expr.function_id, test_expr.expr_id).debug(&expr_formatter)
),
"Missing(ExprMissing { ty: <missing> })"
);
}
#[test]
fn test_function_body() {
let db_val = SemanticDatabaseForTesting::default();
let test_function = setup_db_for_foo(
&db_val,
indoc! {"
fn foo(a: felt252) {
a;
}
"},
);
let db = &db_val;
let item_id = db
.module_item_by_name(test_function.module_id, SmolStrId::from(db, "foo"))
.unwrap()
.unwrap();
let function_id =
FunctionWithBodyId::Free(extract_matches!(item_id, ModuleItemId::FreeFunction));
let sdb: &dyn Database = db;
let body = sdb.function_body_expr(function_id).unwrap();
let semantic::ExprBlock { statements, .. } = extract_matches!(
sdb.expr_semantic(test_function.function_id, body),
crate::Expr::Block,
"Expected a block."
);
assert_eq!(statements.len(), 1);
let expr = sdb.expr_semantic(
test_function.function_id,
extract_matches!(
sdb.statement_semantic(test_function.function_id, statements[0]),
crate::Statement::Expr
)
.expr,
);
let semantic::ExprVar { var, ty: _, stable_ptr: _ } = extract_matches!(expr, crate::Expr::Var);
let param = extract_matches!(var, VarId::Param);
assert_eq!(param.name(db).long(db), "a");
}
fn setup_db_for_foo<'db>(db: &'db dyn Database, foo_code: &str) -> TestFunction<'db> {
setup_test_function_ex(db, foo_code, "foo", "", None, None).unwrap()
}