Repository: aptos-labs/aptos-core (language: python)

Issue:
[Bug][move-compiler-v2] Move allows `_` to be used on RHS, and treats `_` parameters as variables
# 🐛 Bug

While thinking about using `_` as a placeholder for some sort of curry operation, I found some strangeness in the way this special variable is handled in both V1 and V2.

- The value of a function parameter called `_` can be used.
- Having more than one function parameter called `_` leads to a "duplicate parameter" warning.
- `let` statements and assignments to `_` do not produce errors, but have no effect:
    - The code `let _ = 3; _` leads to an unbound/undeclared compile-time error in both compiler V1 and V2.
    - The program below shows assignments to `_` which are seen to have no effect on the value of `_` (which is obtained from the parameter, apparently not really the same thing as the LocalVar).  Essentially, assignment to `_` discards a value (as in Rust, actually).

For comparison, Rust says:
```
error: in expressions, `_` can only be used on the left-hand side of an assignment
 --> src/main.rs:4:5
  |
4 |     _
  |     ^ `_` not allowed here

```
and Rust ignores parameters called `_`, rather than generating errors due to multiple such parameters.

## Action

In Move Language V2 we should change this behavior to be more consistent:
- Disallow taking the value of `_`
- Ignore multiple parameters named `_`

This will free up `_` so we can use it for currying.

## Reproducing example

The below program demonstrates several of these problems:
```
//# publish
module 0xc0ffee::m {
    fun test2(): (u32, u64) {
        (1u32, 2u64)
    }
    
    public fun test(_: u64): u64 {
        let x = _ + 3;
        x + _
    }

    public fun test3(_: u64): u64 {
        let (_, _) = test2();
        let x = _ + 3;
        _ = _ + 1;
        x + _
    }

    public fun test4(_: u64): u64 {
        let (_, _x) = test2();
        _ = _ + 2;
        _
    }

    public fun test5(_: u64): u64 {
        let (_, _) = test2();
        _ = _ + 3;
        _
    }

    public fun test6(_: u64): u64 {
        let (_x, _) = test2();
         _
    }

    // public fun test7(_y: u64): u64 {
    //    let _ = _y;
    //    _  // undefined
    // }
}

//# run 0xc0ffee::m::test --args 4

//# run 0xc0ffee::m::test3 --args 5

//# run 0xc0ffee::m::test4 --args 5

//# run 0xc0ffee::m::test5 --args 5

//# run 0xc0ffee::m::test6 --args 5

//# run 0xc0ffee::m::test7 --args 5

```

Yields the following output, with no compiler errors (after `test7` was commented out):
```
processed 6 tasks

task 1 'run'. lines 37-37:
return values: 11

task 2 'run'. lines 39-39:
return values: 13

task 3 'run'. lines 41-41:
return values: 5

task 4 'run'. lines 43-43:
return values: 5

task 5 'run'. lines 45-45:
return values: 5

==> Compiler v2 delivered same results!
```




Discussion / hints:

Fix patch:
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 77a4ce7eefede..5502f1d9694f4 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
@@ -45,6 +45,7 @@ use log::{debug, trace};
 use move_model::{
     ast::{Exp, ExpData, Operation, Pattern, Spec, SpecBlockTarget, TempIndex},
     exp_rewriter::ExpRewriterFunctions,
+    metadata::LanguageVersion,
     model::{FunId, GlobalEnv, Loc, NodeId, Parameter, QualifiedId},
     symbol::Symbol,
     ty::{ReferenceKind, Type},
@@ -833,7 +834,11 @@ impl<'env, 'rewriter> InlinedRewriter<'env, 'rewriter> {
             .map(|param| {
                 let Parameter(sym, ty, loc) = *param;
                 let id = env.new_node(loc.clone(), ty.instantiate(self.type_args));
-                if let Some(new_sym) = self.shadow_stack.get_shadow_symbol(*sym, true) {
+                if env.language_version().is_at_least(LanguageVersion::V2_0)
+                    && env.symbol_pool().string(*sym).as_ref() == "_"
+                {
+                    Pattern::Wildcard(id)
+                } else if let Some(new_sym) = self.shadow_stack.get_shadow_symbol(*sym, true) {
                     Pattern::Var(id, new_sym)
                 } else {
                     Pattern::Var(id, *sym)
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 5fca751d2e0a2..7c7e49313931f 100644
--- a/third_party/move/move-model/src/builder/exp_builder.rs
+++ b/third_party/move/move-model/src/builder/exp_builder.rs
@@ -677,23 +677,31 @@ impl<'env, 'translator, 'module_translator> ExpTranslator<'env, 'translator, 'mo
         params: &[(PA::Var, EA::Type)],
         for_move_fun: bool,
     ) -> Vec<Parameter> {
+        let is_lang_version_2 = self
+            .env()
+            .language_version
+            .is_at_least(LanguageVersion::V2_0);
         params
             .iter()
             .enumerate()
             .map(|(idx, (v, ty))| {
                 let ty = self.translate_type(ty);
-                let sym = self.symbol_pool().make(v.0.value.as_str());
+                let var_str = v.0.value.as_str();
+                let sym = self.symbol_pool().make(var_str);
                 let loc = self.to_loc(&v.loc());
-                self.define_local(
-                    &loc,
-                    sym,
-                    ty.clone(),
-                    None,
-                    // If this is for a proper Move function (not spec function), add the
-                    // index so we can resolve this to a `Temporary` expression instead of
-                    // a `LocalVar`.
-                    if for_move_fun { Some(idx) } else { None },
-                );
+
+                if !is_lang_version_2 || var_str != "_" {
+                    self.define_local(
+                        &loc,
+                        sym,
+                        ty.clone(),
+                        None,
+                        // If this is for a proper Move function (not spec function), add the
+                        // index so we can resolve this to a `Temporary` expression instead of
+                        // a `LocalVar`.
+                        if for_move_fun { Some(idx) } else { None },
+                    );
+                }
                 Parameter(sym, ty, loc)
             })
             .collect_vec()
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 9f04fe84d1237..ad2da753e48ce 100644
--- a/third_party/move/move-model/src/builder/module_builder.rs
+++ b/third_party/move/move-model/src/builder/module_builder.rs
@@ -1532,8 +1532,12 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> {
                 et.define_type_param(loc, *name, Type::new_param(pos), kind.clone(), false);
             }
             et.enter_scope();
+            let is_lang_version_2 = et.env().language_version.is_at_least(LanguageVersion::V2_0);
             for (idx, Parameter(n, ty, loc)) in params.iter().enumerate() {
-                et.define_local(loc, *n, ty.clone(), None, Some(idx));
+                let symbol_pool = et.parent.parent.env.symbol_pool();
+                if !is_lang_version_2 || symbol_pool.string(*n).as_ref() != "_" {
+                    et.define_local(loc, *n, ty.clone(), None, Some(idx));
+                }
             }
             let access_specifiers = et.translate_access_specifiers(&def.access_specifiers);
             let result = et.translate_seq(&loc, seq, &result_type, &ErrorMessageContext::Return);


Test patch:
diff --git a/third_party/move/move-compiler-v2/tests/checking-lang-v1/underscore.exp b/third_party/move/move-compiler-v2/tests/checking-lang-v1/underscore.exp
new file mode 100644
index 0000000000000..aed1ebf956e2a
--- /dev/null
+++ b/third_party/move/move-compiler-v2/tests/checking-lang-v1/underscore.exp
@@ -0,0 +1,37 @@
+
+Diagnostics:
+error: undeclared `_`
+   ┌─ tests/checking-lang-v1/underscore.move:38:9
+   │
+38 │         _  // undefined
+   │         ^
+
+error: duplicate declaration of `_`
+   ┌─ tests/checking-lang-v1/underscore.move:41:30
+   │
+41 │     public fun test8(_: u64, _: u64): u64 {
+   │                      -       ^
+   │                      │
+   │                      previous declaration of `_`
+
+error: duplicate declaration of `_`
+   ┌─ tests/checking-lang-v1/underscore.move:46:37
+   │
+46 │     inline fun fun9(x: u64, _: u64, _: u64): u64 {
+   │                             -       ^
+   │                             │
+   │                             previous declaration of `_`
+
+error: duplicate declaration of `_`
+   ┌─ tests/checking-lang-v1/underscore.move:54:38
+   │
+54 │     inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+   │                              -       ^
+   │                              │
+   │                              previous declaration of `_`
+
+error: cannot pass `integer` to a function which expects argument of type `|u64|u64`
+   ┌─ tests/checking-lang-v1/underscore.move:59:21
+   │
+59 │         fun10(4, 3, 2)
+   │                     ^
diff --git a/third_party/move/move-compiler-v2/tests/checking-lang-v1/underscore.move b/third_party/move/move-compiler-v2/tests/checking-lang-v1/underscore.move
new file mode 100644
index 0000000000000..78aea11bd22d7
--- /dev/null
+++ b/third_party/move/move-compiler-v2/tests/checking-lang-v1/underscore.move
@@ -0,0 +1,101 @@
+//# publish
+module 0xc0ffee::m {
+    fun test2(): (u32, u64) {
+        (1u32, 2u64)
+    }
+
+    public fun test(_: u64): u64 {
+        let x = _ + 3;
+        x + _
+    }
+
+    public fun test3(_: u64): u64 {
+        let (_, _) = test2();
+        let x = _ + 3;
+        _ = _ + 1;
+        x + _
+    }
+
+    public fun test4(_: u64): u64 {
+        let (_, _x) = test2();
+        _ = _ + 2;
+        _
+    }
+
+    public fun test5(_: u64): u64 {
+        let (_, _) = test2();
+        _ = _ + 3;
+        _
+    }
+
+    public fun test6(_: u64): u64 {
+        let (_x, _) = test2();
+         _
+    }
+
+    public fun test7(_y: u64): u64 {
+        let _ = _y;
+        _  // undefined
+    }
+
+    public fun test8(_: u64, _: u64): u64 {
+        let _ = _;
+        _
+    }
+
+    inline fun fun9(x: u64, _: u64, _: u64): u64 {
+        x + 3
+    }
+
+    public fun test9(): u64 {
+        fun9(4, 3, 2)
+    }
+
+    inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+        x + 3
+    }
+
+    public fun test10a(): u64 {
+        fun10(4, 3, 2)
+    }
+
+    public fun test10b(): u64 {
+        fun10(4, 3, |x|x + 1)
+    }
+
+    public fun test10c(): u64 {
+        fun10(4, 3, |_|1)
+    }
+
+    inline fun fun11(x: u64, _: u64, f: |u64|u64): u64 {
+        f(x)
+    }
+
+    public fun test11(): u64 {
+        fun11(4, 3, |_|1)
+    }
+}
+
+//# run 0xc0ffee::m::test --args 4
+
+//# run 0xc0ffee::m::test3 --args 5
+
+//# run 0xc0ffee::m::test4 --args 5
+
+//# run 0xc0ffee::m::test5 --args 5
+
+//# run 0xc0ffee::m::test6 --args 5
+
+//# run 0xc0ffee::m::test7 --args 5
+
+//# run 0xc0ffee::m::test8 --args 5
+
+//# run 0xc0ffee::m::test9
+
+//# run 0xc0ffee::m::test10a
+
+//# run 0xc0ffee::m::test10b
+
+//# run 0xc0ffee::m::test10c
+
+//# run 0xc0ffee::m::test11
diff --git a/third_party/move/move-compiler-v2/tests/checking/naming/underscore.exp b/third_party/move/move-compiler-v2/tests/checking/naming/underscore.exp
new file mode 100644
index 0000000000000..738c845816b07
--- /dev/null
+++ b/third_party/move/move-compiler-v2/tests/checking/naming/underscore.exp
@@ -0,0 +1,85 @@
+
+Diagnostics:
+error: undeclared `_`
+  ┌─ tests/checking/naming/underscore.move:8:17
+  │
+8 │         let x = _ + 3;
+  │                 ^
+
+error: undeclared `_`
+  ┌─ tests/checking/naming/underscore.move:9:13
+  │
+9 │         x + _
+  │             ^
+
+error: undeclared `_`
+   ┌─ tests/checking/naming/underscore.move:14:17
+   │
+14 │         let x = _ + 3;
+   │                 ^
+
+error: undeclared `_`
+   ┌─ tests/checking/naming/underscore.move:15:13
+   │
+15 │         _ = _ + 1;
+   │             ^
+
+error: undeclared `_`
+   ┌─ tests/checking/naming/underscore.move:16:13
+   │
+16 │         x + _
+   │             ^
+
+error: undeclared `_`
+   ┌─ tests/checking/naming/underscore.move:21:13
+   │
+21 │         _ = _ + 2;
+   │             ^
+
+error: undeclared `_`
+   ┌─ tests/checking/naming/underscore.move:22:9
+   │
+22 │         _
+   │         ^
+
+error: undeclared `_`
+   ┌─ tests/checking/naming/underscore.move:27:13
+   │
+27 │         _ = _ + 3;
+   │             ^
+
+error: undeclared `_`
+   ┌─ tests/checking/naming/underscore.move:28:9
+   │
+28 │         _
+   │         ^
+
+error: undeclared `_`
+   ┌─ tests/checking/naming/underscore.move:33:10
+   │
+33 │          _
+   │          ^
+
+error: undeclared `_`
+   ┌─ tests/checking/naming/underscore.move:38:9
+   │
+38 │         _  // undefined
+   │         ^
+
+error: undeclared `_`
+   ┌─ tests/checking/naming/underscore.move:42:17
+   │
+42 │         let _ = _;
+   │                 ^
+
+error: undeclared `_`
+   ┌─ tests/checking/naming/underscore.move:43:9
+   │
+43 │         _
+   │         ^
+
+error: cannot pass `integer` to a function which expects argument of type `|u64|u64`
+   ┌─ tests/checking/naming/underscore.move:59:21
+   │
+59 │         fun10(4, 3, 2)
+   │                     ^
diff --git a/third_party/move/move-compiler-v2/tests/checking/naming/underscore.move b/third_party/move/move-compiler-v2/tests/checking/naming/underscore.move
new file mode 100644
index 0000000000000..78aea11bd22d7
--- /dev/null
+++ b/third_party/move/move-compiler-v2/tests/checking/naming/underscore.move
@@ -0,0 +1,101 @@
+//# publish
+module 0xc0ffee::m {
+    fun test2(): (u32, u64) {
+        (1u32, 2u64)
+    }
+
+    public fun test(_: u64): u64 {
+        let x = _ + 3;
+        x + _
+    }
+
+    public fun test3(_: u64): u64 {
+        let (_, _) = test2();
+        let x = _ + 3;
+        _ = _ + 1;
+        x + _
+    }
+
+    public fun test4(_: u64): u64 {
+        let (_, _x) = test2();
+        _ = _ + 2;
+        _
+    }
+
+    public fun test5(_: u64): u64 {
+        let (_, _) = test2();
+        _ = _ + 3;
+        _
+    }
+
+    public fun test6(_: u64): u64 {
+        let (_x, _) = test2();
+         _
+    }
+
+    public fun test7(_y: u64): u64 {
+        let _ = _y;
+        _  // undefined
+    }
+
+    public fun test8(_: u64, _: u64): u64 {
+        let _ = _;
+        _
+    }
+
+    inline fun fun9(x: u64, _: u64, _: u64): u64 {
+        x + 3
+    }
+
+    public fun test9(): u64 {
+        fun9(4, 3, 2)
+    }
+
+    inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+        x + 3
+    }
+
+    public fun test10a(): u64 {
+        fun10(4, 3, 2)
+    }
+
+    public fun test10b(): u64 {
+        fun10(4, 3, |x|x + 1)
+    }
+
+    public fun test10c(): u64 {
+        fun10(4, 3, |_|1)
+    }
+
+    inline fun fun11(x: u64, _: u64, f: |u64|u64): u64 {
+        f(x)
+    }
+
+    public fun test11(): u64 {
+        fun11(4, 3, |_|1)
+    }
+}
+
+//# run 0xc0ffee::m::test --args 4
+
+//# run 0xc0ffee::m::test3 --args 5
+
+//# run 0xc0ffee::m::test4 --args 5
+
+//# run 0xc0ffee::m::test5 --args 5
+
+//# run 0xc0ffee::m::test6 --args 5
+
+//# run 0xc0ffee::m::test7 --args 5
+
+//# run 0xc0ffee::m::test8 --args 5
+
+//# run 0xc0ffee::m::test9
+
+//# run 0xc0ffee::m::test10a
+
+//# run 0xc0ffee::m::test10b
+
+//# run 0xc0ffee::m::test10c
+
+//# run 0xc0ffee::m::test11
diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore.move b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore.move
new file mode 100644
index 0000000000000..78aea11bd22d7
--- /dev/null
+++ b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore.move
@@ -0,0 +1,101 @@
+//# publish
+module 0xc0ffee::m {
+    fun test2(): (u32, u64) {
+        (1u32, 2u64)
+    }
+
+    public fun test(_: u64): u64 {
+        let x = _ + 3;
+        x + _
+    }
+
+    public fun test3(_: u64): u64 {
+        let (_, _) = test2();
+        let x = _ + 3;
+        _ = _ + 1;
+        x + _
+    }
+
+    public fun test4(_: u64): u64 {
+        let (_, _x) = test2();
+        _ = _ + 2;
+        _
+    }
+
+    public fun test5(_: u64): u64 {
+        let (_, _) = test2();
+        _ = _ + 3;
+        _
+    }
+
+    public fun test6(_: u64): u64 {
+        let (_x, _) = test2();
+         _
+    }
+
+    public fun test7(_y: u64): u64 {
+        let _ = _y;
+        _  // undefined
+    }
+
+    public fun test8(_: u64, _: u64): u64 {
+        let _ = _;
+        _
+    }
+
+    inline fun fun9(x: u64, _: u64, _: u64): u64 {
+        x + 3
+    }
+
+    public fun test9(): u64 {
+        fun9(4, 3, 2)
+    }
+
+    inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+        x + 3
+    }
+
+    public fun test10a(): u64 {
+        fun10(4, 3, 2)
+    }
+
+    public fun test10b(): u64 {
+        fun10(4, 3, |x|x + 1)
+    }
+
+    public fun test10c(): u64 {
+        fun10(4, 3, |_|1)
+    }
+
+    inline fun fun11(x: u64, _: u64, f: |u64|u64): u64 {
+        f(x)
+    }
+
+    public fun test11(): u64 {
+        fun11(4, 3, |_|1)
+    }
+}
+
+//# run 0xc0ffee::m::test --args 4
+
+//# run 0xc0ffee::m::test3 --args 5
+
+//# run 0xc0ffee::m::test4 --args 5
+
+//# run 0xc0ffee::m::test5 --args 5
+
+//# run 0xc0ffee::m::test6 --args 5
+
+//# run 0xc0ffee::m::test7 --args 5
+
+//# run 0xc0ffee::m::test8 --args 5
+
+//# run 0xc0ffee::m::test9
+
+//# run 0xc0ffee::m::test10a
+
+//# run 0xc0ffee::m::test10b
+
+//# run 0xc0ffee::m::test10c
+
+//# run 0xc0ffee::m::test11
diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore.operator-eval-lang-1.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore.operator-eval-lang-1.exp
new file mode 100644
index 0000000000000..2871d7daee650
--- /dev/null
+++ b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore.operator-eval-lang-1.exp
@@ -0,0 +1,171 @@
+comparison between v1 and v2 failed:
+= processed 13 tasks
+= 
+= task 0 'publish'. lines 1-77:
+- Error: error[E03009]: unbound variable
++ Error: compilation errors:
++  error: undeclared `_`
+=    ┌─ TEMPFILE:32:9
+=    │
+= 32 │         _  // undefined
+-    │         ^ Invalid variable usage. Unbound variable '_'
++    │         ^
+= 
+- error[E02001]: duplicate declaration, item, or annotation
++ error: duplicate declaration of `_`
+=    ┌─ TEMPFILE:34:30
+=    │
+= 34 │     public fun test8(_: u64, _: u64): u64 {
+-    │                      -       ^ Duplicate parameter with name '_'
++    │                      -       ^
+=    │                      │        
+-    │                      Previously declared here
++    │                      previous declaration of `_`
+= 
+- error[E02001]: duplicate declaration, item, or annotation
++ error: duplicate declaration of `_`
+=    ┌─ TEMPFILE:38:37
+=    │
+= 38 │     inline fun fun9(x: u64, _: u64, _: u64): u64 {
+-    │                             -       ^ Duplicate parameter with name '_'
++    │                             -       ^
+=    │                             │        
+-    │                             Previously declared here
++    │                             previous declaration of `_`
+= 
+- error[E02001]: duplicate declaration, item, or annotation
++ error: duplicate declaration of `_`
+=    ┌─ TEMPFILE:44:38
+=    │
+= 44 │     inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+-    │                              -       ^ Duplicate parameter with name '_'
++    │                              -       ^
+=    │                              │        
+-    │                              Previously declared here
++    │                              previous declaration of `_`
+= 
+- error[E04007]: incompatible types
+-    ┌─ TEMPFILE:48:9
++ error: cannot pass `integer` to a function which expects argument of type `|u64|u64`
++    ┌─ TEMPFILE:48:21
+=    │
+- 44 │     inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+-    │                                         -------- Expected: '|u64|u64'
+-    ·
+= 48 │         fun10(4, 3, 2)
+-    │         ^^^^^^^^^^^^^^
+-    │         │           │
+-    │         │           Given: integer
+-    │         Invalid call of '0xC0FFEE::m::fun10'. Invalid argument for parameter '_'
++    │                     ^
+= 
+= 
+= 
+= task 1 'run'. lines 79-79:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 2 'run'. lines 81-81:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 3 'run'. lines 83-83:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 4 'run'. lines 85-85:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 5 'run'. lines 87-87:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 6 'run'. lines 89-89:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 7 'run'. lines 91-91:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 8 'run'. lines 93-93:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 9 'run'. lines 95-95:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 10 'run'. lines 97-97:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 11 'run'. lines 99-99:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 12 'run'. lines 101-101:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore.operator-eval-lang-2.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore.operator-eval-lang-2.exp
new file mode 100644
index 0000000000000..8b21f46b3a6ae
--- /dev/null
+++ b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore.operator-eval-lang-2.exp
@@ -0,0 +1,230 @@
+comparison between v1 and v2 failed:
+= processed 13 tasks
+= 
+= task 0 'publish'. lines 1-77:
+- Error: error[E03009]: unbound variable
++ Error: compilation errors:
++  error: undeclared `_`
++   ┌─ TEMPFILE:7:17
++   │
++ 7 │         let x = _ + 3;
++   │                 ^
++ 
++ error: undeclared `_`
++   ┌─ TEMPFILE:8:13
++   │
++ 8 │         x + _
++   │             ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:12:17
++    │
++ 12 │         let x = _ + 3;
++    │                 ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:13:13
++    │
++ 13 │         _ = _ + 1;
++    │             ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:14:13
++    │
++ 14 │         x + _
++    │             ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:18:13
++    │
++ 18 │         _ = _ + 2;
++    │             ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:19:9
++    │
++ 19 │         _
++    │         ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:23:13
++    │
++ 23 │         _ = _ + 3;
++    │             ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:24:9
++    │
++ 24 │         _
++    │         ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:28:10
++    │
++ 28 │          _
++    │          ^
++ 
++ error: undeclared `_`
+=    ┌─ TEMPFILE:32:9
+=    │
+= 32 │         _  // undefined
+-    │         ^ Invalid variable usage. Unbound variable '_'
++    │         ^
+= 
+- error[E02001]: duplicate declaration, item, or annotation
+-    ┌─ TEMPFILE:34:30
++ error: undeclared `_`
++    ┌─ TEMPFILE:35:17
+=    │
+- 34 │     public fun test8(_: u64, _: u64): u64 {
+-    │                      -       ^ Duplicate parameter with name '_'
+-    │                      │        
+-    │                      Previously declared here
++ 35 │         let _ = _;
++    │                 ^
+= 
+- error[E02001]: duplicate declaration, item, or annotation
+-    ┌─ TEMPFILE:38:37
++ error: undeclared `_`
++    ┌─ TEMPFILE:36:9
+=    │
+- 38 │     inline fun fun9(x: u64, _: u64, _: u64): u64 {
+-    │                             -       ^ Duplicate parameter with name '_'
+-    │                             │        
+-    │                             Previously declared here
++ 36 │         _
++    │         ^
+= 
+- error[E02001]: duplicate declaration, item, or annotation
+-    ┌─ TEMPFILE:44:38
++ error: cannot pass `integer` to a function which expects argument of type `|u64|u64`
++    ┌─ TEMPFILE:48:21
+=    │
+- 44 │     inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+-    │                              -       ^ Duplicate parameter with name '_'
+-    │                              │        
+-    │                              Previously declared here
+- 
+- error[E04007]: incompatible types
+-    ┌─ TEMPFILE:48:9
+-    │
+- 44 │     inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+-    │                                         -------- Expected: '|u64|u64'
+-    ·
+= 48 │         fun10(4, 3, 2)
+-    │         ^^^^^^^^^^^^^^
+-    │         │           │
+-    │         │           Given: integer
+-    │         Invalid call of '0xC0FFEE::m::fun10'. Invalid argument for parameter '_'
++    │                     ^
+= 
+= 
+= 
+= task 1 'run'. lines 79-79:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 2 'run'. lines 81-81:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 3 'run'. lines 83-83:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 4 'run'. lines 85-85:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 5 'run'. lines 87-87:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 6 'run'. lines 89-89:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 7 'run'. lines 91-91:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 8 'run'. lines 93-93:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 9 'run'. lines 95-95:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 10 'run'. lines 97-97:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 11 'run'. lines 99-99:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 12 'run'. lines 101-101:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v1.move b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v1.move
new file mode 100644
index 0000000000000..3228d1dc8101e
--- /dev/null
+++ b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v1.move
@@ -0,0 +1,101 @@
+//# publish
+module 0xc0ffee::m {
+    fun test2(): (u32, u64) {
+        (1u32, 2u64)
+    }
+
+    public fun test(_: u64): u64 {
+        let x = _ + 3;
+        x + _
+    }
+
+    public fun test3(_: u64): u64 {
+        let (_, _) = test2();
+        let x = _ + 3;
+        _ = _ + 1;
+        x + _
+    }
+
+    public fun test4(_: u64): u64 {
+        let (_, _x) = test2();
+        _ = _ + 2;
+        _
+    }
+
+    public fun test5(_: u64): u64 {
+        let (_, _) = test2();
+        _ = _ + 3;
+        _
+    }
+
+    public fun test6(_: u64): u64 {
+        let (_x, _) = test2();
+         _
+    }
+
+    // public fun test7(_y: u64): u64 {
+    //     let _ = _y;
+    //     _  // undefined
+    // }
+
+    // public fun test8(_: u64, _: u64): u64 {
+    //     let _ = _;
+    //     _
+    // }
+
+    // inline fun fun9(x: u64, _: u64, _: u64): u64 {
+    //     x + 3
+    // }
+
+    // public fun test9(): u64 {
+    //     fun9(4, 3, 2)
+    // }
+
+    // inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+    //     x + 3
+    // }
+
+    // public fun test10a(): u64 {
+    //     fun10(4, 3, 2)
+    // }
+
+    // public fun test10b(): u64 {
+    //     fun10(4, 3, |x|x + 1)
+    // }
+
+    // public fun test10c(): u64 {
+    //     fun10(4, 3, |_|1)
+    // }
+
+    inline fun fun11(x: u64, _: u64, f: |u64|u64): u64 {
+        f(x)
+    }
+
+    public fun test11(): u64 {
+        fun11(4, 3, |_|1)
+    }
+}
+
+//# run 0xc0ffee::m::test --args 4
+
+//# run 0xc0ffee::m::test3 --args 5
+
+//# run 0xc0ffee::m::test4 --args 5
+
+//# run 0xc0ffee::m::test5 --args 5
+
+//# run 0xc0ffee::m::test6 --args 5
+
+// //# run 0xc0ffee::m::test7 --args 5
+
+// //# run 0xc0ffee::m::test8 --args 5
+
+// //# run 0xc0ffee::m::test9
+
+// //# run 0xc0ffee::m::test10a
+
+// //# run 0xc0ffee::m::test10b
+
+// //# run 0xc0ffee::m::test10c
+
+//# run 0xc0ffee::m::test11
diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v1.operator-eval-lang-1.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v1.operator-eval-lang-1.exp
new file mode 100644
index 0000000000000..42508cc89789b
--- /dev/null
+++ b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v1.operator-eval-lang-1.exp
@@ -0,0 +1,21 @@
+processed 7 tasks
+
+task 1 'run'. lines 79-79:
+return values: 11
+
+task 2 'run'. lines 81-81:
+return values: 13
+
+task 3 'run'. lines 83-83:
+return values: 5
+
+task 4 'run'. lines 85-85:
+return values: 5
+
+task 5 'run'. lines 87-99:
+return values: 5
+
+task 6 'run'. lines 101-101:
+return values: 1
+
+==> Compiler v2 delivered same results!
diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v1.operator-eval-lang-2.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v1.operator-eval-lang-2.exp
new file mode 100644
index 0000000000000..3735fe1c8fe03
--- /dev/null
+++ b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v1.operator-eval-lang-2.exp
@@ -0,0 +1,127 @@
+comparison between v1 and v2 failed:
+= processed 7 tasks
+= 
++ task 0 'publish'. lines 1-77:
++ Error: compilation errors:
++  error: undeclared `_`
++   ┌─ TEMPFILE:7:17
++   │
++ 7 │         let x = _ + 3;
++   │                 ^
++ 
++ error: undeclared `_`
++   ┌─ TEMPFILE:8:13
++   │
++ 8 │         x + _
++   │             ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:12:17
++    │
++ 12 │         let x = _ + 3;
++    │                 ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:13:13
++    │
++ 13 │         _ = _ + 1;
++    │             ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:14:13
++    │
++ 14 │         x + _
++    │             ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:18:13
++    │
++ 18 │         _ = _ + 2;
++    │             ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:19:9
++    │
++ 19 │         _
++    │         ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:23:13
++    │
++ 23 │         _ = _ + 3;
++    │             ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:24:9
++    │
++ 24 │         _
++    │         ^
++ 
++ error: undeclared `_`
++    ┌─ TEMPFILE:28:10
++    │
++ 28 │          _
++    │          ^
++ 
++ 
++ 
+= task 1 'run'. lines 79-79:
+- return values: 11
++ Error: Function execution failed with VMError: {
++     major_status: LINKER_ERROR,
++     sub_status: None,
++     location: undefined,
++     indices: redacted,
++     offsets: redacted,
++ }
+= 
+= task 2 'run'. lines 81-81:
+- return values: 13
++ Error: Function execution failed with VMError: {
++     major_status: LINKER_ERROR,
++     sub_status: None,
++     location: undefined,
++     indices: redacted,
++     offsets: redacted,
++ }
+= 
+= task 3 'run'. lines 83-83:
+- return values: 5
++ Error: Function execution failed with VMError: {
++     major_status: LINKER_ERROR,
++     sub_status: None,
++     location: undefined,
++     indices: redacted,
++     offsets: redacted,
++ }
+= 
+= task 4 'run'. lines 85-85:
+- return values: 5
++ Error: Function execution failed with VMError: {
++     major_status: LINKER_ERROR,
++     sub_status: None,
++     location: undefined,
++     indices: redacted,
++     offsets: redacted,
++ }
+= 
+= task 5 'run'. lines 87-99:
+- return values: 5
++ Error: Function execution failed with VMError: {
++     major_status: LINKER_ERROR,
++     sub_status: None,
++     location: undefined,
++     indices: redacted,
++     offsets: redacted,
++ }
+= 
+= task 6 'run'. lines 101-101:
+- return values: 1
++ Error: Function execution failed with VMError: {
++     major_status: LINKER_ERROR,
++     sub_status: None,
++     location: undefined,
++     indices: redacted,
++     offsets: redacted,
++ }
+= 
diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v2.move b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v2.move
new file mode 100644
index 0000000000000..4a6c14b9441ef
--- /dev/null
+++ b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v2.move
@@ -0,0 +1,101 @@
+//# publish
+module 0xc0ffee::m {
+    fun test2(): (u32, u64) {
+        (1u32, 2u64)
+    }
+
+    // public fun test(_: u64): u64 {
+    //     let x = _ + 3;
+    //     x + _
+    // }
+
+    // public fun test3(_: u64): u64 {
+    //     let (_, _) = test2();
+    //     let x = _ + 3;
+    //     _ = _ + 1;
+    //     x + _
+    // }
+
+    // public fun test4(_: u64): u64 {
+    //     let (_, _x) = test2();
+    //     _ = _ + 2;
+    //     _
+    // }
+
+    // public fun test5(_: u64): u64 {
+    //     let (_, _) = test2();
+    //     _ = _ + 3;
+    //     _
+    // }
+
+    // public fun test6(_: u64): u64 {
+    //     let (_x, _) = test2();
+    //      _
+    // }
+
+    // public fun test7(_y: u64): u64 {
+    //     let _ = _y;
+    //     _  // undefined
+    // }
+
+    // public fun test8(_: u64, _: u64): u64 {
+    //     let _ = _;
+    //     _
+    // }
+
+    inline fun fun9(x: u64, _: u64, _: u64): u64 {
+        x + 3
+    }
+
+    public fun test9(): u64 {
+        fun9(4, 3, 2)
+    }
+
+    inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+        x + 3
+    }
+
+    // public fun test10a(): u64 {
+    //     fun10(4, 3, 2)
+    // }
+
+    public fun test10b(): u64 {
+        fun10(4, 3, |x|x + 1)
+    }
+
+    public fun test10c(): u64 {
+        fun10(4, 3, |_|1)
+    }
+
+    inline fun fun11(x: u64, _: u64, f: |u64|u64): u64 {
+        f(x)
+    }
+
+    public fun test11(): u64 {
+        fun11(4, 3, |_|1)
+    }
+}
+
+// //# run 0xc0ffee::m::test --args 4
+
+// //# run 0xc0ffee::m::test3 --args 5
+
+// //# run 0xc0ffee::m::test4 --args 5
+
+// //# run 0xc0ffee::m::test5 --args 5
+
+// //# run 0xc0ffee::m::test6 --args 5
+
+// //# run 0xc0ffee::m::test7 --args 5
+
+// //# run 0xc0ffee::m::test8 --args 5
+
+//# run 0xc0ffee::m::test9
+
+// //# run 0xc0ffee::m::test10a
+
+//# run 0xc0ffee::m::test10b
+
+//# run 0xc0ffee::m::test10c
+
+//# run 0xc0ffee::m::test11
diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v2.operator-eval-lang-1.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v2.operator-eval-lang-1.exp
new file mode 100644
index 0000000000000..712a8d39c979d
--- /dev/null
+++ b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v2.operator-eval-lang-1.exp
@@ -0,0 +1,65 @@
+comparison between v1 and v2 failed:
+= processed 5 tasks
+= 
+= task 0 'publish'. lines 1-91:
+- Error: error[E02001]: duplicate declaration, item, or annotation
++ Error: compilation errors:
++  error: duplicate declaration of `_`
+=    ┌─ TEMPFILE:38:37
+=    │
+= 38 │     inline fun fun9(x: u64, _: u64, _: u64): u64 {
+-    │                             -       ^ Duplicate parameter with name '_'
++    │                             -       ^
+=    │                             │        
+-    │                             Previously declared here
++    │                             previous declaration of `_`
+= 
+- error[E02001]: duplicate declaration, item, or annotation
++ error: duplicate declaration of `_`
+=    ┌─ TEMPFILE:44:38
+=    │
+= 44 │     inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+-    │                              -       ^ Duplicate parameter with name '_'
++    │                              -       ^
+=    │                              │        
+-    │                              Previously declared here
++    │                              previous declaration of `_`
+= 
+= 
+= 
+= task 1 'run'. lines 93-95:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 2 'run'. lines 97-97:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 3 'run'. lines 99-99:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
+= task 4 'run'. lines 101-101:
+= Error: Function execution failed with VMError: {
+=     major_status: LINKER_ERROR,
+=     sub_status: None,
+=     location: undefined,
+=     indices: redacted,
+=     offsets: redacted,
+= }
+= 
diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v2.operator-eval-lang-2.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v2.operator-eval-lang-2.exp
new file mode 100644
index 0000000000000..245c9c7f5d23b
--- /dev/null
+++ b/third_party/move/move-compiler-v2/transactional-tests/tests/operator_eval/underscore_v2.operator-eval-lang-2.exp
@@ -0,0 +1,63 @@
+comparison between v1 and v2 failed:
+- processed 5 tasks
++ processed 5 tasks
+= 
+- task 0 'publish'. lines 1-91:
+- Error: error[E02001]: duplicate declaration, item, or annotation
+-    ┌─ TEMPFILE:38:37
+-    │
+- 38 │     inline fun fun9(x: u64, _: u64, _: u64): u64 {
+-    │                             -       ^ Duplicate parameter with name '_'
+-    │                             │        
+-    │                             Previously declared here
+- 
+- error[E02001]: duplicate declaration, item, or annotation
+-    ┌─ TEMPFILE:44:38
+-    │
+- 44 │     inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+-    │                              -       ^ Duplicate parameter with name '_'
+-    │                              │        
+-    │                              Previously declared here
+- 
+- 
+- 
+= task 1 'run'. lines 93-95:
+- Error: Function execution failed with VMError: {
+-     major_status: LINKER_ERROR,
+-     sub_status: None,
+-     location: undefined,
+-     indices: redacted,
+-     offsets: redacted,
+- }
++ return values: 7
+= 
+= task 2 'run'. lines 97-97:
+- Error: Function execution failed with VMError: {
+-     major_status: LINKER_ERROR,
+-     sub_status: None,
+-     location: undefined,
+-     indices: redacted,
+-     offsets: redacted,
+- }
++ return values: 7
+= 
+= task 3 'run'. lines 99-99:
+- Error: Function execution failed with VMError: {
+-     major_status: LINKER_ERROR,
+-     sub_status: None,
+-     location: undefined,
+-     indices: redacted,
+-     offsets: redacted,
+- }
++ return values: 7
+= 
+= task 4 'run'. lines 101-101:
+- Error: Function execution failed with VMError: {
+-     major_status: LINKER_ERROR,
+-     sub_status: None,
+-     location: undefined,
+-     indices: redacted,
+-     offsets: redacted,
+- }
++ return values: 1
+= 
diff --git a/third_party/move/move-compiler/tests/move_check/naming/underscore.exp b/third_party/move/move-compiler/tests/move_check/naming/underscore.exp
new file mode 100644
index 0000000000000..c54300bf34c93
--- /dev/null
+++ b/third_party/move/move-compiler/tests/move_check/naming/underscore.exp
@@ -0,0 +1,42 @@
+error[E03009]: unbound variable
+   ┌─ tests/move_check/naming/underscore.move:38:9
+   │
+38 │         _  // undefined
+   │         ^ Invalid variable usage. Unbound variable '_'
+
+error[E02001]: duplicate declaration, item, or annotation
+   ┌─ tests/move_check/naming/underscore.move:41:30
+   │
+41 │     public fun test8(_: u64, _: u64): u64 {
+   │                      -       ^ Duplicate parameter with name '_'
+   │                      │        
+   │                      Previously declared here
+
+error[E02001]: duplicate declaration, item, or annotation
+   ┌─ tests/move_check/naming/underscore.move:46:37
+   │
+46 │     inline fun fun9(x: u64, _: u64, _: u64): u64 {
+   │                             -       ^ Duplicate parameter with name '_'
+   │                             │        
+   │                             Previously declared here
+
+error[E02001]: duplicate declaration, item, or annotation
+   ┌─ tests/move_check/naming/underscore.move:54:38
+   │
+54 │     inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+   │                              -       ^ Duplicate parameter with name '_'
+   │                              │        
+   │                              Previously declared here
+
+error[E04007]: incompatible types
+   ┌─ tests/move_check/naming/underscore.move:59:9
+   │
+54 │     inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+   │                                         -------- Expected: '|u64|u64'
+   ·
+59 │         fun10(4, 3, 2)
+   │         ^^^^^^^^^^^^^^
+   │         │           │
+   │         │           Given: integer
+   │         Invalid call of '0xC0FFEE::m::fun10'. Invalid argument for parameter '_'
+
diff --git a/third_party/move/move-compiler/tests/move_check/naming/underscore.move b/third_party/move/move-compiler/tests/move_check/naming/underscore.move
new file mode 100644
index 0000000000000..78aea11bd22d7
--- /dev/null
+++ b/third_party/move/move-compiler/tests/move_check/naming/underscore.move
@@ -0,0 +1,101 @@
+//# publish
+module 0xc0ffee::m {
+    fun test2(): (u32, u64) {
+        (1u32, 2u64)
+    }
+
+    public fun test(_: u64): u64 {
+        let x = _ + 3;
+        x + _
+    }
+
+    public fun test3(_: u64): u64 {
+        let (_, _) = test2();
+        let x = _ + 3;
+        _ = _ + 1;
+        x + _
+    }
+
+    public fun test4(_: u64): u64 {
+        let (_, _x) = test2();
+        _ = _ + 2;
+        _
+    }
+
+    public fun test5(_: u64): u64 {
+        let (_, _) = test2();
+        _ = _ + 3;
+        _
+    }
+
+    public fun test6(_: u64): u64 {
+        let (_x, _) = test2();
+         _
+    }
+
+    public fun test7(_y: u64): u64 {
+        let _ = _y;
+        _  // undefined
+    }
+
+    public fun test8(_: u64, _: u64): u64 {
+        let _ = _;
+        _
+    }
+
+    inline fun fun9(x: u64, _: u64, _: u64): u64 {
+        x + 3
+    }
+
+    public fun test9(): u64 {
+        fun9(4, 3, 2)
+    }
+
+    inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+        x + 3
+    }
+
+    public fun test10a(): u64 {
+        fun10(4, 3, 2)
+    }
+
+    public fun test10b(): u64 {
+        fun10(4, 3, |x|x + 1)
+    }
+
+    public fun test10c(): u64 {
+        fun10(4, 3, |_|1)
+    }
+
+    inline fun fun11(x: u64, _: u64, f: |u64|u64): u64 {
+        f(x)
+    }
+
+    public fun test11(): u64 {
+        fun11(4, 3, |_|1)
+    }
+}
+
+//# run 0xc0ffee::m::test --args 4
+
+//# run 0xc0ffee::m::test3 --args 5
+
+//# run 0xc0ffee::m::test4 --args 5
+
+//# run 0xc0ffee::m::test5 --args 5
+
+//# run 0xc0ffee::m::test6 --args 5
+
+//# run 0xc0ffee::m::test7 --args 5
+
+//# run 0xc0ffee::m::test8 --args 5
+
+//# run 0xc0ffee::m::test9
+
+//# run 0xc0ffee::m::test10a
+
+//# run 0xc0ffee::m::test10b
+
+//# run 0xc0ffee::m::test10c
+
+//# run 0xc0ffee::m::test11
diff --git a/third_party/move/move-compiler/transactional-tests/tests/parser/underscore.exp b/third_party/move/move-compiler/transactional-tests/tests/parser/underscore.exp
new file mode 100644
index 0000000000000..8d27f2cbd2509
--- /dev/null
+++ b/third_party/move/move-compiler/transactional-tests/tests/parser/underscore.exp
@@ -0,0 +1,154 @@
+processed 13 tasks
+
+task 0 'publish'. lines 1-77:
+Error: error[E03009]: unbound variable
+   ┌─ TEMPFILE:32:9
+   │
+32 │         _  // undefined
+   │         ^ Invalid variable usage. Unbound variable '_'
+
+error[E02001]: duplicate declaration, item, or annotation
+   ┌─ TEMPFILE:34:30
+   │
+34 │     public fun test8(_: u64, _: u64): u64 {
+   │                      -       ^ Duplicate parameter with name '_'
+   │                      │        
+   │                      Previously declared here
+
+error[E02001]: duplicate declaration, item, or annotation
+   ┌─ TEMPFILE:38:37
+   │
+38 │     inline fun fun9(x: u64, _: u64, _: u64): u64 {
+   │                             -       ^ Duplicate parameter with name '_'
+   │                             │        
+   │                             Previously declared here
+
+error[E02001]: duplicate declaration, item, or annotation
+   ┌─ TEMPFILE:44:38
+   │
+44 │     inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+   │                              -       ^ Duplicate parameter with name '_'
+   │                              │        
+   │                              Previously declared here
+
+error[E04007]: incompatible types
+   ┌─ TEMPFILE:48:9
+   │
+44 │     inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+   │                                         -------- Expected: '|u64|u64'
+   ·
+48 │         fun10(4, 3, 2)
+   │         ^^^^^^^^^^^^^^
+   │         │           │
+   │         │           Given: integer
+   │         Invalid call of '0xC0FFEE::m::fun10'. Invalid argument for parameter '_'
+
+
+
+task 1 'run'. lines 79-79:
+Error: Function execution failed with VMError: {
+    major_status: LINKER_ERROR,
+    sub_status: None,
+    location: undefined,
+    indices: [],
+    offsets: [],
+}
+
+task 2 'run'. lines 81-81:
+Error: Function execution failed with VMError: {
+    major_status: LINKER_ERROR,
+    sub_status: None,
+    location: undefined,
+    indices: [],
+    offsets: [],
+}
+
+task 3 'run'. lines 83-83:
+Error: Function execution failed with VMError: {
+    major_status: LINKER_ERROR,
+    sub_status: None,
+    location: undefined,
+    indices: [],
+    offsets: [],
+}
+
+task 4 'run'. lines 85-85:
+Error: Function execution failed with VMError: {
+    major_status: LINKER_ERROR,
+    sub_status: None,
+    location: undefined,
+    indices: [],
+    offsets: [],
+}
+
+task 5 'run'. lines 87-87:
+Error: Function execution failed with VMError: {
+    major_status: LINKER_ERROR,
+    sub_status: None,
+    location: undefined,
+    indices: [],
+    offsets: [],
+}
+
+task 6 'run'. lines 89-89:
+Error: Function execution failed with VMError: {
+    major_status: LINKER_ERROR,
+    sub_status: None,
+    location: undefined,
+    indices: [],
+    offsets: [],
+}
+
+task 7 'run'. lines 91-91:
+Error: Function execution failed with VMError: {
+    major_status: LINKER_ERROR,
+    sub_status: None,
+    location: undefined,
+    indices: [],
+    offsets: [],
+}
+
+task 8 'run'. lines 93-93:
+Error: Function execution failed with VMError: {
+    major_status: LINKER_ERROR,
+    sub_status: None,
+    location: undefined,
+    indices: [],
+    offsets: [],
+}
+
+task 9 'run'. lines 95-95:
+Error: Function execution failed with VMError: {
+    major_status: LINKER_ERROR,
+    sub_status: None,
+    location: undefined,
+    indices: [],
+    offsets: [],
+}
+
+task 10 'run'. lines 97-97:
+Error: Function execution failed with VMError: {
+    major_status: LINKER_ERROR,
+    sub_status: None,
+    location: undefined,
+    indices: [],
+    offsets: [],
+}
+
+task 11 'run'. lines 99-99:
+Error: Function execution failed with VMError: {
+    major_status: LINKER_ERROR,
+    sub_status: None,
+    location: undefined,
+    indices: [],
+    offsets: [],
+}
+
+task 12 'run'. lines 101-101:
+Error: Function execution failed with VMError: {
+    major_status: LINKER_ERROR,
+    sub_status: None,
+    location: undefined,
+    indices: [],
+    offsets: [],
+}
diff --git a/third_party/move/move-compiler/transactional-tests/tests/parser/underscore.move b/third_party/move/move-compiler/transactional-tests/tests/parser/underscore.move
new file mode 100644
index 0000000000000..78aea11bd22d7
--- /dev/null
+++ b/third_party/move/move-compiler/transactional-tests/tests/parser/underscore.move
@@ -0,0 +1,101 @@
+//# publish
+module 0xc0ffee::m {
+    fun test2(): (u32, u64) {
+        (1u32, 2u64)
+    }
+
+    public fun test(_: u64): u64 {
+        let x = _ + 3;
+        x + _
+    }
+
+    public fun test3(_: u64): u64 {
+        let (_, _) = test2();
+        let x = _ + 3;
+        _ = _ + 1;
+        x + _
+    }
+
+    public fun test4(_: u64): u64 {
+        let (_, _x) = test2();
+        _ = _ + 2;
+        _
+    }
+
+    public fun test5(_: u64): u64 {
+        let (_, _) = test2();
+        _ = _ + 3;
+        _
+    }
+
+    public fun test6(_: u64): u64 {
+        let (_x, _) = test2();
+         _
+    }
+
+    public fun test7(_y: u64): u64 {
+        let _ = _y;
+        _  // undefined
+    }
+
+    public fun test8(_: u64, _: u64): u64 {
+        let _ = _;
+        _
+    }
+
+    inline fun fun9(x: u64, _: u64, _: u64): u64 {
+        x + 3
+    }
+
+    public fun test9(): u64 {
+        fun9(4, 3, 2)
+    }
+
+    inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+        x + 3
+    }
+
+    public fun test10a(): u64 {
+        fun10(4, 3, 2)
+    }
+
+    public fun test10b(): u64 {
+        fun10(4, 3, |x|x + 1)
+    }
+
+    public fun test10c(): u64 {
+        fun10(4, 3, |_|1)
+    }
+
+    inline fun fun11(x: u64, _: u64, f: |u64|u64): u64 {
+        f(x)
+    }
+
+    public fun test11(): u64 {
+        fun11(4, 3, |_|1)
+    }
+}
+
+//# run 0xc0ffee::m::test --args 4
+
+//# run 0xc0ffee::m::test3 --args 5
+
+//# run 0xc0ffee::m::test4 --args 5
+
+//# run 0xc0ffee::m::test5 --args 5
+
+//# run 0xc0ffee::m::test6 --args 5
+
+//# run 0xc0ffee::m::test7 --args 5
+
+//# run 0xc0ffee::m::test8 --args 5
+
+//# run 0xc0ffee::m::test9
+
+//# run 0xc0ffee::m::test10a
+
+//# run 0xc0ffee::m::test10b
+
+//# run 0xc0ffee::m::test10c
+
+//# run 0xc0ffee::m::test11
diff --git a/third_party/move/move-compiler/transactional-tests/tests/parser/underscore_v1.exp b/third_party/move/move-compiler/transactional-tests/tests/parser/underscore_v1.exp
new file mode 100644
index 0000000000000..22e0cf9bc3959
--- /dev/null
+++ b/third_party/move/move-compiler/transactional-tests/tests/parser/underscore_v1.exp
@@ -0,0 +1,19 @@
+processed 7 tasks
+
+task 1 'run'. lines 79-79:
+return values: 11
+
+task 2 'run'. lines 81-81:
+return values: 13
+
+task 3 'run'. lines 83-83:
+return values: 5
+
+task 4 'run'. lines 85-85:
+return values: 5
+
+task 5 'run'. lines 87-99:
+return values: 5
+
+task 6 'run'. lines 101-101:
+return values: 1
diff --git a/third_party/move/move-compiler/transactional-tests/tests/parser/underscore_v1.move b/third_party/move/move-compiler/transactional-tests/tests/parser/underscore_v1.move
new file mode 100644
index 0000000000000..3228d1dc8101e
--- /dev/null
+++ b/third_party/move/move-compiler/transactional-tests/tests/parser/underscore_v1.move
@@ -0,0 +1,101 @@
+//# publish
+module 0xc0ffee::m {
+    fun test2(): (u32, u64) {
+        (1u32, 2u64)
+    }
+
+    public fun test(_: u64): u64 {
+        let x = _ + 3;
+        x + _
+    }
+
+    public fun test3(_: u64): u64 {
+        let (_, _) = test2();
+        let x = _ + 3;
+        _ = _ + 1;
+        x + _
+    }
+
+    public fun test4(_: u64): u64 {
+        let (_, _x) = test2();
+        _ = _ + 2;
+        _
+    }
+
+    public fun test5(_: u64): u64 {
+        let (_, _) = test2();
+        _ = _ + 3;
+        _
+    }
+
+    public fun test6(_: u64): u64 {
+        let (_x, _) = test2();
+         _
+    }
+
+    // public fun test7(_y: u64): u64 {
+    //     let _ = _y;
+    //     _  // undefined
+    // }
+
+    // public fun test8(_: u64, _: u64): u64 {
+    //     let _ = _;
+    //     _
+    // }
+
+    // inline fun fun9(x: u64, _: u64, _: u64): u64 {
+    //     x + 3
+    // }
+
+    // public fun test9(): u64 {
+    //     fun9(4, 3, 2)
+    // }
+
+    // inline fun fun10(x: u64, _: u64, _: |u64|u64): u64 {
+    //     x + 3
+    // }
+
+    // public fun test10a(): u64 {
+    //     fun10(4, 3, 2)
+    // }
+
+    // public fun test10b(): u64 {
+    //     fun10(4, 3, |x|x + 1)
+    // }
+
+    // public fun test10c(): u64 {
+    //     fun10(4, 3, |_|1)
+    // }
+
+    inline fun fun11(x: u64, _: u64, f: |u64|u64): u64 {
+        f(x)
+    }
+
+    public fun test11(): u64 {
+        fun11(4, 3, |_|1)
+    }
+}
+
+//# run 0xc0ffee::m::test --args 4
+
+//# run 0xc0ffee::m::test3 --args 5
+
+//# run 0xc0ffee::m::test4 --args 5
+
+//# run 0xc0ffee::m::test5 --args 5
+
+//# run 0xc0ffee::m::test6 --args 5
+
+// //# run 0xc0ffee::m::test7 --args 5
+
+// //# run 0xc0ffee::m::test8 --args 5
+
+// //# run 0xc0ffee::m::test9
+
+// //# run 0xc0ffee::m::test10a
+
+// //# run 0xc0ffee::m::test10b
+
+// //# run 0xc0ffee::m::test10c
+
+//# run 0xc0ffee::m::test11