Skip to content

Commit

Permalink
added calling expressions on closures (#6192)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomerStarkware authored Aug 18, 2024
1 parent 4304673 commit 6e5f2d6
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 2 deletions.
13 changes: 12 additions & 1 deletion crates/cairo-lang-semantic/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
}

Expand All @@ -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,
}
Expand Down Expand Up @@ -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.
Expand Down
94 changes: 93 additions & 1 deletion crates/cairo-lang-semantic/src/expr/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ pub struct ComputationContext<'ctx> {
pub semantic_defs: UnorderedHashMap<semantic::VarId, semantic::Variable>,
loop_ctx: Option<LoopContext>,
cfg_set: Arc<CfgSet>,
/// 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(
Expand Down Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -887,6 +975,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;
Expand Down Expand Up @@ -1486,6 +1577,7 @@ fn compute_expr_closure_semantic(
ctx: &mut ComputationContext<'_>,
syntax: &ast::ExprClosure,
) -> Maybe<Expr> {
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) {
Expand Down
170 changes: 170 additions & 0 deletions crates/cairo-lang-semantic/src/expr/test_data/closure
Original file line number Diff line number Diff line change
Expand Up @@ -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::<core::integer::u256, core::integer::u32>.
--> 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<T, +core::ops::FnOnce<T, (u32,)>>(c: T) -> core::ops::FnOnce::<T, (u32,)>::Output {
let bar = 3;
let _x: u32 = bar();
c(9)
}

//! > expected_diagnostics
error: Trait has no implementation in context: core::ops::function::FnOnce::<core::felt252, ()>.
--> lib.cairo:7:19
let _x: u32 = bar();
^***^

error: Trait has no implementation in context: core::traits::Into::<core::integer::u32, core::integer::u16>.
--> 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);
^***^

0 comments on commit 6e5f2d6

Please sign in to comment.