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

[move-compiler-v2] clean up a few remaining issues in lambda parser/front-end code #15365

Open
wants to merge 1 commit into
base: 09-27-add_parser_code_for_lambda_types
Choose a base branch
from
Open
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
74 changes: 56 additions & 18 deletions third_party/move/move-compiler-v2/src/bytecode_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Parts of the project are originally copyright © Meta Platforms, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::{experiments::Experiment, Options};
use codespan_reporting::diagnostic::Severity;
use ethnum::U256;
use itertools::Itertools;
Expand Down Expand Up @@ -324,6 +325,14 @@ impl<'env> Generator<'env> {
let loc = env.get_node_loc(id);
env.diag(severity, &loc, msg.as_ref())
}

fn check_if_lambdas_enabled(&self) -> bool {
let options = self
.env()
.get_extension::<Options>()
.expect("Options is available");
options.experiment_on(Experiment::LAMBDA_VALUES)
}
}

// ======================================================================================
Expand Down Expand Up @@ -480,14 +489,23 @@ impl<'env> Generator<'env> {
self.emit_with(*id, |attr| Bytecode::SpecBlock(attr, spec));
},
// TODO(LAMBDA)
ExpData::Lambda(id, _, _, _, _) => self.error(
ExpData::Lambda(id, _, _, _, _) =>
self.error(
*id,
"Function-typed values not yet supported except as parameters to calls to inline functions",
if self.check_if_lambdas_enabled() {
"Function-typed values not yet implemented except as parameters to calls to inline functions"
} else {
"Function-typed values not yet supported except as parameters to calls to inline functions"
}
),
// TODO(LAMBDA)
ExpData::Invoke(id, _exp, _) => self.error(
*id,
"Calls to function values other than inline function parameters not yet supported",
if self.check_if_lambdas_enabled() {
"Calls to function values other than inline function parameters not yet implemented"
} else {
"Calls to function values other than inline function parameters not yet supported"
}
),
ExpData::Quant(id, _, _, _, _, _) => {
self.internal_error(*id, "unsupported specification construct")
Expand Down Expand Up @@ -564,10 +582,16 @@ impl<'env> Generator<'env> {
Constant::Bool(false)
}
},
// TODO(LAMBDA)
Value::Function(_mid, _fid) => {
self.error(
id,
"Function-typed values not yet supported except as parameters to calls to inline functions");
if self.check_if_lambdas_enabled() {
"Function-typed values not yet implemented except as parameters to calls to inline functions"
} else {
"Function-typed values not yet supported except as parameters to calls to inline functions"
}
);
Constant::Bool(false)
},
}
Expand Down Expand Up @@ -794,7 +818,11 @@ impl<'env> Generator<'env> {
// TODO(LAMBDA)
Operation::EarlyBind => self.error(
id,
"Function-typed values not yet supported except as parameters to calls to inline functions",
if self.check_if_lambdas_enabled() {
"Function-typed values not yet implemented except as parameters to calls to inline functions"
} else {
"Function-typed values not yet supported except as parameters to calls to inline functions"
},
),
Operation::TestVariants(mid, sid, variants) => {
self.gen_test_variants(targets, id, mid.qualified(*sid), variants, args)
Expand Down Expand Up @@ -1336,9 +1364,12 @@ impl<'env> Generator<'env> {
};
self.gen_borrow_field_operation(id, borrow_dest, str, fields, oper_temp);
if need_read_ref {
self.emit_call(id, vec![target], BytecodeOperation::ReadRef, vec![
borrow_dest,
])
self.emit_call(
id,
vec![target],
BytecodeOperation::ReadRef,
vec![borrow_dest],
)
}
}

Expand Down Expand Up @@ -1526,10 +1557,13 @@ enum MatchMode {
impl MatchMode {
/// Whether this match is in probing mode.
fn is_probing(&self) -> bool {
matches!(self, MatchMode::Refutable {
probing_vars: Some(_),
..
})
matches!(
self,
MatchMode::Refutable {
probing_vars: Some(_),
..
}
)
}

/// Whether a variable appearing in the pattern should be bound to a temporary.
Expand Down Expand Up @@ -1657,9 +1691,12 @@ impl<'env> Generator<'env> {
ReferenceKind::Immutable,
Box::new(value_ty.clone()),
));
self.emit_call(id, vec![value_ref], BytecodeOperation::BorrowLoc, vec![
value,
]);
self.emit_call(
id,
vec![value_ref],
BytecodeOperation::BorrowLoc,
vec![value],
);
needs_probing = true;
value_ref
}
Expand Down Expand Up @@ -1781,10 +1818,11 @@ impl<'env> Generator<'env> {
),
);
return Some(
ExpData::Call(id, Operation::Deref, vec![ExpData::LocalVar(
new_id, var,
ExpData::Call(
id,
Operation::Deref,
vec![ExpData::LocalVar(new_id, var).into_exp()],
)
.into_exp()])
.into_exp(),
);
}
Expand Down
78 changes: 61 additions & 17 deletions third_party/move/move-compiler-v2/src/env_pipeline/lambda_lifter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
//! not modify free variables.
//!
//! Lambda lifting rewrites lambda expressions into construction
//! of *closures*. A closure refers to a function and contains a partial list
//! of arguments for that function, essentially currying it. We use the
//! of *closures* using the `EarlyBind` operation. A closure refers to a function and contains a list
//! of "early bound" leading arguments for that function, essentially currying it. We use the
//! `EarlyBind` operation to construct a closure from a function and set of arguemnts,
//! which must be the first `k` arguments to the function argument list.
//!
Expand Down Expand Up @@ -204,7 +204,9 @@ impl<'a> LambdaLifter<'a> {
/// - `closure_args` = corresponding expressions to provide as actual arg for each param
/// - `param_index_mapping` = for each free var which is a Parameter from the enclosing function,
/// a mapping from index there to index in the params list
fn get_params_for_freevars(&mut self) -> (Vec<Parameter>, Vec<Exp>, BTreeMap<usize, usize>) {
fn get_params_for_freevars(
&mut self,
) -> Option<(Vec<Parameter>, Vec<Exp>, BTreeMap<usize, usize>)> {
let env = self.fun_env.module_env.env;
let mut closure_args = vec![];

Expand All @@ -213,6 +215,9 @@ impl<'a> LambdaLifter<'a> {
// functions (courtesy of #12317)
let mut param_index_mapping = BTreeMap::new();
let mut params = vec![];
let ty_params = self.fun_env.get_type_parameters_ref();
let ability_inferer = AbilityInferer::new(env, ty_params);
let mut saw_error = false;

for (used_param_count, (param, var_info)) in
mem::take(&mut self.free_params).into_iter().enumerate()
Expand All @@ -229,6 +234,18 @@ impl<'a> LambdaLifter<'a> {
name.display(env.symbol_pool())
),
);
saw_error = true;
}
let param_abilities = ability_inferer.infer_abilities(&ty).1;
if !param_abilities.has_copy() {
env.error(
&loc,
&format!(
"captured variable `{}` must have a value with `copy` ability", // TODO(LAMBDA)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a test case for this error?

name.display(env.symbol_pool())
),
);
saw_error = true;
}
params.push(Parameter(name, ty.clone(), loc.clone()));
let new_id = env.new_node(loc, ty);
Expand All @@ -252,6 +269,7 @@ impl<'a> LambdaLifter<'a> {
name.display(env.symbol_pool())
),
);
saw_error = true;
}
params.push(Parameter(name, ty.clone(), loc.clone()));
let new_id = env.new_node(loc, ty);
Expand All @@ -261,7 +279,11 @@ impl<'a> LambdaLifter<'a> {
closure_args.push(ExpData::LocalVar(new_id, name).into_exp())
}

(params, closure_args, param_index_mapping)
if !saw_error {
Some((params, closure_args, param_index_mapping))
} else {
None
}
}

fn get_arg_if_simple(arg: &Exp) -> Option<&Exp> {
Expand Down Expand Up @@ -606,10 +628,13 @@ impl<'a> ExpRewriterFunctions for LambdaLifter<'a> {

fn rewrite_assign(&mut self, _node_id: NodeId, lhs: &Pattern, _rhs: &Exp) -> Option<Exp> {
for (node_id, name) in lhs.vars() {
self.free_locals.insert(name, VarInfo {
node_id,
modified: true,
});
self.free_locals.insert(
name,
VarInfo {
node_id,
modified: true,
},
);
}
None
}
Expand All @@ -618,16 +643,22 @@ impl<'a> ExpRewriterFunctions for LambdaLifter<'a> {
if matches!(oper, Operation::Borrow(ReferenceKind::Mutable)) {
match args[0].as_ref() {
ExpData::LocalVar(node_id, name) => {
self.free_locals.insert(*name, VarInfo {
node_id: *node_id,
modified: true,
});
self.free_locals.insert(
*name,
VarInfo {
node_id: *node_id,
modified: true,
},
);
},
ExpData::Temporary(node_id, param) => {
self.free_params.insert(*param, VarInfo {
node_id: *node_id,
modified: true,
});
self.free_params.insert(
*param,
VarInfo {
node_id: *node_id,
modified: true,
},
);
},
_ => {},
}
Expand Down Expand Up @@ -669,7 +700,11 @@ impl<'a> ExpRewriterFunctions for LambdaLifter<'a> {
// param_index_mapping = for each free var which is a Parameter from the enclosing function,
// a mapping from index there to index in the params list; other free vars are
// substituted automatically by using the same symbol for the param
let (mut params, mut closure_args, param_index_mapping) = self.get_params_for_freevars();
let Some((mut params, mut closure_args, param_index_mapping)) =
self.get_params_for_freevars()
else {
return None;
};

// Some(ExpData::Invalid(env.clone_node(id)).into_exp());
// Add lambda args. For dealing with patterns in lambdas (`|S{..}|e`) we need
Expand Down Expand Up @@ -732,6 +767,15 @@ impl<'a> ExpRewriterFunctions for LambdaLifter<'a> {
let body = ExpRewriter::new(env, &mut replacer).rewrite_exp(body.clone());
let fun_id = FunId::new(fun_name);
let params_types = params.iter().map(|param| param.get_type()).collect();
if abilities.has_store() {
let loc = env.get_node_loc(id);
env.error(
&loc,
// TODO(LAMBDA)
"Lambdas expressions with `store` ability currently may only be a simple call to an existing `public` function. This lambda expression requires defining a `public` helper function, which might affect module upgradeability and is not yet supported."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message looks a little bit confusing for users (e.g what is a helper function and how it may affect upgradeability). Maybe just remove requires defining a public helper function, which might affect module upgradeability` and just saying it is not supported yet?

);
return None;
};
self.lifted.push(ClosureFunction {
loc: lambda_loc.clone(),
fun_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ impl ModuleGenerator {
ctx.error(
loc,
format!(
"Unexpected type: {}",
"Unimplemented type: {}",
ty.display(&ctx.env.get_type_display_ctx())
),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

Diagnostics:
warning: Unused parameter `f`. Consider removing or prefixing with an underscore: `_f`
┌─ tests/checking/inlining/function_name_shadowing.move:8:28
8 │ public inline fun quux(f:|u64, u64|u64, a: u64, b: u64): u64 {
│ ^

// -- Model dump before bytecode pipeline
module 0x42::Test {
public fun f(a: u64,b: u64): u64 {
Mul<u64>(a, b)
}
public inline fun quux(f: |(u64, u64)|u64,a: u64,b: u64): u64 {
Test::f(a, b)
}
public fun test_shadowing(): u64 {
Test::f(10, 2)
}
} // end 0x42::Test

// -- Sourcified model before bytecode pipeline
module 0x42::Test {
public fun f(a: u64, b: u64): u64 {
a * b
}
public inline fun quux(f: |(u64, u64)|u64, a: u64, b: u64): u64 {
f(a, b)
}
public fun test_shadowing(): u64 {
f(10, 2)
}
}


============ bytecode verification succeeded ========
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//# publish
module 0x42::Test {

public fun f(a: u64, b: u64): u64 {
a * b
}

public inline fun quux(f:|u64, u64|u64, a: u64, b: u64): u64 {
f(a, b)
}

public fun test_shadowing(): u64 {
quux(|a, b| a - b, 10, 2)
}
}

//# run 0x42::Test::test_shadowing
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,3 @@ error: tuple type `()` is not allowed as a type argument (type was inferred)
│ ^
= required by instantiating type parameter `T` of function `foreach`

error: function type `|u64|u64` is not allowed as a field type
┌─ tests/checking/typing/lambda.move:81:12
81 │ f: |u64|u64, // expected lambda not allowed
│ ^^^^^^^^
= required by declaration of field `f`
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,3 @@ error: cannot pass `|&u64|u64 with copy+drop+store` to a function which expects
73 │ foreach(&v, |e: &u64| { sum = sum + *e; *e }) // expected to have wrong result type of lambda
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: function type `|u64|u64` is not allowed as a field type
┌─ tests/checking/typing/lambda_typed.move:81:12
81 │ f: |u64|u64, // expected lambda not allowed
│ ^^^^^^^^
= required by declaration of field `f`
Original file line number Diff line number Diff line change
Expand Up @@ -532,13 +532,13 @@ module 0x8675309::M {


Diagnostics:
error: Calls to function values other than inline function parameters not yet supported
error: Calls to function values other than inline function parameters not yet implemented
┌─ tests/lambda/inline-parity/subtype_args.move:24:9
24 │ f(&mut 0, &mut 0);
│ ^^^^^^^^^^^^^^^^^

error: Calls to function values other than inline function parameters not yet supported
error: Calls to function values other than inline function parameters not yet implemented
┌─ tests/lambda/inline-parity/subtype_args.move:25:9
25 │ f(&0, &mut 0);
Expand Down
Loading
Loading