From 5babeedec1f37204031cb009c4a2a0711599aeb0 Mon Sep 17 00:00:00 2001
From: TilakMaddy <tilak.madichetti@gmail.com>
Date: Sun, 15 Dec 2024 16:30:01 +0530
Subject: [PATCH 1/7] feat: warn for payable fallback func without receive func

---
 crates/sema/src/typeck/mod.rs                 | 24 +++++++++++++++++++
 .../payable_fallback_without_receive.sol      | 15 ++++++++++++
 .../payable_fallback_without_receive.stderr   | 18 ++++++++++++++
 3 files changed, 57 insertions(+)
 create mode 100644 tests/ui/parser/payable_fallback_without_receive.sol
 create mode 100644 tests/ui/parser/payable_fallback_without_receive.stderr

diff --git a/crates/sema/src/typeck/mod.rs b/crates/sema/src/typeck/mod.rs
index c2bde199..248ed6d7 100644
--- a/crates/sema/src/typeck/mod.rs
+++ b/crates/sema/src/typeck/mod.rs
@@ -11,6 +11,7 @@ pub(crate) fn check(gcx: Gcx<'_>) {
         gcx.sess,
         gcx.hir.par_contract_ids().for_each(|id| {
             check_duplicate_definitions(gcx, &gcx.symbol_resolver.contract_scopes[id]);
+            check_payable_fallback_without_receive(gcx, id);
         }),
         gcx.hir.par_source_ids().for_each(|id| {
             check_duplicate_definitions(gcx, &gcx.symbol_resolver.source_scopes[id]);
@@ -18,6 +19,29 @@ pub(crate) fn check(gcx: Gcx<'_>) {
     );
 }
 
+fn check_payable_fallback_without_receive(gcx: Gcx<'_>, contract_id: hir::ContractId) {
+    let mut payable_fallback_found = false;
+    let mut receive_found = false;
+    for item in gcx.hir.contract_items(contract_id) {
+        if let hir::Item::Function(f) = item {
+            if f.kind.is_fallback() && f.state_mutability.is_payable() {
+                payable_fallback_found = true;
+            } else if f.kind.is_receive() {
+                receive_found = true;
+            }
+        }
+    }
+
+    if payable_fallback_found && !receive_found {
+        let contract = gcx.hir.contract(contract_id);
+        gcx.dcx()
+            .warn("contract has a payable fallback function, but no receive ether function")
+            .span(contract.name.span)
+            .help("consider adding a `receive() external payable { ... }`")
+            .emit();
+    }
+}
+
 /// Checks for definitions that have the same name and parameter types in the given scope.
 fn check_duplicate_definitions(gcx: Gcx<'_>, scope: &Declarations) {
     let is_duplicate = |a: Declaration, b: Declaration| -> bool {
diff --git a/tests/ui/parser/payable_fallback_without_receive.sol b/tests/ui/parser/payable_fallback_without_receive.sol
new file mode 100644
index 00000000..f14287f8
--- /dev/null
+++ b/tests/ui/parser/payable_fallback_without_receive.sol
@@ -0,0 +1,15 @@
+contract P1 {
+    fallback() external payable {}
+}
+
+contract P2 is P1 {
+    receive() external payable {}
+}
+
+contract P3 {
+    fallback() external payable {}
+
+    receive() external payable {}
+}
+
+contract P4 is P1 {}
diff --git a/tests/ui/parser/payable_fallback_without_receive.stderr b/tests/ui/parser/payable_fallback_without_receive.stderr
new file mode 100644
index 00000000..40c8196b
--- /dev/null
+++ b/tests/ui/parser/payable_fallback_without_receive.stderr
@@ -0,0 +1,18 @@
+warning: contract has a payable fallback function, but no receive ether function
+  --> ROOT/tests/ui/parser/payable_fallback_without_receive.sol:LL:CC
+   |
+LL | contract P1 {
+   |          --
+   |
+   = help: consider adding a `receive() external payable { ... }`
+
+warning: contract has a payable fallback function, but no receive ether function
+  --> ROOT/tests/ui/parser/payable_fallback_without_receive.sol:LL:CC
+   |
+LL | contract P4 is P1 {}
+   |          --
+   |
+   = help: consider adding a `receive() external payable { ... }`
+
+warning: 2 warnings emitted
+

From 287a55bf98bf05c18270419f045703baf0d05af1 Mon Sep 17 00:00:00 2001
From: TilakMaddy <tilak.madichetti@gmail.com>
Date: Sun, 15 Dec 2024 20:49:34 +0530
Subject: [PATCH 2/7] fix

---
 crates/sema/src/typeck/mod.rs | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)

diff --git a/crates/sema/src/typeck/mod.rs b/crates/sema/src/typeck/mod.rs
index 248ed6d7..ab624675 100644
--- a/crates/sema/src/typeck/mod.rs
+++ b/crates/sema/src/typeck/mod.rs
@@ -20,20 +20,14 @@ pub(crate) fn check(gcx: Gcx<'_>) {
 }
 
 fn check_payable_fallback_without_receive(gcx: Gcx<'_>, contract_id: hir::ContractId) {
-    let mut payable_fallback_found = false;
-    let mut receive_found = false;
-    for item in gcx.hir.contract_items(contract_id) {
-        if let hir::Item::Function(f) = item {
-            if f.kind.is_fallback() && f.state_mutability.is_payable() {
-                payable_fallback_found = true;
-            } else if f.kind.is_receive() {
-                receive_found = true;
-            }
-        }
-    }
+    let contract = gcx.hir.contract(contract_id);
+
+    let payable_fallback_found =
+        contract.fallback.is_some_and(|f| gcx.hir.function(f).state_mutability.is_payable());
+
+    let receive_found = contract.receive.is_some();
 
     if payable_fallback_found && !receive_found {
-        let contract = gcx.hir.contract(contract_id);
         gcx.dcx()
             .warn("contract has a payable fallback function, but no receive ether function")
             .span(contract.name.span)

From adc0aae865fc74aecbf2f3eebf25b70284b97fa3 Mon Sep 17 00:00:00 2001
From: TilakMaddy <tilak.madichetti@gmail.com>
Date: Wed, 5 Feb 2025 23:22:09 +0530
Subject: [PATCH 3/7] fix

---
 crates/sema/src/typeck/mod.rs                           | 4 +++-
 tests/ui/parser/payable_fallback_without_receive.stderr | 8 ++++----
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/crates/sema/src/typeck/mod.rs b/crates/sema/src/typeck/mod.rs
index ab624675..48239015 100644
--- a/crates/sema/src/typeck/mod.rs
+++ b/crates/sema/src/typeck/mod.rs
@@ -5,6 +5,7 @@ use crate::{
 };
 use rayon::prelude::*;
 use solar_data_structures::{map::FxHashSet, parallel};
+use solar_interface::error_code;
 
 pub(crate) fn check(gcx: Gcx<'_>) {
     parallel!(
@@ -31,7 +32,8 @@ fn check_payable_fallback_without_receive(gcx: Gcx<'_>, contract_id: hir::Contra
         gcx.dcx()
             .warn("contract has a payable fallback function, but no receive ether function")
             .span(contract.name.span)
-            .help("consider adding a `receive() external payable { ... }`")
+            .code(error_code!(3628))
+            .help("consider changing fallback to receive")
             .emit();
     }
 }
diff --git a/tests/ui/parser/payable_fallback_without_receive.stderr b/tests/ui/parser/payable_fallback_without_receive.stderr
index 40c8196b..9fd80558 100644
--- a/tests/ui/parser/payable_fallback_without_receive.stderr
+++ b/tests/ui/parser/payable_fallback_without_receive.stderr
@@ -1,18 +1,18 @@
-warning: contract has a payable fallback function, but no receive ether function
+warning[3628]: contract has a payable fallback function, but no receive ether function
   --> ROOT/tests/ui/parser/payable_fallback_without_receive.sol:LL:CC
    |
 LL | contract P1 {
    |          --
    |
-   = help: consider adding a `receive() external payable { ... }`
+   = help: consider changing fallback to receive
 
-warning: contract has a payable fallback function, but no receive ether function
+warning[3628]: contract has a payable fallback function, but no receive ether function
   --> ROOT/tests/ui/parser/payable_fallback_without_receive.sol:LL:CC
    |
 LL | contract P4 is P1 {}
    |          --
    |
-   = help: consider adding a `receive() external payable { ... }`
+   = help: consider changing fallback to receive
 
 warning: 2 warnings emitted
 

From dc874aa630dc09e7925b7c7de3d4010fcc735a08 Mon Sep 17 00:00:00 2001
From: TilakMaddy <tilak.madichetti@gmail.com>
Date: Wed, 5 Feb 2025 23:29:05 +0530
Subject: [PATCH 4/7] fix

---
 crates/sema/src/typeck/mod.rs                           | 3 +--
 tests/ui/parser/payable_fallback_without_receive.stderr | 6 ++----
 2 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/crates/sema/src/typeck/mod.rs b/crates/sema/src/typeck/mod.rs
index 48239015..813a15ed 100644
--- a/crates/sema/src/typeck/mod.rs
+++ b/crates/sema/src/typeck/mod.rs
@@ -31,9 +31,8 @@ fn check_payable_fallback_without_receive(gcx: Gcx<'_>, contract_id: hir::Contra
     if payable_fallback_found && !receive_found {
         gcx.dcx()
             .warn("contract has a payable fallback function, but no receive ether function")
-            .span(contract.name.span)
             .code(error_code!(3628))
-            .help("consider changing fallback to receive")
+            .span_help(contract.name.span, "consider changing fallback to receive")
             .emit();
     }
 }
diff --git a/tests/ui/parser/payable_fallback_without_receive.stderr b/tests/ui/parser/payable_fallback_without_receive.stderr
index 9fd80558..bedbf6b5 100644
--- a/tests/ui/parser/payable_fallback_without_receive.stderr
+++ b/tests/ui/parser/payable_fallback_without_receive.stderr
@@ -2,17 +2,15 @@ warning[3628]: contract has a payable fallback function, but no receive ether fu
   --> ROOT/tests/ui/parser/payable_fallback_without_receive.sol:LL:CC
    |
 LL | contract P1 {
-   |          --
+   |          -- help: consider changing fallback to receive
    |
-   = help: consider changing fallback to receive
 
 warning[3628]: contract has a payable fallback function, but no receive ether function
   --> ROOT/tests/ui/parser/payable_fallback_without_receive.sol:LL:CC
    |
 LL | contract P4 is P1 {}
-   |          --
+   |          -- help: consider changing fallback to receive
    |
-   = help: consider changing fallback to receive
 
 warning: 2 warnings emitted
 

From 5ad42eb05b4444737c6fff5ed8cf956093e335f4 Mon Sep 17 00:00:00 2001
From: TilakMaddy <tilak.madichetti@gmail.com>
Date: Fri, 7 Feb 2025 20:06:35 +0530
Subject: [PATCH 5/7] fix: add warnings to test solidity file

---
 tests/ui/parser/payable_fallback_without_receive.sol | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/tests/ui/parser/payable_fallback_without_receive.sol b/tests/ui/parser/payable_fallback_without_receive.sol
index f14287f8..90d5bd71 100644
--- a/tests/ui/parser/payable_fallback_without_receive.sol
+++ b/tests/ui/parser/payable_fallback_without_receive.sol
@@ -1,4 +1,5 @@
 contract P1 {
+//~^ WARN: contract has a payable fallback function, but no receive ether function
     fallback() external payable {}
 }
 
@@ -13,3 +14,5 @@ contract P3 {
 }
 
 contract P4 is P1 {}
+//~^ WARN: contract has a payable fallback function, but no receive ether function
+

From c3f1f43478317dea2f698fec565d7cfa5f41f08d Mon Sep 17 00:00:00 2001
From: TilakMaddy <tilak.madichetti@gmail.com>
Date: Fri, 7 Feb 2025 20:14:47 +0530
Subject: [PATCH 6/7] remove space

---
 tests/ui/parser/payable_fallback_without_receive.sol | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/ui/parser/payable_fallback_without_receive.sol b/tests/ui/parser/payable_fallback_without_receive.sol
index 90d5bd71..9a5edff5 100644
--- a/tests/ui/parser/payable_fallback_without_receive.sol
+++ b/tests/ui/parser/payable_fallback_without_receive.sol
@@ -1,5 +1,5 @@
 contract P1 {
-//~^ WARN: contract has a payable fallback function, but no receive ether function
+//~^WARN: contract has a payable fallback function, but no receive ether function
     fallback() external payable {}
 }
 
@@ -14,5 +14,5 @@ contract P3 {
 }
 
 contract P4 is P1 {}
-//~^ WARN: contract has a payable fallback function, but no receive ether function
+//~^WARN: contract has a payable fallback function, but no receive ether function
 

From 843313581ad4202911c2f2b954b823d2756b80f4 Mon Sep 17 00:00:00 2001
From: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
Date: Fri, 7 Feb 2025 16:17:40 +0100
Subject: [PATCH 7/7] fix span and ui_test workaround

---
 crates/interface/src/diagnostics/context.rs   |  5 ++++-
 crates/sema/src/hir/mod.rs                    |  5 +++++
 crates/sema/src/typeck/mod.rs                 | 21 +++++++++----------
 .../payable_fallback_without_receive.sol      |  5 ++---
 .../payable_fallback_without_receive.stderr   | 14 +++++++++----
 5 files changed, 31 insertions(+), 19 deletions(-)

diff --git a/crates/interface/src/diagnostics/context.rs b/crates/interface/src/diagnostics/context.rs
index 5b22c1c4..9bb4828e 100644
--- a/crates/interface/src/diagnostics/context.rs
+++ b/crates/interface/src/diagnostics/context.rs
@@ -345,7 +345,10 @@ impl DiagCtxtInner {
         match (self.deduplicated_err_count, self.deduplicated_warn_count) {
             (0, 0) => Ok(()),
             (0, w) => {
-                self.emitter.emit_diagnostic(&Diag::new(Level::Warning, warnings(w)));
+                // TODO: Don't emit in tests since it's not handled by `ui_test`.
+                if self.flags.track_diagnostics {
+                    self.emitter.emit_diagnostic(&Diag::new(Level::Warning, warnings(w)));
+                }
                 Ok(())
             }
             (e, 0) => self.emit_diagnostic(Diag::new(Level::Error, errors(e))),
diff --git a/crates/sema/src/hir/mod.rs b/crates/sema/src/hir/mod.rs
index 26cbcb65..7395d2b9 100644
--- a/crates/sema/src/hir/mod.rs
+++ b/crates/sema/src/hir/mod.rs
@@ -612,6 +612,11 @@ pub struct Function<'hir> {
 }
 
 impl Function<'_> {
+    /// Returns the span of the `kind` keyword.
+    pub fn keyword_span(&self) -> Span {
+        self.span.with_hi(self.span.lo() + self.kind.to_str().len() as u32)
+    }
+
     /// Returns `true` if this is a free function, meaning it is not part of a contract.
     pub fn is_free(&self) -> bool {
         self.contract.is_none()
diff --git a/crates/sema/src/typeck/mod.rs b/crates/sema/src/typeck/mod.rs
index 813a15ed..2adf6795 100644
--- a/crates/sema/src/typeck/mod.rs
+++ b/crates/sema/src/typeck/mod.rs
@@ -23,17 +23,16 @@ pub(crate) fn check(gcx: Gcx<'_>) {
 fn check_payable_fallback_without_receive(gcx: Gcx<'_>, contract_id: hir::ContractId) {
     let contract = gcx.hir.contract(contract_id);
 
-    let payable_fallback_found =
-        contract.fallback.is_some_and(|f| gcx.hir.function(f).state_mutability.is_payable());
-
-    let receive_found = contract.receive.is_some();
-
-    if payable_fallback_found && !receive_found {
-        gcx.dcx()
-            .warn("contract has a payable fallback function, but no receive ether function")
-            .code(error_code!(3628))
-            .span_help(contract.name.span, "consider changing fallback to receive")
-            .emit();
+    if let Some(fallback) = contract.fallback {
+        let fallback = gcx.hir.function(fallback);
+        if fallback.state_mutability.is_payable() && contract.receive.is_none() {
+            gcx.dcx()
+                .warn("contract has a payable fallback function, but no receive ether function")
+                .span(contract.name.span)
+                .code(error_code!(3628))
+                .span_help(fallback.keyword_span(), "consider changing `fallback` to `receive`")
+                .emit();
+        }
     }
 }
 
diff --git a/tests/ui/parser/payable_fallback_without_receive.sol b/tests/ui/parser/payable_fallback_without_receive.sol
index 9a5edff5..2e6254ad 100644
--- a/tests/ui/parser/payable_fallback_without_receive.sol
+++ b/tests/ui/parser/payable_fallback_without_receive.sol
@@ -1,5 +1,5 @@
 contract P1 {
-//~^WARN: contract has a payable fallback function, but no receive ether function
+    //~^ WARN: contract has a payable fallback function, but no receive ether function
     fallback() external payable {}
 }
 
@@ -14,5 +14,4 @@ contract P3 {
 }
 
 contract P4 is P1 {}
-//~^WARN: contract has a payable fallback function, but no receive ether function
-
+//~^ WARN: contract has a payable fallback function, but no receive ether function
diff --git a/tests/ui/parser/payable_fallback_without_receive.stderr b/tests/ui/parser/payable_fallback_without_receive.stderr
index bedbf6b5..9ce0a9fb 100644
--- a/tests/ui/parser/payable_fallback_without_receive.stderr
+++ b/tests/ui/parser/payable_fallback_without_receive.stderr
@@ -2,15 +2,21 @@ warning[3628]: contract has a payable fallback function, but no receive ether fu
   --> ROOT/tests/ui/parser/payable_fallback_without_receive.sol:LL:CC
    |
 LL | contract P1 {
-   |          -- help: consider changing fallback to receive
+   |          --
+LL |
+LL |     fallback() external payable {}
+   |     -------- help: consider changing `fallback` to `receive`
    |
 
 warning[3628]: contract has a payable fallback function, but no receive ether function
   --> ROOT/tests/ui/parser/payable_fallback_without_receive.sol:LL:CC
    |
+LL |     fallback() external payable {}
+   |     -------- help: consider changing `fallback` to `receive`
+LL | }
+...
+LL |
 LL | contract P4 is P1 {}
-   |          -- help: consider changing fallback to receive
+   |          --
    |
 
-warning: 2 warnings emitted
-