Repository: gleam-lang/gleam (rust)

Issue:
Incorrect formatting of nested tuple access
<!--
Please include the following:
- Steps and code to reproduce the problem
- Versions for Gleam, OS, the runtime and any other software used
- Logs from running Gleam with the `GLEAM_LOG` environment variable set to `trace`
-->
When formatting one of these cases:
```gleam
tuple.0.field
```
```gleam
tuple.0.1
```

It produces:
```gleam
{ tuple.0 }.field
```

Instead, it should leave them as is.

Pull request description:
Fix nested tuple access formatting
This PR closes #3619

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b177ef82b..f163283f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## Unreleased
+
+### Formatter
+
+- Fixed a bug where nested tuple access wouldn't be formatted properly.
+  ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
+
 ## v1.5.0-rc1 - 2024-09-14
 
 ### Build tool
diff --git a/compiler-core/src/format.rs b/compiler-core/src/format.rs
index 7414b101b..462f72753 100644
--- a/compiler-core/src/format.rs
+++ b/compiler-core/src/format.rs
@@ -1779,7 +1779,23 @@ impl<'comments> Formatter<'comments> {
 
     fn tuple_index<'a>(&mut self, tuple: &'a UntypedExpr, index: u64) -> Document<'a> {
         match tuple {
-            UntypedExpr::TupleIndex { .. } => self.expr(tuple).surround("{", "}"),
+            // In case we have a block with a single variable tuple access we
+            // remove that redundat wrapper:
+            //
+            //     {tuple.1}.0 becomes
+            //     tuple.1.0
+            //
+            UntypedExpr::Block { statements, .. } => match statements.as_slice() {
+                [Statement::Expression(tuple @ UntypedExpr::TupleIndex { tuple: inner, .. })]
+                    // We can't apply this change if the inner thing is a
+                    // literal tuple because the compiler cannot currently parse
+                    // it:  `#(1, #(2, 3)).1.0` is a syntax error at the moment.
+                    if !inner.is_tuple() =>
+                {
+                    self.expr(tuple)
+                }
+                _ => self.expr(tuple),
+            },
             _ => self.expr(tuple),
         }
         .append(".")


Test patch:
diff --git a/compiler-core/src/format/tests.rs b/compiler-core/src/format/tests.rs
index d1db40049..e5152d044 100644
--- a/compiler-core/src/format/tests.rs
+++ b/compiler-core/src/format/tests.rs
@@ -3166,7 +3166,7 @@ fn tuple_access2() {
 fn tuple_access3() {
     assert_format!(
         r#"fn main() {
-  { tup.1 }.2
+  tup.1.2
 }
 "#
     );
diff --git a/compiler-core/src/format/tests/tuple.rs b/compiler-core/src/format/tests/tuple.rs
index 0ff9a457d..44ffe09a8 100644
--- a/compiler-core/src/format/tests/tuple.rs
+++ b/compiler-core/src/format/tests/tuple.rs
@@ -1,4 +1,4 @@
-use crate::assert_format;
+use crate::{assert_format, assert_format_rewrite};
 
 // https://github.com/gleam-lang/gleam/issues/2083
 #[test]
@@ -67,3 +67,37 @@ pub fn main() {
 "#
     );
 }
+
+#[test]
+fn nested_tuple_access() {
+    assert_format!(
+        r#"pub fn main() {
+  wibble.1.0
+}
+"#
+    );
+}
+
+#[test]
+fn nested_tuple_with_needless_block() {
+    assert_format_rewrite!(
+        r#"pub fn main() {
+  { wibble.1 }.0
+}
+"#,
+        r#"pub fn main() {
+  wibble.1.0
+}
+"#
+    );
+}
+
+#[test]
+fn nested_literal_tuple_with_needless_block_is_not_changed() {
+    assert_format!(
+        r#"pub fn main() {
+  { #(wibble, wobble).1 }.0
+}
+"#
+    );
+}