From b3c11066aef2535af18ab8dac8dedcb3cf2c0ad2 Mon Sep 17 00:00:00 2001 From: "Brian R. Murphy" <132495859+brmataptos@users.noreply.github.com> Date: Tue, 1 Oct 2024 22:22:16 -0700 Subject: [PATCH] Add LambdaCaptureKind to allow specifying move/copy/borrow treatment for free variables. Tweak simplifier to *not* remove lambda values (even though they are now ) to avoid losing some errors/warnings. --- .../src/bytecode_generator.rs | 40 ++- .../src/env_pipeline/ast_simplifier.rs | 5 +- .../env_pipeline/flow_insensitive_checkers.rs | 2 +- .../src/env_pipeline/function_checker.rs | 2 +- .../src/env_pipeline/inliner.rs | 2 +- .../src/env_pipeline/lambda_lifter.rs | 54 ++-- .../move-compiler/src/attr_derivation/mod.rs | 5 +- .../move/move-compiler/src/expansion/ast.rs | 63 +++-- .../src/expansion/dependency_ordering.rs | 6 +- .../move-compiler/src/expansion/translate.rs | 202 +++++++-------- .../move-compiler/src/naming/translate.rs | 14 +- .../move/move-compiler/src/parser/ast.rs | 88 +++++-- .../move/move-compiler/src/parser/syntax.rs | 120 ++++++--- .../src/unit_test/filter_test_members.rs | 1 + third_party/move/move-model/src/ast.rs | 52 +++- .../move-model/src/builder/exp_builder.rs | 231 ++++++++++++------ .../move-model/src/builder/module_builder.rs | 18 +- .../move/move-model/src/exp_rewriter.rs | 51 ++-- 18 files changed, 583 insertions(+), 373 deletions(-) diff --git a/third_party/move/move-compiler-v2/src/bytecode_generator.rs b/third_party/move/move-compiler-v2/src/bytecode_generator.rs index 474f8e7585798..e604adc1a28a2 100644 --- a/third_party/move/move-compiler-v2/src/bytecode_generator.rs +++ b/third_party/move/move-compiler-v2/src/bytecode_generator.rs @@ -480,7 +480,7 @@ 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", ), @@ -814,7 +814,7 @@ impl<'env> Generator<'env> { Operation::NoOp => {}, // do nothing // TODO(LAMBDA) - Operation::Closure(..) => self.error( + Operation::Closure => self.error( id, "Function-typed values not yet supported except as parameters to calls to inline functions", ), @@ -1331,12 +1331,9 @@ 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, + ]) } } @@ -1524,13 +1521,10 @@ 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. @@ -1658,12 +1652,9 @@ 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 } @@ -1785,11 +1776,10 @@ impl<'env> Generator<'env> { ), ); return Some( - ExpData::Call( - id, - Operation::Deref, - vec![ExpData::LocalVar(new_id, var).into_exp()], + ExpData::Call(id, Operation::Deref, vec![ExpData::LocalVar( + new_id, var, ) + .into_exp()]) .into_exp(), ); } diff --git a/third_party/move/move-compiler-v2/src/env_pipeline/ast_simplifier.rs b/third_party/move/move-compiler-v2/src/env_pipeline/ast_simplifier.rs index 6b679c8da1525..62c9a6cf63d12 100644 --- a/third_party/move/move-compiler-v2/src/env_pipeline/ast_simplifier.rs +++ b/third_party/move/move-compiler-v2/src/env_pipeline/ast_simplifier.rs @@ -359,7 +359,7 @@ fn find_possibly_modified_vars( _ => {}, } }, - Lambda(node_id, pat, _, _) => { + Lambda(node_id, pat, _, _, _) => { // Define a new scope for bound vars, and turn off `modifying` within. match pos { VisitorPosition::Pre => { @@ -978,7 +978,8 @@ impl<'env> ExpRewriterFunctions for SimplifierRewriter<'env> { let ability_set = self .env() .type_abilities(&ty, self.func_env.get_type_parameters_ref()); - ability_set.has_ability(Ability::Drop) + // Don't drop a function-valued expression so we don't lose errors. + !ty.has_function() && ability_set.has_ability(Ability::Drop) } else { // We're missing type info, be conservative false diff --git a/third_party/move/move-compiler-v2/src/env_pipeline/flow_insensitive_checkers.rs b/third_party/move/move-compiler-v2/src/env_pipeline/flow_insensitive_checkers.rs index 07cd34081d3a7..72bbd295d5853 100644 --- a/third_party/move/move-compiler-v2/src/env_pipeline/flow_insensitive_checkers.rs +++ b/third_party/move/move-compiler-v2/src/env_pipeline/flow_insensitive_checkers.rs @@ -147,7 +147,7 @@ impl<'env, 'params> SymbolVisitor<'env, 'params> { Pre | Post | BeforeBody | MidMutate | BeforeThen | BeforeElse | PreSequenceValue => {}, }, - Lambda(_, pat, _, _) => { + Lambda(_, pat, _, _, _) => { match position { Pre => self.seen_uses.enter_scope(), Post => { diff --git a/third_party/move/move-compiler-v2/src/env_pipeline/function_checker.rs b/third_party/move/move-compiler-v2/src/env_pipeline/function_checker.rs index 3a3987ca63c63..72c2fe7ba0c1b 100644 --- a/third_party/move/move-compiler-v2/src/env_pipeline/function_checker.rs +++ b/third_party/move/move-compiler-v2/src/env_pipeline/function_checker.rs @@ -270,7 +270,7 @@ fn check_privileged_operations_on_structs(env: &GlobalEnv, fun_env: &FunctionEnv }, ExpData::Assign(_, pat, _) | ExpData::Block(_, pat, _, _) - | ExpData::Lambda(_, pat, _, _) => { + | ExpData::Lambda(_, pat, _, _, _) => { pat.visit_pre_post(&mut |_, pat| { if let Pattern::Struct(id, str, _, _) = pat { let module_id = str.module_id; diff --git a/third_party/move/move-compiler-v2/src/env_pipeline/inliner.rs b/third_party/move/move-compiler-v2/src/env_pipeline/inliner.rs index 98928dd2195ba..db24783b9a12e 100644 --- a/third_party/move/move-compiler-v2/src/env_pipeline/inliner.rs +++ b/third_party/move/move-compiler-v2/src/env_pipeline/inliner.rs @@ -1156,7 +1156,7 @@ impl<'env, 'rewriter> ExpRewriterFunctions for InlinedRewriter<'env, 'rewriter> }; let call_loc = self.env.get_node_loc(id); if let Some(lambda_target) = optional_lambda_target { - if let ExpData::Lambda(_, pat, body, _) = lambda_target.as_ref() { + if let ExpData::Lambda(_, pat, body, _, _) = lambda_target.as_ref() { let args_vec: Vec = args.to_vec(); Some(InlinedRewriter::construct_inlined_call_expression( self.env, diff --git a/third_party/move/move-compiler-v2/src/env_pipeline/lambda_lifter.rs b/third_party/move/move-compiler-v2/src/env_pipeline/lambda_lifter.rs index eb70934815c3b..e3944f2094c01 100644 --- a/third_party/move/move-compiler-v2/src/env_pipeline/lambda_lifter.rs +++ b/third_party/move/move-compiler-v2/src/env_pipeline/lambda_lifter.rs @@ -40,10 +40,9 @@ //! ``` use itertools::Itertools; -use move_binary_format::file_format::AbilitySet; -use move_binary_format::file_format::Visibility; +use move_binary_format::file_format::{AbilitySet, Visibility}; use move_model::{ - ast::{Exp, ExpData, Operation, Pattern, TempIndex}, + ast::{Exp, ExpData, LambdaCaptureKind, Operation, Pattern, TempIndex}, exp_rewriter::{ExpRewriter, ExpRewriterFunctions, RewriteTarget}, model::{FunId, FunctionEnv, GlobalEnv, Loc, NodeId, Parameter, TypeParameter}, symbol::Symbol, @@ -260,13 +259,10 @@ impl<'a> ExpRewriterFunctions for LambdaLifter<'a> { fn rewrite_assign(&mut self, _node_id: NodeId, lhs: &Pattern, _rhs: &Exp) -> Option { 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 } @@ -275,22 +271,16 @@ 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, + }); }, _ => {}, } @@ -303,6 +293,7 @@ impl<'a> ExpRewriterFunctions for LambdaLifter<'a> { id: NodeId, pat: &Pattern, body: &Exp, + capture_kind: LambdaCaptureKind, _abilities: AbilitySet, // TODO(LAMBDA): do something with this ) -> Option { if self.exempted_lambdas.contains(&id) { @@ -315,6 +306,21 @@ impl<'a> ExpRewriterFunctions for LambdaLifter<'a> { // parameter indices in the lambda context to indices in the lifted // functions (courtesy of #12317) let mut param_index_mapping = BTreeMap::new(); + match capture_kind { + LambdaCaptureKind::Default => { + // OK. + }, + LambdaCaptureKind::Move | LambdaCaptureKind::Copy | LambdaCaptureKind::Borrow => { + let loc = env.get_node_loc(id); + env.error( + &loc, + &format!( + "Lambda function `{}` of free variables not yet supported.", // TODO(LAMBDA) + capture_kind + ), + ); + }, + }; for (used_param_count, (param, var_info)) in mem::take(&mut self.free_params).into_iter().enumerate() { diff --git a/third_party/move/move-compiler/src/attr_derivation/mod.rs b/third_party/move/move-compiler/src/attr_derivation/mod.rs index b007e2356d1a1..a12889a76a77d 100644 --- a/third_party/move/move-compiler/src/attr_derivation/mod.rs +++ b/third_party/move/move-compiler/src/attr_derivation/mod.rs @@ -218,7 +218,10 @@ pub(crate) fn new_full_name( /// Helper to create a call exp. pub(crate) fn new_call_exp(loc: Loc, fun: NameAccessChain, args: Vec) -> Exp { - sp(loc, Exp_::Call(fun, CallKind::Regular, None, sp(loc, args))) + sp( + loc, + Exp_::Call(fun, CallKind::Regular, None, sp(loc, args), false), + ) } pub(crate) fn new_borrow_exp(loc: Loc, arg: Exp) -> Exp { diff --git a/third_party/move/move-compiler/src/expansion/ast.rs b/third_party/move/move-compiler/src/expansion/ast.rs index e612069665010..5b268fde2e5d8 100644 --- a/third_party/move/move-compiler/src/expansion/ast.rs +++ b/third_party/move/move-compiler/src/expansion/ast.rs @@ -6,8 +6,8 @@ use crate::{ expansion::translate::is_valid_struct_constant_or_schema_name, parser::ast::{ self as P, Ability, Ability_, BinOp, CallKind, ConstantName, Field, FunctionName, - ModuleName, QuantKind, SpecApplyPattern, StructName, UnaryOp, UseDecl, Var, VariantName, - ENTRY_MODIFIER, + LambdaCaptureKind, ModuleName, QuantKind, SpecApplyPattern, StructName, UnaryOp, UseDecl, + Var, VariantName, ENTRY_MODIFIER, }, shared::{ ast_debug::*, @@ -498,8 +498,18 @@ pub enum Exp_ { Copy(Var), Name(ModuleAccess, Option>), - Call(ModuleAccess, CallKind, Option>, Spanned>), - ExpCall(Box, Spanned>), + Call( + ModuleAccess, + CallKind, + Option>, + Spanned>, + bool, // ends in ".." + ), + ExpCall( + Box, + Spanned>, + bool, // ends in ".." + ), Pack(ModuleAccess, Option>, Fields), Vector(Loc, Option>, Spanned>), @@ -508,7 +518,7 @@ pub enum Exp_ { While(Box, Box), Loop(Box), Block(Sequence), - Lambda(TypedLValueList, Box, AbilitySet), + Lambda(TypedLValueList, Box, LambdaCaptureKind, AbilitySet), Quant( QuantKind, LValueWithRangeList, @@ -901,16 +911,12 @@ impl fmt::Display for ModuleAccess_ { impl fmt::Display for Visibility { fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { - write!( - f, - "{}", - match &self { - Visibility::Public(_) => Visibility::PUBLIC, - Visibility::Package(_) => Visibility::PACKAGE, - Visibility::Friend(_) => Visibility::FRIEND, - Visibility::Internal => Visibility::INTERNAL, - } - ) + write!(f, "{}", match &self { + Visibility::Public(_) => Visibility::PUBLIC, + Visibility::Package(_) => Visibility::PACKAGE, + Visibility::Friend(_) => Visibility::FRIEND, + Visibility::Internal => Visibility::INTERNAL, + }) } } @@ -1079,11 +1085,13 @@ impl AstDebug for ModuleDefinition { w.writeln(&format!("{}", n)) } attributes.ast_debug(w); - w.writeln(if *is_source_module { - "source module" - } else { - "library module" - }); + w.writeln( + if *is_source_module { + "source module" + } else { + "library module" + }, + ); w.writeln(&format!("dependency order #{}", dependency_order)); for (mident, neighbor) in immediate_neighbors.key_cloned_iter() { w.write(&format!("{} {};", neighbor, mident)); @@ -1612,7 +1620,7 @@ impl AstDebug for Exp_ { w.write(">"); } }, - E::Call(ma, kind, tys_opt, sp!(_, rhs)) => { + E::Call(ma, kind, tys_opt, sp!(_, rhs), ends_in_dotdot) => { ma.ast_debug(w); w.write(kind.to_string()); if let Some(ss) = tys_opt { @@ -1622,12 +1630,18 @@ impl AstDebug for Exp_ { } w.write("("); w.comma(rhs, |w, e| e.ast_debug(w)); + if ends_in_dotdot { + w.write(".."); + } w.write(")"); }, - E::ExpCall(fexp, sp!(_, rhs)) => { + E::ExpCall(fexp, sp!(_, rhs), ends_in_dotdot) => { fexp.ast_debug(w); w.write("("); w.comma(rhs, |w, e| e.ast_debug(w)); + if ends_in_dotdot { + w.write(".."); + } w.write(")"); }, E::Pack(ma, tys_opt, fields) => { @@ -1689,7 +1703,10 @@ impl AstDebug for Exp_ { } }, E::Block(seq) => w.block(|w| seq.ast_debug(w)), - E::Lambda(sp!(_, bs), e, abilities) => { + E::Lambda(sp!(_, bs), e, capture_kind, abilities) => { + if *capture_kind != LambdaCaptureKind::Default { + w.write(format!(" {}", capture_kind)); + } w.write("|"); bs.ast_debug(w); w.write("|"); diff --git a/third_party/move/move-compiler/src/expansion/dependency_ordering.rs b/third_party/move/move-compiler/src/expansion/dependency_ordering.rs index 25a02e0a1b68e..c1414de40c38f 100644 --- a/third_party/move/move-compiler/src/expansion/dependency_ordering.rs +++ b/third_party/move/move-compiler/src/expansion/dependency_ordering.rs @@ -446,12 +446,12 @@ fn exp(context: &mut Context, sp!(_loc, e_): &E::Exp) { module_access(context, ma); types_opt(context, tys_opt) }, - E::Call(ma, _is_macro, tys_opt, sp!(_, args_)) => { + E::Call(ma, _is_macro, tys_opt, sp!(_, args_), _ends_in_dotdot) => { module_access(context, ma); types_opt(context, tys_opt); args_.iter().for_each(|e| exp(context, e)) }, - E::ExpCall(fexp, sp!(_, args_)) => { + E::ExpCall(fexp, sp!(_, args_), _ends_in_dotdot) => { exp(context, fexp); args_.iter().for_each(|e| exp(context, e)) }, @@ -516,7 +516,7 @@ fn exp(context: &mut Context, sp!(_loc, e_): &E::Exp) { tys.iter().for_each(|ty| type_(context, ty)) }, - E::Lambda(ll, e, _abilities) => { + E::Lambda(ll, e, _capture_kind, _abilities) => { use crate::expansion::ast::TypedLValue_; let mapped = ll.value.iter().map(|sp!(_, TypedLValue_(lv, _opt_ty))| lv); lvalues(context, mapped); diff --git a/third_party/move/move-compiler/src/expansion/translate.rs b/third_party/move/move-compiler/src/expansion/translate.rs index 6e0a2e01b076a..5197985132bd6 100644 --- a/third_party/move/move-compiler/src/expansion/translate.rs +++ b/third_party/move/move-compiler/src/expansion/translate.rs @@ -816,20 +816,17 @@ fn attribute( ) -> Option { use E::Attribute_ as EA; use P::Attribute_ as PA; - Some(sp( - loc, - match attribute_ { - PA::Name(n) => EA::Name(n), - PA::Assigned(n, v) => EA::Assigned(n, Box::new(attribute_value(context, *v)?)), - PA::Parameterized(n, sp!(_, pattrs_)) => { - let attrs = pattrs_ - .into_iter() - .map(|a| attribute(context, attr_position, a)) - .collect::>>()?; - EA::Parameterized(n, unique_attributes(context, attr_position, true, attrs)) - }, + Some(sp(loc, match attribute_ { + PA::Name(n) => EA::Name(n), + PA::Assigned(n, v) => EA::Assigned(n, Box::new(attribute_value(context, *v)?)), + PA::Parameterized(n, sp!(_, pattrs_)) => { + let attrs = pattrs_ + .into_iter() + .map(|a| attribute(context, attr_position, a)) + .collect::>>()?; + EA::Parameterized(n, unique_attributes(context, attr_position, true, attrs)) }, - )) + })) } fn check_module_name(context: &mut Context, ident_loc: &Loc, mident: &ModuleIdent) { @@ -852,48 +849,45 @@ fn attribute_value( ) -> Option { use E::AttributeValue_ as EV; use P::{AttributeValue_ as PV, LeadingNameAccess_ as LN, NameAccessChain_ as PN}; - Some(sp( - loc, - match avalue_ { - PV::Value(v) => EV::Value(value(context, v)?), - PV::ModuleAccess(sp!(ident_loc, PN::Two(sp!(aloc, LN::AnonymousAddress(a)), n))) => { - let addr = Address::Numerical(None, sp(aloc, a)); - let mident = sp(ident_loc, ModuleIdent_::new(addr, ModuleName(n))); - check_module_name(context, &ident_loc, &mident); - EV::Module(mident) - }, - // bit wonky, but this is the only spot currently where modules and expressions exist - // in the same namespace. - // TODO consider if we want to just force all of these checks into the well-known - // attribute setup - PV::ModuleAccess(sp!(ident_loc, PN::One(n))) - if context.aliases.module_alias_get(&n).is_some() => - { - let sp!(_, mident_) = context.aliases.module_alias_get(&n).unwrap(); - let mident = sp(ident_loc, mident_); - check_module_name(context, &ident_loc, &mident); - EV::Module(mident) - }, - PV::ModuleAccess(sp!(ident_loc, PN::Two(sp!(aloc, LN::Name(n1)), n2))) - if context - .named_address_mapping - .as_ref() - .map(|m| m.contains_key(&n1.value)) - .unwrap_or(false) => - { - let addr = address(context, false, sp(aloc, LN::Name(n1))); - let mident = sp(ident_loc, ModuleIdent_::new(addr, ModuleName(n2))); - check_module_name(context, &ident_loc, &mident); - EV::Module(mident) - }, - PV::ModuleAccess(ma) => EV::ModuleAccess(name_access_chain( - context, - Access::Type, - ma, - Some(DeprecatedItem::Module), - )?), + Some(sp(loc, match avalue_ { + PV::Value(v) => EV::Value(value(context, v)?), + PV::ModuleAccess(sp!(ident_loc, PN::Two(sp!(aloc, LN::AnonymousAddress(a)), n))) => { + let addr = Address::Numerical(None, sp(aloc, a)); + let mident = sp(ident_loc, ModuleIdent_::new(addr, ModuleName(n))); + check_module_name(context, &ident_loc, &mident); + EV::Module(mident) + }, + // bit wonky, but this is the only spot currently where modules and expressions exist + // in the same namespace. + // TODO consider if we want to just force all of these checks into the well-known + // attribute setup + PV::ModuleAccess(sp!(ident_loc, PN::One(n))) + if context.aliases.module_alias_get(&n).is_some() => + { + let sp!(_, mident_) = context.aliases.module_alias_get(&n).unwrap(); + let mident = sp(ident_loc, mident_); + check_module_name(context, &ident_loc, &mident); + EV::Module(mident) + }, + PV::ModuleAccess(sp!(ident_loc, PN::Two(sp!(aloc, LN::Name(n1)), n2))) + if context + .named_address_mapping + .as_ref() + .map(|m| m.contains_key(&n1.value)) + .unwrap_or(false) => + { + let addr = address(context, false, sp(aloc, LN::Name(n1))); + let mident = sp(ident_loc, ModuleIdent_::new(addr, ModuleName(n2))); + check_module_name(context, &ident_loc, &mident); + EV::Module(mident) }, - )) + PV::ModuleAccess(ma) => EV::ModuleAccess(name_access_chain( + context, + Access::Type, + ma, + Some(DeprecatedItem::Module), + )?), + })) } //************************************************************************************************** @@ -971,13 +965,10 @@ fn record_module_member_info( attributes: &[P::Attributes], member_kind: ModuleMemberKind, ) { - cur_members.insert( - *name, - ModuleMemberInfo { - kind: member_kind, - deprecation: deprecated_attribute_location(attributes), - }, - ); + cur_members.insert(*name, ModuleMemberInfo { + kind: member_kind, + deprecation: deprecated_attribute_location(attributes), + }); } /// Record ModuleMemberInfo about a specified member name, skipping @@ -987,13 +978,10 @@ fn record_module_member_info_without_deprecation( name: &Spanned, member_kind: ModuleMemberKind, ) { - cur_members.insert( - *name, - ModuleMemberInfo { - kind: member_kind, - deprecation: None, - }, - ); + cur_members.insert(*name, ModuleMemberInfo { + kind: member_kind, + deprecation: None, + }); } /// Specified module with identifier mident and definition m, @@ -1039,14 +1027,11 @@ fn module_members( ); }, P::ModuleMember::Spec( - sp!( - _, - SB { - target, - members, - .. - } - ), + sp!(_, SB { + target, + members, + .. + }), ) => match &target.value { SBT::Schema(n, _) => { record_module_member_info_without_deprecation( @@ -1124,14 +1109,11 @@ fn aliases_from_member( Some(P::ModuleMember::Struct(s)) }, P::ModuleMember::Spec(s) => { - let sp!( - _, - SB { - target, - members, - .. - } - ) = &s; + let sp!(_, SB { + target, + members, + .. + }) = &s; match &target.value { SBT::Schema(n, _) => { check_name_and_add_implicit_alias!(ModuleMemberKind::Schema, *n); @@ -1769,18 +1751,15 @@ fn access_specifier(context: &mut Context, specifier: P::AccessSpecifier) -> E:: access_specifier_name_access_chain(context, chain); let type_args = optional_types(context, type_args); let address = address_specifier(context, address); - sp( - specifier.loc, - E::AccessSpecifier_ { - kind, - negated, - module_address, - module_name, - resource_name, - type_args, - address, - }, - ) + sp(specifier.loc, E::AccessSpecifier_ { + kind, + negated, + module_address, + module_name, + resource_name, + type_args, + address, + }) } fn access_specifier_name_access_chain( @@ -1989,14 +1968,11 @@ fn spec(context: &mut Context, sp!(loc, pspec): P::SpecBlock) -> E::SpecBlock { context.set_to_outer_scope(old_aliases); context.in_spec_context = false; - sp( - loc, - E::SpecBlock_ { - attributes, - target: spec_target(context, target), - members, - }, - ) + sp(loc, E::SpecBlock_ { + attributes, + target: spec_target(context, target), + members, + }) } fn spec_target(context: &mut Context, sp!(loc, pt): P::SpecBlockTarget) -> E::SpecBlockTarget { @@ -2561,7 +2537,7 @@ fn exp_(context: &mut Context, sp!(loc, pe_): P::Exp) -> E::Exp { }, } }, - PE::Call(pn, kind, ptys_opt, sp!(rloc, prs)) => { + PE::Call(pn, kind, ptys_opt, sp!(rloc, prs), ends_in_dotdot) => { let tys_opt = optional_types(context, ptys_opt); let ers = sp(rloc, exps(context, prs)); let en_opt = if kind != CallKind::Receiver { @@ -2579,17 +2555,17 @@ fn exp_(context: &mut Context, sp!(loc, pe_): P::Exp) -> E::Exp { Some(E::ModuleAccess::new(pn.loc, E::ModuleAccess_::Name(name))) }; match en_opt { - Some(en) => EE::Call(en, kind, tys_opt, ers), + Some(en) => EE::Call(en, kind, tys_opt, ers, ends_in_dotdot), None => { assert!(context.env.has_errors()); EE::UnresolvedError }, } }, - PE::ExpCall(boxed_fexp, sp!(rloc, args)) => { + PE::ExpCall(boxed_fexp, sp!(rloc, args), ends_in_dotdot) => { let e_fexp = exp(context, *boxed_fexp); let e_args = sp(rloc, exps(context, args)); - EE::ExpCall(e_fexp, e_args) + EE::ExpCall(e_fexp, e_args, ends_in_dotdot) }, PE::Pack(pn, ptys_opt, pfields) => { let en_opt = name_access_chain( @@ -2644,12 +2620,12 @@ fn exp_(context: &mut Context, sp!(loc, pe_): P::Exp) -> E::Exp { PE::While(pb, ploop) => EE::While(exp(context, *pb), exp(context, *ploop)), PE::Loop(ploop) => EE::Loop(exp(context, *ploop)), PE::Block(seq) => EE::Block(sequence(context, loc, seq)), - PE::Lambda(pbs, pe, abilities_vec) => { + PE::Lambda(pbs, pe, capture_kind, abilities_vec) => { let tbs_opt = typed_bind_list(context, pbs); let e = exp_(context, *pe); let abilities = ability_set(context, "lambda expression", abilities_vec); match tbs_opt { - Some(tbs) => EE::Lambda(tbs, Box::new(e), abilities), + Some(tbs) => EE::Lambda(tbs, Box::new(e), capture_kind, abilities), None => { assert!(context.env.has_errors()); EE::UnresolvedError @@ -3336,7 +3312,7 @@ fn unbound_names_exp(unbound: &mut UnboundNames, sp!(_, e_): &E::Exp) { EE::Name(sp!(_, E::ModuleAccess_::Name(n)), _) => { unbound.vars.insert(*n); }, - EE::Call(sp!(_, ma_), _, _, sp!(_, es_)) => { + EE::Call(sp!(_, ma_), _, _, sp!(_, es_), _ends_in_dotdot) => { match ma_ { // capture the case of calling a lambda / function pointer // NOTE: this also captures calls to built-in move and spec functions @@ -3348,7 +3324,7 @@ fn unbound_names_exp(unbound: &mut UnboundNames, sp!(_, e_): &E::Exp) { } unbound_names_exps(unbound, es_); }, - EE::ExpCall(fexp, sp!(_, es_)) => { + EE::ExpCall(fexp, sp!(_, es_), _ends_in_dotdot) => { unbound_names_exp(unbound, &fexp); unbound_names_exps(unbound, es_); }, @@ -3376,7 +3352,7 @@ fn unbound_names_exp(unbound: &mut UnboundNames, sp!(_, e_): &E::Exp) { EE::Loop(eloop) => unbound_names_exp(unbound, eloop), EE::Block(seq) => unbound_names_sequence(unbound, seq), - EE::Lambda(ls, er, _abilities) => { + EE::Lambda(ls, er, _capture_kind, _abilities) => { unbound_names_exp(unbound, er); // remove anything in `ls` unbound_names_typed_binds(unbound, ls); diff --git a/third_party/move/move-compiler/src/naming/translate.rs b/third_party/move/move-compiler/src/naming/translate.rs index 1316d67a2871c..85a2ad2f5bf08 100644 --- a/third_party/move/move-compiler/src/naming/translate.rs +++ b/third_party/move/move-compiler/src/naming/translate.rs @@ -941,7 +941,7 @@ fn exp_(context: &mut Context, e: E::Exp) -> N::Exp { EE::While(eb, el) => NE::While(exp(context, *eb), exp(context, *el)), EE::Loop(el) => NE::Loop(exp(context, *el)), EE::Block(seq) => NE::Block(sequence(context, seq)), - EE::Lambda(args, body, _abilities) => { + EE::Lambda(args, body, _lambda_capture_kind, _abilities) => { let bind_opt = bind_typed_list(context, args); match bind_opt { None => { @@ -1032,9 +1032,13 @@ fn exp_(context: &mut Context, e: E::Exp) -> N::Exp { EE::Cast(e, t) => NE::Cast(exp(context, *e), type_(context, t)), EE::Annotate(e, t) => NE::Annotate(exp(context, *e), type_(context, t)), - EE::Call(sp!(mloc, E::ModuleAccess_::Name(n)), CallKind::Macro, tys_opt, rhs) - if n.value.as_str() == N::BuiltinFunction_::ASSERT_MACRO => - { + EE::Call( + sp!(mloc, E::ModuleAccess_::Name(n)), + CallKind::Macro, + tys_opt, + rhs, + _ends_in_dotdot, // not relevant to Move V1 + ) if n.value.as_str() == N::BuiltinFunction_::ASSERT_MACRO => { use N::BuiltinFunction_ as BF; if tys_opt.is_some() { context.env.add_diag(diag!( @@ -1061,7 +1065,7 @@ fn exp_(context: &mut Context, e: E::Exp) -> N::Exp { )); NE::UnresolvedError }, - EE::Call(sp!(mloc, ma_), kind, tys_opt, rhs) => { + EE::Call(sp!(mloc, ma_), kind, tys_opt, rhs, _ends_in_dotdot) => { use E::ModuleAccess_ as EA; let ty_args = tys_opt.map(|tys| types(context, tys)); let nes = call_args(context, rhs); diff --git a/third_party/move/move-compiler/src/parser/ast.rs b/third_party/move/move-compiler/src/parser/ast.rs index db696dc51a064..db222c2baa7a3 100644 --- a/third_party/move/move-compiler/src/parser/ast.rs +++ b/third_party/move/move-compiler/src/parser/ast.rs @@ -644,6 +644,34 @@ pub enum CallKind { Receiver, } +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Default)] +pub enum LambdaCaptureKind { + /// Direct use (e.g., inlining) + #[default] + Default, + /// Copy + Copy, + /// Move + Move, + /// Borrow (`&`) + Borrow, +} + +impl fmt::Display for LambdaCaptureKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &LambdaCaptureKind::Default => { + write!(f, "") + }, + &LambdaCaptureKind::Copy => { + write!(f, "copy") + }, + &LambdaCaptureKind::Move => write!(f, "move"), + &LambdaCaptureKind::Borrow => write!(f, "&"), + } + } +} + #[derive(Debug, Clone, PartialEq)] #[allow(clippy::large_enum_variant)] pub enum Exp_ { @@ -663,10 +691,15 @@ pub enum Exp_ { CallKind, Option>, Spanned>, + bool, // ends in ".." ), - // e(earg,*) - ExpCall(Box, Spanned>), + // e(earg,* [..]?) + ExpCall( + Box, + Spanned>, + bool, // ends in ".." + ), // tn {f1: e1, ... , f_n: e_n } Pack(NameAccessChain, Option>, Vec<(Field, Exp)>), @@ -691,7 +724,7 @@ pub enum Exp_ { // { seq } Block(Sequence), // | x1 [: t1], ..., xn [: tn] | e - Lambda(TypedBindList, Box, Vec), + Lambda(TypedBindList, Box, LambdaCaptureKind, Vec), // forall/exists x1 : e1, ..., xn [{ t1, .., tk } *] [where cond]: en. Quant( QuantKind, @@ -1056,32 +1089,24 @@ impl fmt::Display for BinOp_ { impl fmt::Display for Visibility { fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { - write!( - f, - "{}", - match &self { - Visibility::Public(_) => Visibility::PUBLIC, - Visibility::Script(_) => Visibility::SCRIPT, - Visibility::Friend(_) => Visibility::FRIEND, - Visibility::Package(_) => Visibility::PACKAGE, - Visibility::Internal => Visibility::INTERNAL, - } - ) + write!(f, "{}", match &self { + Visibility::Public(_) => Visibility::PUBLIC, + Visibility::Script(_) => Visibility::SCRIPT, + Visibility::Friend(_) => Visibility::FRIEND, + Visibility::Package(_) => Visibility::PACKAGE, + Visibility::Internal => Visibility::INTERNAL, + }) } } impl fmt::Display for Ability_ { fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { - write!( - f, - "{}", - match &self { - Ability_::Copy => Ability_::COPY, - Ability_::Drop => Ability_::DROP, - Ability_::Store => Ability_::STORE, - Ability_::Key => Ability_::KEY, - } - ) + write!(f, "{}", match &self { + Ability_::Copy => Ability_::COPY, + Ability_::Drop => Ability_::DROP, + Ability_::Store => Ability_::STORE, + Ability_::Key => Ability_::KEY, + }) } } @@ -1833,7 +1858,7 @@ impl AstDebug for Exp_ { w.write(">"); } }, - E::Call(ma, kind, tys_opt, sp!(_, rhs)) => { + E::Call(ma, kind, tys_opt, sp!(_, rhs), ends_in_dotdot) => { ma.ast_debug(w); w.write(kind.to_string()); if let Some(ss) = tys_opt { @@ -1843,12 +1868,18 @@ impl AstDebug for Exp_ { } w.write("("); w.comma(rhs, |w, e| e.ast_debug(w)); + if ends_in_dotdot { + w.write(".."); + } w.write(")"); }, - E::ExpCall(arg, sp!(_, rhs)) => { + E::ExpCall(arg, sp!(_, rhs), ends_in_dotdot) => { arg.ast_debug(w); w.write("("); w.comma(rhs, |w, e| e.ast_debug(w)); + if ends_in_dotdot { + w.write("..") + } w.write(")"); }, E::Pack(ma, tys_opt, fields) => { @@ -1911,7 +1942,10 @@ impl AstDebug for Exp_ { e.ast_debug(w); }, E::Block(seq) => w.block(|w| seq.ast_debug(w)), - E::Lambda(sp!(_, tbs), e, abilities) => { + E::Lambda(sp!(_, tbs), e, capture_kind, abilities) => { + if *capture_kind != LambdaCaptureKind::Default { + w.write(format!("{} ", capture_kind)); + } w.write("|"); tbs.ast_debug(w); w.write("|"); diff --git a/third_party/move/move-compiler/src/parser/syntax.rs b/third_party/move/move-compiler/src/parser/syntax.rs index 3e5f971168409..71b476c3f1fe7 100644 --- a/third_party/move/move-compiler/src/parser/syntax.rs +++ b/third_party/move/move-compiler/src/parser/syntax.rs @@ -1515,10 +1515,10 @@ fn parse_for_loop(context: &mut Context) -> Result<(Exp, bool), Box> // To create the declaration "let flag = false", first create the variable flag, and then assign it to false let flag_symb = Symbol::from(FOR_LOOP_UPDATE_ITER_FLAG); - let flag = sp( + let flag = sp(for_loc, vec![sp( for_loc, - vec![sp(for_loc, Bind_::Var(Var(sp(for_loc, flag_symb))))], - ); + Bind_::Var(Var(sp(for_loc, flag_symb))), + )]); let false_exp = sp(for_loc, Exp_::Value(sp(for_loc, Value_::Bool(false)))); let decl_flag = sp( for_loc, @@ -1528,10 +1528,10 @@ fn parse_for_loop(context: &mut Context) -> Result<(Exp, bool), Box> // To create the declaration "let ub_value = upper_bound", first create the variable flag, and // then assign it to upper_bound let ub_value_symbol = Symbol::from(FOR_LOOP_UPPER_BOUND_VALUE); - let ub_value_bindlist = sp( + let ub_value_bindlist = sp(for_loc, vec![sp( for_loc, - vec![sp(for_loc, Bind_::Var(Var(sp(for_loc, ub_value_symbol))))], - ); + Bind_::Var(Var(sp(for_loc, ub_value_symbol))), + )]); let ub_value_assignment = sp( for_loc, SequenceItem_::Bind(ub_value_bindlist, None, Box::new(ub)), @@ -1672,6 +1672,7 @@ fn parse_match_exp(context: &mut Context) -> Result> { end_loc, vec![], ), + false, ), )) } else { @@ -1716,6 +1717,7 @@ fn parse_match_exp(context: &mut Context) -> Result> { None, spanned(match_ident.loc.file_hash(), start_lparen_loc, end_loc, args), ), + false, )) } } @@ -1769,8 +1771,8 @@ fn parse_name_exp(context: &mut Context) -> Result> { let start_loc = context.tokens.start_loc(); if context.tokens.peek() == Tok::Exclaim { context.tokens.advance()?; - let rhs = parse_call_args(context)?; - return Ok(Exp_::Call(n, CallKind::Macro, tys, rhs)); + let (rhs, ends_in_dotdot) = parse_call_args(context)?; + return Ok(Exp_::Call(n, CallKind::Macro, tys, rhs, ends_in_dotdot)); } if context.tokens.peek() == Tok::Less && n.loc.end() as usize == start_loc { @@ -1794,8 +1796,8 @@ fn parse_name_exp(context: &mut Context) -> Result> { // Call: Tok::Exclaim | Tok::LParen => { - let rhs = parse_call_args(context)?; - Ok(Exp_::Call(n, CallKind::Regular, tys, rhs)) + let (rhs, ends_in_dotdot) = parse_call_args(context)?; + Ok(Exp_::Call(n, CallKind::Regular, tys, rhs, ends_in_dotdot)) }, // Other name reference... @@ -1806,7 +1808,9 @@ fn parse_name_exp(context: &mut Context) -> Result> { // Parse the arguments to a call: // CallArgs = // "(" Comma ")" -fn parse_call_args(context: &mut Context) -> Result>, Box> { +// | "(" Comma ".." ")" +// Result is a pair: (, ends_in_dotdot) +fn parse_call_args(context: &mut Context) -> Result<(Spanned>, bool), Box> { let start_loc = context.tokens.start_loc(); let args = parse_comma_list( context, @@ -1815,12 +1819,17 @@ fn parse_call_args(context: &mut Context) -> Result>, Box bool { ) } +// Parse the rest of a lambda expression, after already processing any capture designator (move/copy/&). +fn parse_lambda( + context: &mut Context, + start_loc: usize, + capture_kind: LambdaCaptureKind, + token: Tok, +) -> Result> { + let bindings = if token == Tok::Pipe { + parse_lambda_bind_list(context)? + } else { + // token is Tok::PipePipe, i.e., empty bind list in this context. + consume_token(context.tokens, Tok::PipePipe)?; + spanned(context.tokens.file_hash(), start_loc, start_loc + 1, vec![]) + }; + let body = Box::new(parse_exp(context)?); + let abilities_start = context.tokens.start_loc(); + let abilities = parse_abilities(context)?; + if !abilities.is_empty() { + let abilities_end = context.tokens.previous_end_loc(); + let loc = make_loc(context.tokens.file_hash(), abilities_start, abilities_end); + require_move_2(context, loc, "Abilities on function expressions"); + } + + Ok(Exp_::Lambda(bindings, body, capture_kind, abilities)) +} + // Parse an expression: // Exp = -// +// ( "move" | "copy" | "&" )? // | spec only // | // | "=" @@ -1877,17 +1912,30 @@ fn parse_exp(context: &mut Context) -> Result> { let start_loc = context.tokens.start_loc(); let token = context.tokens.peek(); let exp = match token { - Tok::Pipe | Tok::PipePipe => { - let bindings = if token == Tok::Pipe { - parse_lambda_bind_list(context)? - } else { - // token is Tok::PipePipe, i.e., empty bind list in this context. - consume_token(context.tokens, Tok::PipePipe)?; - spanned(context.tokens.file_hash(), start_loc, start_loc + 1, vec![]) + Tok::Move | Tok::Copy | Tok::Amp + if matches!( + context.tokens.lookahead_with_start_loc(), + Ok((Tok::Pipe | Tok::PipePipe, _)) + ) => + { + let _ = require_move_2_and_advance(context, "Modifier on lambda expression"); // consume the Move/Copy/Amp + let capture_kind = match token { + Tok::Move => LambdaCaptureKind::Move, + Tok::Copy => LambdaCaptureKind::Copy, + Tok::Amp => LambdaCaptureKind::Borrow, + _ => { + panic!("can't happen"); + }, }; - let body = Box::new(parse_exp(context)?); - let abilities = parse_abilities(context)?; - Exp_::Lambda(bindings, body, abilities) + parse_lambda( + context, + context.tokens.start_loc(), + capture_kind, + context.tokens.peek(), + )? + }, + Tok::Pipe | Tok::PipePipe => { + parse_lambda(context, start_loc, LambdaCaptureKind::default(), token)? }, Tok::Identifier if is_quant(context) => parse_quant(context)?, _ => { @@ -2134,7 +2182,7 @@ fn parse_unary_exp(context: &mut Context) -> Result> { // DotOrIndexChain = // "." [ ["::" "<" Comma ">"]? ]? // | "[" "]" -// | "(" Comma ")" // --> ExpCall +// | // --> ExpCall // | fn parse_dot_or_index_chain(context: &mut Context) -> Result> { let start_loc = context.tokens.start_loc(); @@ -2158,12 +2206,12 @@ fn parse_dot_or_index_chain(context: &mut Context) -> Result Result { - let args = parse_call_args(context)?; - Exp_::ExpCall(Box::new(lhs), args) + let (args, ends_in_dotdot) = parse_call_args(context)?; + Exp_::ExpCall(Box::new(lhs), args, ends_in_dotdot) }, _ => break, }; @@ -2358,7 +2406,7 @@ fn make_builtin_call(loc: Loc, name: Symbol, type_args: Option>, args: let maccess = sp(loc, NameAccessChain_::One(sp(loc, name))); sp( loc, - Exp_::Call(maccess, CallKind::Regular, type_args, sp(loc, args)), + Exp_::Call(maccess, CallKind::Regular, type_args, sp(loc, args), false), ) } @@ -2413,7 +2461,13 @@ fn parse_type(context: &mut Context) -> Result> { Type_::Unit, ) }; + let abilities_start = context.tokens.start_loc(); let abilities = parse_type_constraints(context)?; + if !abilities.is_empty() { + let abilities_end = context.tokens.previous_end_loc(); + let loc = make_loc(context.tokens.file_hash(), abilities_start, abilities_end); + require_move_2(context, loc, "Ability constraints on function types"); + } return Ok(spanned( context.tokens.file_hash(), start_loc, diff --git a/third_party/move/move-compiler/src/unit_test/filter_test_members.rs b/third_party/move/move-compiler/src/unit_test/filter_test_members.rs index 44e7d3c9989ef..76103841e1081 100644 --- a/third_party/move/move-compiler/src/unit_test/filter_test_members.rs +++ b/third_party/move/move-compiler/src/unit_test/filter_test_members.rs @@ -191,6 +191,7 @@ fn create_test_poison(mloc: Loc) -> P::ModuleMember { CallKind::Regular, None, sp(mloc, args_), + false, ); // fun unit_test_poison() { 0x1::UnitTest::create_signers_for_testing(0); () } diff --git a/third_party/move/move-model/src/ast.rs b/third_party/move/move-model/src/ast.rs index 1b2628d994dcf..1debe1addd4b5 100644 --- a/third_party/move/move-model/src/ast.rs +++ b/third_party/move/move-model/src/ast.rs @@ -512,6 +512,34 @@ pub enum AddressSpecifier { Call(QualifiedInstId, Symbol), } +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash, Default)] +pub enum LambdaCaptureKind { + /// No modifier (e.g., inlining) + #[default] + Default, + /// Copy + Copy, + /// Move + Move, + /// Borrow (`&`) + Borrow, +} + +impl fmt::Display for LambdaCaptureKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &LambdaCaptureKind::Default => { + write!(f, "") + }, + &LambdaCaptureKind::Copy => { + write!(f, "copy") + }, + &LambdaCaptureKind::Move => write!(f, "move"), + &LambdaCaptureKind::Borrow => write!(f, "&"), + } + } +} + impl ResourceSpecifier { /// Checks whether this resource specifier matches the given struct. A function /// instantiation is passed to instantiate the specifier in the calling context @@ -598,7 +626,7 @@ pub enum ExpData { /// Represents an invocation of a function value, as a lambda. Invoke(NodeId, Exp, Vec), /// Represents a lambda. - Lambda(NodeId, Pattern, Exp, AbilitySet), + Lambda(NodeId, Pattern, Exp, LambdaCaptureKind, AbilitySet), /// Represents a quantified formula over multiple variables and ranges. Quant( NodeId, @@ -1272,7 +1300,7 @@ impl ExpData { exp.visit_positions_impl(visitor)?; } }, - Lambda(_, _, body, _) => body.visit_positions_impl(visitor)?, + Lambda(_, _, body, _, _) => body.visit_positions_impl(visitor)?, Quant(_, _, ranges, triggers, condition, body) => { for (_, range) in ranges { range.visit_positions_impl(visitor)?; @@ -1629,7 +1657,7 @@ pub enum Operation { // Specification specific SpecFunction(ModuleId, SpecFunId, Option>), - Closure(ModuleId, FunId), + Closure, UpdateField(ModuleId, StructId, FieldId), Result(usize), Index, @@ -2990,19 +3018,31 @@ impl<'a> fmt::Display for ExpDisplay<'a> { self.fmt_exps(args) ) }, - Lambda(id, pat, body, abilities) => { + Lambda(id, pat, body, capture_kind, abilities) => { if self.verbose { write!( f, - "{}: |{}| {}", + "{}: {}{}|{}| {}", id.as_usize(), + if *capture_kind != LambdaCaptureKind::Default { + " " + } else { + "" + }, + capture_kind, pat.display_for_exp(self), body.display_cont(self) )?; } else { write!( f, - "|{}| {}", + "{}{}|{}| {}", + if *capture_kind != LambdaCaptureKind::Default { + " " + } else { + "" + }, + capture_kind, pat.display_for_exp(self), body.display_cont(self) )?; diff --git a/third_party/move/move-model/src/builder/exp_builder.rs b/third_party/move/move-model/src/builder/exp_builder.rs index 5db1d1ec079d0..ff3be0cb8258b 100644 --- a/third_party/move/move-model/src/builder/exp_builder.rs +++ b/third_party/move/move-model/src/builder/exp_builder.rs @@ -4,9 +4,9 @@ use crate::{ ast::{ - AccessSpecifier, Address, AddressSpecifier, Exp, ExpData, MatchArm, ModuleName, Operation, - Pattern, QualifiedSymbol, QuantKind, ResourceSpecifier, RewriteResult, Spec, TempIndex, - Value, + AccessSpecifier, Address, AddressSpecifier, Exp, ExpData, LambdaCaptureKind, MatchArm, + ModuleName, Operation, Pattern, QualifiedSymbol, QuantKind, ResourceSpecifier, + RewriteResult, Spec, TempIndex, Value, }, builder::{ model_builder::{ @@ -175,9 +175,14 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo et } - pub fn check_language_version(&self, loc: &Loc, feature: &str, version_min: LanguageVersion) { + pub fn check_language_version( + &self, + loc: &Loc, + feature: &str, + version_min: LanguageVersion, + ) -> bool { self.parent - .check_language_version(loc, feature, version_min); + .check_language_version(loc, feature, version_min) } pub fn set_spec_block_map(&mut self, map: BTreeMap) { @@ -1127,19 +1132,29 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo address, } = &specifier.value; if *kind != file_format::AccessKind::Acquires { - self.check_language_version( + if !self.check_language_version( &loc, "read/write access specifiers. Try `acquires` instead.", LanguageVersion::V2_0, - ) + ) { + return None; + } } else if *negated { - self.check_language_version(&loc, "access specifier negation", LanguageVersion::V2_0) + if !self.check_language_version( + &loc, + "access specifier negation", + LanguageVersion::V2_0, + ) { + return None; + } } else if type_args.is_some() && !type_args.as_ref().unwrap().is_empty() { - self.check_language_version( + if !self.check_language_version( &loc, "access specifier type instantiation. Try removing the type instantiation.", LanguageVersion::V2_0, - ) + ) { + return None; + } } let resource = match (module_address, module_name, resource_name) { (None, None, None) => { @@ -1173,13 +1188,10 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo if is_wildcard(resource) { ResourceSpecifier::DeclaredInModule(module_id) } else { - let mident = sp( - specifier.loc, - EA::ModuleIdent_ { - address: *address, - module: *module, - }, - ); + let mident = sp(specifier.loc, EA::ModuleIdent_ { + address: *address, + module: *module, + }); let maccess = sp( specifier.loc, EA::ModuleAccess_::ModuleAccess(mident, *resource, None), @@ -1231,11 +1243,13 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo }, }; if !matches!(resource, ResourceSpecifier::Resource(..)) { - self.check_language_version( + if !self.check_language_version( &loc, "address and wildcard access specifiers. Only resource type names can be provided.", LanguageVersion::V2_0, - ); + ) { + return None; + } } let address = self.translate_address_specifier(address)?; Some(AccessSpecifier { @@ -1255,30 +1269,36 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo let res = match &specifier.value { EA::AddressSpecifier_::Empty => (loc, AddressSpecifier::Any), EA::AddressSpecifier_::Any => { - self.check_language_version( + if !self.check_language_version( &loc, "wildcard address specifiers", LanguageVersion::V2_0, - ); + ) { + return None; + } (loc, AddressSpecifier::Any) }, EA::AddressSpecifier_::Literal(addr) => { - self.check_language_version( + if !self.check_language_version( &loc, "literal address specifiers", LanguageVersion::V2_0, - ); + ) { + return None; + } ( loc, AddressSpecifier::Address(Address::Numerical(addr.into_inner())), ) }, EA::AddressSpecifier_::Name(name) => { - self.check_language_version( + if !self.check_language_version( &loc, "named address specifiers", LanguageVersion::V2_0, - ); + ) { + return None; + } // Construct an expansion name exp for regular type check let maccess = sp(name.loc, EA::ModuleAccess_::Name(*name)); self.translate_name( @@ -1294,11 +1314,13 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo ) }, EA::AddressSpecifier_::Call(maccess, type_args, name) => { - self.check_language_version( + if !self.check_language_version( &loc, "derived address specifiers", LanguageVersion::V2_0, - ); + ) { + return None; + } // Construct an expansion function call for regular type check let name_exp = sp( name.loc, @@ -1367,6 +1389,16 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo self.translate_exp_in_context(exp, expected_type, &ErrorMessageContext::General) } + /// Translates LambdaCaptureKind + pub fn translate_lambda_capture_kind(kind: PA::LambdaCaptureKind) -> LambdaCaptureKind { + match kind { + PA::LambdaCaptureKind::Default => LambdaCaptureKind::Default, + PA::LambdaCaptureKind::Copy => LambdaCaptureKind::Copy, + PA::LambdaCaptureKind::Move => LambdaCaptureKind::Move, + PA::LambdaCaptureKind::Borrow => LambdaCaptureKind::Borrow, + } + } + /// Translates an expression in a specific error message context. pub fn translate_exp_in_context( &mut self, @@ -1481,10 +1513,26 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo self.set_node_instantiation(id, vec![elem_ty.clone()]); ExpData::Call(id, Operation::Vector, elems) }, - EA::Exp_::Call(maccess, kind, type_params, args) => { + EA::Exp_::Call(maccess, kind, type_params, args, ends_in_dotdot) => { if *kind == CallKind::Macro { + if ends_in_dotdot { + self.error( + &loc, + "`..` syntax not supported calling macros such as `assert!`", + ); + } self.translate_macro_call(maccess, type_params, args, expected_type, context) } else { + if ends_in_dotdot + && !self.check_language_version( + &loc, + "trailing `..` in call argument list", + LanguageVersion::V2_1, + ) + { + let id = self.new_node_id_with_type_loc(&Type::Error, &loc); + return ExpData::Invalid(id); + } // Need to make a &[&Exp] out of args. let args = args.value.iter().collect_vec(); self.translate_fun_call( @@ -1495,22 +1543,42 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo type_params, &args, context, + ends_in_dotdot, ) } }, - EA::Exp_::ExpCall(efexp, args) => { + EA::Exp_::ExpCall(efexp, args, ends_in_dotdot) => { + if ends_in_dotdot + && !self.check_language_version( + &loc, + "trailing `..` in call argument list", + LanguageVersion::V2_1, + ) + { + let id = self.new_node_id_with_type_loc(&Type::Error, &loc); + return ExpData::Invalid(id); + } let (ftype, fexp) = self.translate_exp_free(efexp); // Need to make a &[&Exp] out of args. let args = args.value.iter().collect_vec(); - let (arg_types, args) = self.translate_exp_list(&args); - let fun_t = Type::Fun( - Box::new(Type::tuple(arg_types)), - Box::new(expected_type.clone()), - AbilitySet::FUNCTIONS, - ); - let fun_t = self.check_type(&loc, &ftype, &fun_t, context); - let id = self.new_node_id_with_type_loc(&fun_t, &loc); - return ExpData::Invoke(id, fexp.into(), args); + let (arg_types, mut args) = self.translate_exp_list(&args); + if !ends_in_dotdot { + let fun_t = Type::Fun( + Box::new(Type::tuple(arg_types)), + Box::new(expected_type.clone()), + AbilitySet::FUNCTIONS, + ); + let fun_t = self.check_type(&loc, &ftype, &fun_t, context); + let id = self.new_node_id_with_type_loc(&fun_t, &loc); + ExpData::Invoke(id, fexp.into(), args) + } else { + // We are building a closure + let fun_result_t = + self.resolve_closure_type(&loc, &ftype, &arg_types, expected_type, context); + let id = self.new_node_id_with_type_loc(&fun_result_t, &loc); + args.insert(0, fexp); + ExpData::Call(id, Operation::Closure, args) + } }, EA::Exp_::Pack(maccess, generics, fields) => self .translate_pack( @@ -1603,12 +1671,13 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo ExpData::LoopCont(id, true) }, EA::Exp_::Block(seq) => self.translate_seq(&loc, seq, expected_type, context), - EA::Exp_::Lambda(bindings, exp, abilities) => self.translate_lambda( + EA::Exp_::Lambda(bindings, exp, capture_kind, abilities) => self.translate_lambda( &loc, bindings, exp, expected_type, context, + Self::translate_lambda_capture_kind(*capture_kind), self.parent.translate_abilities(abilities), ), EA::Exp_::Quant(kind, ranges, triggers, condition, body) => self.translate_quant( @@ -1884,13 +1953,11 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo let id = self.new_node_id_with_type_loc(&rt, &loc); if self.mode == ExpTranslationMode::Impl { // Remember information about this spec block for deferred checking. - self.placeholder_map.insert( - id, - ExpPlaceholder::SpecBlockInfo { + self.placeholder_map + .insert(id, ExpPlaceholder::SpecBlockInfo { spec_id: *spec_id, locals: self.get_locals(), - }, - ); + }); } ExpData::Call(id, Operation::NoOp, vec![]) }, @@ -3005,6 +3072,7 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo generics: &Option>, args: &[&EA::Exp], context: &ErrorMessageContext, + ends_in_dotdot: bool, ) -> ExpData { debug_assert!(matches!(kind, CallKind::Regular | CallKind::Receiver)); @@ -3092,7 +3160,9 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo // handles call of struct/variant with positional fields let expected_type = &self.subs.specialize(expected_type); if self.can_resolve_to_struct(expected_type, maccess) { - self.check_language_version(loc, "positional fields", LanguageVersion::V2_0); + if !self.check_language_version(loc, "positional fields", LanguageVersion::V2_0) { + return None; + } // translates StructName(e0, e1, ...) to pack { 0: e0, 1: e1, ... } let fields: EA::Fields<_> = EA::Fields::maybe_from_iter(args.iter().enumerate().map(|(i, &arg)| { @@ -3102,6 +3172,9 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo (field, (i, arg.clone())) })) .expect("duplicate keys"); + if ends_in_dotdot { + self.error(loc, "Trailing `..` not allowed in this context."); + } return self .translate_pack( loc, @@ -3484,11 +3557,9 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo ); let global_id = self.new_node_id_with_type_loc(&ghost_mem_ty, loc); self.set_node_instantiation(global_id, vec![ghost_mem_ty]); - let global_access = ExpData::Call( - global_id, - Operation::Global(None), - vec![zero_addr.into_exp()], - ); + let global_access = ExpData::Call(global_id, Operation::Global(None), vec![ + zero_addr.into_exp() + ]); let select_id = self.new_node_id_with_type_loc(&ty, loc); self.set_node_instantiation(select_id, instantiation); return ExpData::Call( @@ -3697,11 +3768,10 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo ), ); self.set_node_instantiation(node_id, vec![inner_ty.clone()]); - let call = ExpData::Call( - node_id, - Operation::MoveFunction(mid, fid), - vec![vec_exp_e.into_exp(), idx_exp_e.clone().into_exp()], - ); + let call = ExpData::Call(node_id, Operation::MoveFunction(mid, fid), vec![ + vec_exp_e.into_exp(), + idx_exp_e.clone().into_exp(), + ]); return call; } ExpData::Invalid(self.env().new_node_id()) @@ -3732,7 +3802,9 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo .struct_table .contains_key(&global_var_sym) { - self.check_language_version(loc, "resource indexing", LanguageVersion::V2_0); + if !self.check_language_version(loc, "resource indexing", LanguageVersion::V2_0) { + return None; + } if self .parent .parent @@ -3762,7 +3834,9 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo } } if !self.is_spec_mode() { - self.check_language_version(loc, "vector indexing", LanguageVersion::V2_0); + if !self.check_language_version(loc, "vector indexing", LanguageVersion::V2_0) { + return None; + } // Translate to vector indexing in impl mode if the target is not a resource or a spec schema // spec mode is handled in `translate_index` if call.is_none() { @@ -3875,13 +3949,11 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo self.create_select_oper(&loc, &mid.qualified_inst(sid, inst), field_name) } else { // Create a placeholder for later resolution. - self.placeholder_map.insert( - id, - ExpPlaceholder::FieldSelectInfo { + self.placeholder_map + .insert(id, ExpPlaceholder::FieldSelectInfo { struct_ty: ty, field_name, - }, - ); + }); Operation::NoOp }; ExpData::Call(id, oper, vec![exp.into_exp()]) @@ -4475,7 +4547,11 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo args: Vec, expected_type: &Type, ) -> ExpData { - self.check_language_version(loc, "receiver style function calls", LanguageVersion::V2_0); + if !self.check_language_version(loc, "receiver style function calls", LanguageVersion::V2_0) + { + let id = self.new_node_id_with_type_loc(&Type::Error, &loc); + return ExpData::Invalid(id); + } let generics = generics .as_ref() .map(|tys| self.translate_types_with_loc(tys)); @@ -4496,15 +4572,13 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo None, ); let id = self.new_node_id_with_type_loc(expected_type, loc); - self.placeholder_map.insert( - id, - ExpPlaceholder::ReceiverCallInfo { + self.placeholder_map + .insert(id, ExpPlaceholder::ReceiverCallInfo { name, generics: generics.map(|g| g.1.clone()), arg_types, result_type: expected_type.clone(), - }, - ); + }); ExpData::Call(id, Operation::NoOp, args) } @@ -4926,11 +5000,13 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo ) -> Option { let struct_entry = self.parent.parent.lookup_struct_entry(struct_id); match (&struct_entry.layout, variant) { - (StructLayout::Singleton(fields, _), None) => Some(if struct_entry.is_empty_struct { - 0 - } else { - fields.len() - }), + (StructLayout::Singleton(fields, _), None) => Some( + if struct_entry.is_empty_struct { + 0 + } else { + fields.len() + }, + ), (StructLayout::Variants(variants), Some(name)) => variants .iter() .find(|v| v.name == name) @@ -5057,6 +5133,7 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo body: &EA::Exp, expected_type: &Type, context: &ErrorMessageContext, + capture_kind: LambdaCaptureKind, abilities: AbilitySet, ) -> ExpData { // Translate the argument list @@ -5078,14 +5155,18 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo let ty = self.fresh_type_var(); let rty = self.check_type( loc, - &Type::Fun(Box::new(arg_type), Box::new(ty.clone()), abilities.clone()), + &Type::Fun( + Box::new(arg_type), + Box::new(ty.clone()), + abilities.union(AbilitySet::FUNCTIONS), + ), expected_type, context, ); let rbody = self.translate_exp(body, &ty); self.exit_scope(); let id = self.new_node_id_with_type_loc(&rty, loc); - ExpData::Lambda(id, pat, rbody.into_exp(), abilities) + ExpData::Lambda(id, pat, rbody.into_exp(), capture_kind, abilities) } fn translate_quant( diff --git a/third_party/move/move-model/src/builder/module_builder.rs b/third_party/move/move-model/src/builder/module_builder.rs index 5b5cdaeae904c..7f84647dcddc9 100644 --- a/third_party/move/move-model/src/builder/module_builder.rs +++ b/third_party/move/move-model/src/builder/module_builder.rs @@ -877,7 +877,12 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { /// # Definition Analysis impl<'env, 'translator> ModuleBuilder<'env, 'translator> { - pub fn check_language_version(&self, loc: &Loc, feature: &str, version_min: LanguageVersion) { + pub fn check_language_version( + &self, + loc: &Loc, + feature: &str, + version_min: LanguageVersion, + ) -> bool { if !self.parent.env.language_version().is_at_least(version_min) { self.parent.env.error( loc, @@ -885,7 +890,10 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { "not supported before language version `{}`: {}", version_min, feature ), - ) + ); + false + } else { + true } } @@ -971,11 +979,13 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { if !self.parent.const_table.contains_key(&qsym) { continue; } - self.check_language_version( + if !self.check_language_version( &loc, "constant definitions referring to other constants", LanguageVersion::V2_0, - ); + ) { + continue; + } if visited.contains(&const_name) { continue; } diff --git a/third_party/move/move-model/src/exp_rewriter.rs b/third_party/move/move-model/src/exp_rewriter.rs index 07685eddd22cc..b97d5b9b8b013 100644 --- a/third_party/move/move-model/src/exp_rewriter.rs +++ b/third_party/move/move-model/src/exp_rewriter.rs @@ -4,8 +4,8 @@ use crate::{ ast::{ - Condition, Exp, ExpData, MatchArm, MemoryLabel, Operation, Pattern, Spec, SpecBlockTarget, - TempIndex, Value, + Condition, Exp, ExpData, LambdaCaptureKind, MatchArm, MemoryLabel, Operation, Pattern, + Spec, SpecBlockTarget, TempIndex, Value, }, model::{GlobalEnv, Loc, ModuleId, NodeId, SpecVarId}, symbol::Symbol, @@ -200,6 +200,7 @@ pub trait ExpRewriterFunctions { id: NodeId, pat: &Pattern, body: &Exp, + capture_kind: LambdaCaptureKind, abilities: AbilitySet, ) -> Option { None @@ -358,17 +359,18 @@ pub trait ExpRewriterFunctions { exp } }, - Lambda(id, pat, body, abilities) => { + Lambda(id, pat, body, capture_kind, abilities) => { let (id_changed, new_id) = self.internal_rewrite_id(*id); let (pat_changed, new_pat) = self.internal_rewrite_pattern(pat, true); self.rewrite_enter_scope(new_id, new_pat.vars().iter()); let (body_changed, new_body) = self.internal_rewrite_exp(body); self.rewrite_exit_scope(new_id); - if let Some(new_exp) = self.rewrite_lambda(new_id, &new_pat, &new_body, *abilities) + if let Some(new_exp) = + self.rewrite_lambda(new_id, &new_pat, &new_body, *capture_kind, *abilities) { new_exp } else if id_changed || pat_changed || body_changed { - Lambda(new_id, new_pat, new_body, *abilities).into_exp() + Lambda(new_id, new_pat, new_body, *capture_kind, *abilities).into_exp() } else { exp } @@ -487,15 +489,12 @@ pub trait ExpRewriterFunctions { { (true, new_exp) } else { - ( - false, - MatchArm { - loc: arm.loc.clone(), - pattern: newer_pat, - condition: new_cond, - body: new_body, - }, - ) + (false, MatchArm { + loc: arm.loc.clone(), + pattern: newer_pat, + condition: new_cond, + body: new_body, + }) }; new_arms.push(new_arm); arms_changed = @@ -634,24 +633,18 @@ pub trait ExpRewriterFunctions { let new_exp = self.rewrite_exp(condition.exp.clone()); let maybe_new_additional_exps = self.internal_rewrite_vec(&condition.additional_exps); if let Some(new_additional_exps) = maybe_new_additional_exps { - ( - true, - Condition { - exp: new_exp, - additional_exps: new_additional_exps, - ..condition - }, - ) + (true, Condition { + exp: new_exp, + additional_exps: new_additional_exps, + ..condition + }) } else { let changed = !ExpData::ptr_eq(&condition.exp, &new_exp); if changed { - ( - true, - Condition { - exp: new_exp, - ..condition - }, - ) + (true, Condition { + exp: new_exp, + ..condition + }) } else { (false, condition) }