use std::sync::Arc;
use cairo_lang_defs::db::{DefsGroup, defs_group_input};
use cairo_lang_defs::ids::{GenericTypeId, MacroPluginLongId, ModuleId, TopLevelLanguageElementId};
use cairo_lang_defs::patcher::{PatchBuilder, RewriteNode};
use cairo_lang_defs::plugin::{
MacroPlugin, MacroPluginMetadata, PluginDiagnostic, PluginGeneratedFile, PluginResult,
};
use cairo_lang_filesystem::ids::SmolStrId;
use cairo_lang_syntax::node::helpers::QueryAttrs;
use cairo_lang_syntax::node::{TypedStablePtr, ast};
use indoc::indoc;
use pretty_assertions::assert_eq;
use salsa::{Database, Setter};
use crate::db::{SemanticGroup, semantic_group_input};
use crate::ids::AnalyzerPluginLongId;
use crate::items::us::SemanticUseEx;
use crate::plugin::AnalyzerPlugin;
use crate::resolve::ResolvedGenericItem;
use crate::test_utils::{
SemanticDatabaseForTesting, get_crate_semantic_diagnostics, setup_test_crate,
test_expr_diagnostics,
};
cairo_lang_test_utils::test_file_test!(
diagnostics,
"src/diagnostic_test_data",
{
allow: "allow",
allow_attr: "allow_attr",
deref: "deref",
tests: "tests",
not_found: "not_found",
missing: "missing",
neg_impl: "neg_impl",
plus_eq: "plus_eq",
inline: "inline",
},
test_expr_diagnostics,
["expect_diagnostics"]
);
#[cairo_lang_test_utils::test]
fn test_missing_module_file() {
let db_val = SemanticDatabaseForTesting::default();
let db = &db_val;
let crate_id = setup_test_crate(
db,
"
mod a {
mod abc;
}",
);
let submodule_id =
*db.module_submodules_ids(ModuleId::CrateRoot(crate_id)).unwrap().first().unwrap();
assert_eq!(
db.module_semantic_diagnostics(ModuleId::Submodule(submodule_id)).unwrap().format(db),
indoc! {"
error[E0005]: Module file not found. Expected path: abc.cairo
--> lib.cairo:3:9
mod abc;
^^^^^^^^
"
},
);
}
#[derive(Debug)]
struct AddInlineModuleDummyPlugin;
impl MacroPlugin for AddInlineModuleDummyPlugin {
fn generate_code<'db>(
&self,
db: &'db dyn Database,
item_ast: ast::ModuleItem<'db>,
_metadata: &MacroPluginMetadata<'_>,
) -> PluginResult<'db> {
match item_ast {
ast::ModuleItem::FreeFunction(func) if func.has_attr(db, "test_change_return_type") => {
let mut builder = PatchBuilder::new(db, &func);
let mut new_func = RewriteNode::from_ast(&func);
if matches!(
func.declaration(db).signature(db).ret_ty(db),
ast::OptionReturnTypeClause::ReturnTypeClause(_)
) {
new_func
.modify_child(db, ast::FunctionWithBody::INDEX_DECLARATION)
.modify_child(db, ast::FunctionDeclaration::INDEX_SIGNATURE)
.modify_child(db, ast::FunctionSignature::INDEX_RET_TY)
.modify_child(db, ast::ReturnTypeClause::INDEX_TY)
.set_str("NewType".into());
new_func
.modify_child(db, ast::FunctionWithBody::INDEX_ATTRIBUTES)
.modify(db)
.children
.as_mut()
.unwrap()
.remove(0);
}
builder.add_modified(RewriteNode::interpolate_patched(
indoc! {"
mod inner_mod {{
extern type NewType;
// Comment 1.
// Comment $$.
$func$
}}
"},
&[("func".to_string(), new_func)].into(),
));
let (content, code_mappings) = builder.build();
PluginResult {
code: Some(PluginGeneratedFile {
name: "virt2".into(),
content,
code_mappings,
aux_data: None,
diagnostics_note: Default::default(),
is_unhygienic: false,
}),
diagnostics: vec![],
remove_original_item: false,
}
}
_ => PluginResult::default(),
}
}
fn declared_attributes<'db>(&self, db: &'db dyn Database) -> Vec<SmolStrId<'db>> {
vec![SmolStrId::from(db, "test_change_return_type")]
}
}
#[cairo_lang_test_utils::test]
fn test_inline_module_diagnostics() {
let mut db_val = SemanticDatabaseForTesting::new_empty();
let db = &mut db_val;
defs_group_input(db)
.set_default_macro_plugins(db)
.to(Some(vec![MacroPluginLongId(Arc::new(AddInlineModuleDummyPlugin))]));
let crate_id = setup_test_crate(
db,
indoc! {"
mod a {
#[test_change_return_type]
fn bad() -> u128 {
return 5_felt252;
}
}
"},
);
assert_eq!(
get_crate_semantic_diagnostics(db, crate_id).format(db),
indoc! {r#"
error[E2042]: Unexpected return type. Expected: "core::integer::u128", found: "core::felt252".
--> lib.cairo:4:16
return 5_felt252;
^^^^^^^^^
error[E2042]: Unexpected return type. Expected: "test::a::inner_mod::NewType", found: "core::felt252".
--> lib.cairo:4:16
return 5_felt252;
^^^^^^^^^
"#},
);
}
#[cairo_lang_test_utils::test]
fn test_inline_inline_module_diagnostics() {
let db_val = SemanticDatabaseForTesting::default();
let db = &db_val;
let crate_id = setup_test_crate(
db,
indoc! {"
mod a {
fn bad_a() -> u128 {
return 1_felt252;
}
}
mod b {
mod c {
fn bad_c() -> u128 {
return 2_felt252;
}
}
mod d {
fn foo_d() {
}
}
}
fn foo() {
b::c::bad_c();
}
"},
);
assert_eq!(
get_crate_semantic_diagnostics(db, crate_id).format(db),
indoc! {r#"error[E2042]: Unexpected return type. Expected: "core::integer::u128", found: "core::felt252".
--> lib.cairo:3:16
return 1_felt252;
^^^^^^^^^
error[E2042]: Unexpected return type. Expected: "core::integer::u128", found: "core::felt252".
--> lib.cairo:9:20
return 2_felt252;
^^^^^^^^^
"#},
);
}
#[derive(Debug)]
struct NoU128RenameAnalyzerPlugin;
impl AnalyzerPlugin for NoU128RenameAnalyzerPlugin {
fn diagnostics<'db>(
&self,
db: &'db dyn Database,
module_id: ModuleId<'db>,
) -> Vec<PluginDiagnostic<'db>> {
let mut diagnostics = vec![];
let Ok(uses) = db.module_uses_ids(module_id) else {
return diagnostics;
};
for use_id in uses.iter() {
let Ok(ResolvedGenericItem::GenericType(GenericTypeId::Extern(ty))) =
db.use_resolved_item(*use_id)
else {
continue;
};
if ty.full_path(db) == "core::integer::u128" {
diagnostics.push(PluginDiagnostic::error(
use_id.stable_ptr(db).untyped(),
"Use items for u128 disallowed.".to_string(),
));
}
}
diagnostics
}
fn declared_allows(&self) -> Vec<String> {
vec!["u128_rename".to_string()]
}
}
#[cairo_lang_test_utils::test]
fn test_analyzer_diagnostics() {
let mut db_val = SemanticDatabaseForTesting::new_empty();
let db = &mut db_val;
let db_ref: &mut dyn Database = db;
semantic_group_input(db_ref)
.set_default_analyzer_plugins(db_ref)
.to(Some(vec![AnalyzerPluginLongId(Arc::new(NoU128RenameAnalyzerPlugin))]));
let crate_id = setup_test_crate(
db,
indoc! {"
mod inner {
use core::integer::u128 as long_u128_rename;
use u128 as short_u128_rename;
use core::integer::u64 as long_u64_rename;
use u64 as short_u64_rename;
}
use core::integer::u128 as long_u128_rename;
use u128 as short_u128_rename;
use inner::long_u128_rename as additional_u128_rename;
#[allow(u128_rename)]
use core::integer::u64 as long_u64_rename;
use u64 as short_u64_rename;
use inner::long_u64_rename as additional_u64_rename;
"},
);
assert_eq!(
get_crate_semantic_diagnostics(db, crate_id).format(db),
indoc! {r#"
error[E2200]: Plugin diagnostic: Use items for u128 disallowed.
--> lib.cairo:7:20
use core::integer::u128 as long_u128_rename;
^^^^^^^^^^^^^^^^^^^^^^^^
error[E2200]: Plugin diagnostic: Use items for u128 disallowed.
--> lib.cairo:8:5
use u128 as short_u128_rename;
^^^^^^^^^^^^^^^^^^^^^^^^^
error[E2200]: Plugin diagnostic: Use items for u128 disallowed.
--> lib.cairo:9:12
use inner::long_u128_rename as additional_u128_rename;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E2200]: Plugin diagnostic: Use items for u128 disallowed.
--> lib.cairo:2:24
use core::integer::u128 as long_u128_rename;
^^^^^^^^^^^^^^^^^^^^^^^^
error[E2200]: Plugin diagnostic: Use items for u128 disallowed.
--> lib.cairo:3:9
use u128 as short_u128_rename;
^^^^^^^^^^^^^^^^^^^^^^^^^
"#},
);
}