diff --git a/crates/cairo-lang-semantic/src/diagnostic.rs b/crates/cairo-lang-semantic/src/diagnostic.rs index f1524b93b57..e66959967fd 100644 --- a/crates/cairo-lang-semantic/src/diagnostic.rs +++ b/crates/cairo-lang-semantic/src/diagnostic.rs @@ -848,6 +848,12 @@ impl DiagnosticEntry for SemanticDiagnostic { "Closures are not allowed in this context.".into() } SemanticDiagnosticKind::MaybeMissingColonColon => "Are you missing a `::`?.".into(), + SemanticDiagnosticKind::CallingShadowedFunction { shadowed_function_name } => { + format!("Function `{}` is shadowed by a local variable.", shadowed_function_name) + } + SemanticDiagnosticKind::RefClosureArgument => { + "Arguments to closure functions cannot be references".into() + } } } @@ -871,7 +877,8 @@ impl DiagnosticEntry for SemanticDiagnostic { | SemanticDiagnosticKind::ImplItemForbiddenInTheImpl | SemanticDiagnosticKind::UnstableFeature { .. } | SemanticDiagnosticKind::DeprecatedFeature { .. } - | SemanticDiagnosticKind::UnusedImport { .. } => Severity::Warning, + | SemanticDiagnosticKind::UnusedImport { .. } + | SemanticDiagnosticKind::CallingShadowedFunction { .. } => Severity::Warning, SemanticDiagnosticKind::PluginDiagnostic(diag) => diag.severity, _ => Severity::Error, } @@ -1197,6 +1204,10 @@ pub enum SemanticDiagnosticKind { TypeEqualTraitReImplementation, ClosureInGlobalScope, MaybeMissingColonColon, + CallingShadowedFunction { + shadowed_function_name: SmolStr, + }, + RefClosureArgument, } /// The kind of an expression with multiple possible return types. diff --git a/crates/cairo-lang-semantic/src/expr/compute.rs b/crates/cairo-lang-semantic/src/expr/compute.rs index f811feb5988..ce542c93151 100644 --- a/crates/cairo-lang-semantic/src/expr/compute.rs +++ b/crates/cairo-lang-semantic/src/expr/compute.rs @@ -144,6 +144,9 @@ pub struct ComputationContext<'ctx> { pub semantic_defs: UnorderedHashMap, loop_ctx: Option, cfg_set: Arc, + /// whether to look for closures when calling variables. + /// TODO(TomerStarkware): Remove this once we disallow calling shadowed functions. + are_closures_in_context: bool, } impl<'ctx> ComputationContext<'ctx> { pub fn new( @@ -171,6 +174,7 @@ impl<'ctx> ComputationContext<'ctx> { .crate_config(owning_crate_id) .and_then(|cfg| cfg.settings.cfg_set.map(Arc::new)) .unwrap_or(db.cfg_set()), + are_closures_in_context: false, } } @@ -755,9 +759,81 @@ fn compute_expr_function_call_semantic( let syntax_db = db.upcast(); let path = syntax.path(syntax_db); + let args_syntax = syntax.arguments(syntax_db).arguments(syntax_db); + // Check if this is a variable. + let segments = path.elements(syntax_db); + let mut is_shadowed_by_variable = false; + if let [PathSegment::Simple(ident_segment)] = &segments[..] { + let identifier = ident_segment.ident(syntax_db); + let variable_name = identifier.text(ctx.db.upcast()); + if let Some(var) = get_variable_by_name(ctx, &variable_name, path.stable_ptr().into()) { + is_shadowed_by_variable = true; + // if closures are not in context, we want to call the function instead of the variable. + if ctx.are_closures_in_context { + // TODO(TomerStarkware): find the correct trait based on captured variables. + let fn_once_trait = crate::corelib::fn_once_trait(db); + let self_expr = ExprAndId { expr: var.clone(), id: ctx.arenas.exprs.alloc(var) }; + let (call_function_id, _, fixed_closure, closure_mutability) = + compute_method_function_call_data( + ctx, + &[fn_once_trait], + "call".into(), + self_expr, + syntax.into(), + None, + |ty, _, inference_errors| { + Some(NoImplementationOfTrait { + ty, + inference_errors, + trait_name: "FnOnce".into(), + }) + }, + |_, _, _| { + unreachable!( + "There is one explicit trait, FnOnce trait. No implementations of \ + the trait, caused by both lack of implementation or multiple \ + implementations of the trait, are handled in \ + NoImplementationOfTrait function." + ) + }, + )?; + + let args_iter = args_syntax.elements(syntax_db).into_iter(); + // Normal parameters + let mut args = vec![]; + let mut arg_types = vec![]; + for arg_syntax in args_iter { + let stable_ptr = arg_syntax.stable_ptr(); + let arg = compute_named_argument_clause(ctx, arg_syntax); + if arg.2 != Mutability::Immutable { + return Err(ctx.diagnostics.report(stable_ptr, RefClosureArgument)); + } + args.push(arg.0.id); + arg_types.push(arg.0.ty()); + } + let args_expr = Expr::Tuple(ExprTuple { + items: args, + ty: TypeLongId::Tuple(arg_types).intern(db), + stable_ptr: syntax.stable_ptr().into(), + }); + let args_expr = + ExprAndId { expr: args_expr.clone(), id: ctx.arenas.exprs.alloc(args_expr) }; + return expr_function_call( + ctx, + call_function_id, + vec![ + NamedArg(fixed_closure, None, closure_mutability), + NamedArg(args_expr, None, Mutability::Immutable), + ], + syntax, + syntax.stable_ptr().into(), + ); + } + } + } + let item = ctx.resolver.resolve_concrete_path(ctx.diagnostics, &path, NotFoundItemType::Function)?; - let args_syntax = syntax.arguments(syntax_db).arguments(syntax_db); match item { ResolvedConcreteItem::Variant(variant) => { @@ -805,6 +881,18 @@ fn compute_expr_function_call_semantic( })) } ResolvedConcreteItem::Function(function) => { + if is_shadowed_by_variable { + return Err(ctx.diagnostics.report( + &path, + CallingShadowedFunction { + shadowed_function_name: path + .elements(syntax_db) + .first() + .unwrap() + .identifier(syntax_db), + }, + )); + } // TODO(Gil): Consider not invoking the TraitFunction inference below if there were // errors in argument semantics, in order to avoid unnecessary diagnostics. @@ -888,6 +976,9 @@ pub fn compute_root_expr( }; let ConcreteTraitLongId { trait_id, generic_args } = concrete_trait_id.lookup_intern(ctx.db); + if trait_id == crate::corelib::fn_once_trait(ctx.db) { + ctx.are_closures_in_context = true; + } if trait_id != get_core_trait(ctx.db, CoreTraitContext::MetaProgramming, "TypeEqual".into()) { continue; @@ -1487,6 +1578,7 @@ fn compute_expr_closure_semantic( ctx: &mut ComputationContext<'_>, syntax: &ast::ExprClosure, ) -> Maybe { + ctx.are_closures_in_context = true; let syntax_db = ctx.db.upcast(); let (params, ret_ty, body) = ctx.run_in_subscope(|new_ctx| { let params = if let ClosureParamWrapper::NAry(params) = syntax.wrapper(syntax_db) { diff --git a/crates/cairo-lang-semantic/src/expr/test_data/closure b/crates/cairo-lang-semantic/src/expr/test_data/closure index d3cafe95925..6ec61886e48 100644 --- a/crates/cairo-lang-semantic/src/expr/test_data/closure +++ b/crates/cairo-lang-semantic/src/expr/test_data/closure @@ -270,3 +270,173 @@ error: Type mismatch: `core::felt252` and `core::integer::u128`. --> lib.cairo:4:10 fn foo() { ^ + +//! > ========================================================================== + +//! > Test closure calling. + +//! > test_runner_name +test_function_diagnostics(expect_diagnostics: false) + +//! > function +fn foo() { + let c = |a, b, c| { + a.into() + b.into() + c.into() + }; + let _f: u256 = c(2_felt252, 2_u256, 6_u64); +} + +//! > function_name +foo + +//! > module_code + +//! > expected_diagnostics + +//! > ========================================================================== + +//! > Test closure calling with diagnostics. + +//! > test_runner_name +test_function_diagnostics(expect_diagnostics: true) + +//! > function +fn foo() { + let c = |a, b, c| { + a.into() + b.into() + c.into() + }; + let _f: u32 = c(2_felt252, 2_u256, 6_u64); +} + +//! > function_name +foo + +//! > module_code + +//! > expected_diagnostics +error: Trait has no implementation in context: core::traits::Into::. + --> lib.cairo:3:22 + a.into() + b.into() + c.into() + ^**^ + +//! > ========================================================================== + +//! > Test shadowing of a function with a variable. + +//! > test_runner_name +test_function_diagnostics(expect_diagnostics: warnings_only) + +//! > function +fn foo() { + let bar: u8 = 3; + let _f: u32 = bar(2); +} + +//! > function_name +foo + +//! > module_code +fn bar() -> u32 { + 2 +} + +//! > expected_diagnostics +warning: Function `bar` is shadowed by a local variable. + --> lib.cairo:6:19 + let _f: u32 = bar(2); + ^*^ + +//! > ========================================================================== + +//! > Test shadowing of a function with a variable when closure are in context. + +//! > test_runner_name +test_function_diagnostics(expect_diagnostics: true) + +//! > function +fn foo() { + let _y: u16 = baz(|a| { + a.into() + }); +} + +//! > function_name +foo + +//! > module_code +fn bar() -> u32 { + 2 +} + +fn baz>(c: T) -> core::ops::FnOnce::::Output { + let bar = 3; + let _x: u32 = bar(); + c(9) +} + +//! > expected_diagnostics +error: Trait has no implementation in context: core::ops::function::FnOnce::. + --> lib.cairo:7:19 + let _x: u32 = bar(); + ^***^ + +error: Trait has no implementation in context: core::traits::Into::. + --> lib.cairo:12:11 + a.into() + ^**^ + +//! > ========================================================================== + +//! > Test shadowing of a function with a closure. + +//! > test_runner_name +test_function_diagnostics(expect_diagnostics: true) + +//! > function +fn foo() { + let bar = |_a, _b, _c| -> u32 { + 3 + }; + let _f: u32 = bar(2); +} + +//! > function_name +foo + +//! > module_code +fn bar(a: felt252) -> u32 { + 2 +} + +//! > expected_diagnostics +error: Type mismatch: `(?6,)` and `(?0, ?1, ?2)`. + --> lib.cairo:4:10 +fn foo() { + ^ + +//! > ========================================================================== + +//! > Closure ref argument. + +//! > test_runner_name +test_function_diagnostics(expect_diagnostics: true) + +//! > function +fn foo() { + let bar = |a| -> u32 { + a + }; + let a = 5; + let _f: u32 = bar(ref a); +} + +//! > function_name +foo + +//! > module_code + +//! > expected_diagnostics +error: Arguments to closure functions cannot be references + --> lib.cairo:6:23 + let _f: u32 = bar(ref a); + ^***^