Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added calling expressions on closures #6192

Merged
merged 1 commit into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
^***^