From a820f9002d8f75385aaaa141ac3c6f001e8a9874 Mon Sep 17 00:00:00 2001
From: Sylvain Henry <sylvain@haskus.fr>
Date: Thu, 1 Jul 2021 16:55:47 +0200
Subject: [PATCH] Detect underflow in fromIntegral/Int->Natural rule

Fix #20066
---
 libraries/base/GHC/Real.hs                | 18 +++++++++++++++---
 testsuite/tests/lib/integer/T20066.hs     | 12 ++++++++++++
 testsuite/tests/lib/integer/T20066.stderr |  1 +
 testsuite/tests/lib/integer/all.T         |  1 +
 4 files changed, 29 insertions(+), 3 deletions(-)
 create mode 100644 testsuite/tests/lib/integer/T20066.hs
 create mode 100644 testsuite/tests/lib/integer/T20066.stderr

diff --git a/libraries/base/GHC/Real.hs b/libraries/base/GHC/Real.hs
index df4143e1eed4..6c7ae43e5c83 100644
--- a/libraries/base/GHC/Real.hs
+++ b/libraries/base/GHC/Real.hs
@@ -597,7 +597,7 @@ fromIntegral = fromInteger . toInteger
   #-}
 
 -- Don't forget the type signatures in the following rules! Without a type
--- signature we end up with the rule:
+-- signature we ended up with the rule:
 --
 --  "fromIntegral/Int->Natural" forall a (d::Integral a).
 --        fromIntegral @a @Natural = naturalFromWord . fromIntegral @a d
@@ -606,12 +606,24 @@ fromIntegral = fromInteger . toInteger
 --
 -- This rule wraps any Integral input into Word's range. As a consequence,
 -- (2^64 :: Integer) was incorrectly wrapped to (0 :: Natural), see #19345.
+--
+-- A follow-up issue with this rule was that no underflow exception was raised
+-- for negative Int values (see #20066). We now use a naturalFromInt helper
+-- function to restore this behavior.
 
 {-# RULES
-"fromIntegral/Word->Natural"    fromIntegral = naturalFromWord                :: Word -> Natural
-"fromIntegral/Int->Natural"     fromIntegral = naturalFromWord . fromIntegral :: Int -> Natural
+"fromIntegral/Word->Natural"    fromIntegral = naturalFromWord :: Word -> Natural
+"fromIntegral/Int->Natural"     fromIntegral = naturalFromInt  :: Int -> Natural
   #-}
 
+-- | Convert an Int into a Natural, throwing an underflow exception for negative
+-- values.
+naturalFromInt :: Int -> Natural
+{-# INLINE naturalFromInt #-}
+naturalFromInt x
+  | x < 0     = underflowError
+  | otherwise = naturalFromWord (fromIntegral x)
+
 -- | general coercion to fractional types
 realToFrac :: (Real a, Fractional b) => a -> b
 {-# NOINLINE [1] realToFrac #-}
diff --git a/testsuite/tests/lib/integer/T20066.hs b/testsuite/tests/lib/integer/T20066.hs
new file mode 100644
index 000000000000..9cb67a652030
--- /dev/null
+++ b/testsuite/tests/lib/integer/T20066.hs
@@ -0,0 +1,12 @@
+{-# OPTIONS_GHC -O #-}
+module Main where
+
+import Numeric.Natural
+
+i :: Int
+i = -10
+
+main :: IO ()
+main = let n :: Natural
+           n = fromIntegral i
+        in print n
diff --git a/testsuite/tests/lib/integer/T20066.stderr b/testsuite/tests/lib/integer/T20066.stderr
new file mode 100644
index 000000000000..589cc2a4a804
--- /dev/null
+++ b/testsuite/tests/lib/integer/T20066.stderr
@@ -0,0 +1 @@
+T20066: arithmetic underflow
diff --git a/testsuite/tests/lib/integer/all.T b/testsuite/tests/lib/integer/all.T
index c6710c69a1f6..4366955e817c 100644
--- a/testsuite/tests/lib/integer/all.T
+++ b/testsuite/tests/lib/integer/all.T
@@ -32,3 +32,4 @@ test('integerImportExport', [omit_ways(['ghci'])], compile_and_run, [''])
 #test('integerGmpInternals', [], compile_and_run, [''])
 
 test('T19345', [], compile_and_run, [''])
+test('T20066', [exit_code(1)], compile_and_run, [''])
-- 
GitLab