-
Notifications
You must be signed in to change notification settings - Fork 479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better errors for literal ranges in PlutusTx (PLT-8174) #5619
Changes from 35 commits
cc2703e
f46e80b
983f6cd
9daed91
62cc8fb
a4e3011
1af0087
acaa303
094efa3
8b88ac9
b3f877d
ed1bb4d
50673bc
00f8844
8c17243
f6d1f1a
33421ed
1188177
a04f3d8
1d87725
3122346
7c2130b
699ebff
beb3b52
2d41078
db2a12d
69d45d3
88c015d
a86d5c8
fa4ef71
e133bbe
fd46147
01fb753
ef3e5b8
465ddad
33d3787
2cc18fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
({cpu: 310936611 | ||
| mem: 1245566}) | ||
({cpu: 310959611 | ||
| mem: 1245666}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
({cpu: 310775611 | ||
| mem: 1244866}) | ||
({cpu: 310798611 | ||
| mem: 1244966}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
### Added | ||
|
||
- A more informative error message when the plugin encounters a literal range. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -68,7 +68,7 @@ import Control.Monad | |
import Control.Monad.Reader (ask) | ||
import Data.Array qualified as Array | ||
import Data.ByteString qualified as BS | ||
import Data.List (elemIndex) | ||
import Data.List (elemIndex, isPrefixOf, isSuffixOf) | ||
import Data.Map qualified as Map | ||
import Data.Set qualified as Set | ||
import Data.Text qualified as T | ||
|
@@ -228,6 +228,40 @@ isProbablyIntegerEq (GHC.getName -> n) | |
True | ||
isProbablyIntegerEq _ = False | ||
|
||
-- | Check for literal ranges like [1..9] and [1, 5..101]. This will also | ||
-- return `True` if there's an explicit use of `enumFromTo` or similar. | ||
isProbablyBoundedRange :: GHC.Id -> Bool | ||
isProbablyBoundedRange (GHC.getName -> n) | ||
| Just m <- GHC.nameModule_maybe n | ||
, GHC.moduleNameString (GHC.moduleName m) == "GHC.Enum" = | ||
("$fEnum" `isPrefixOf` methodName && | ||
( "_$cenumFromTo" `isSuffixOf` methodName -- [1..100] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any better way of getting names of particular instances of methods like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, this is too bad. I hadn't realized that GHC might specialize The answer to your question is, unfortunately, no. There's no way to prevent GHC from specializing a method. It is only possible to disable user written rules, not builtin rules. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if we should match on the specialized name. I don't know if we can guarantee that that isn't an arbitrarily messed up version 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We pass some information about GHC names through to the compiler in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Maybe we can come back to this in a later PR. I've run out of JIRA points on this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's what I was asking about in my original comment. Can we actually get at the original name, especially after the inlining/rewriting? Is that what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it's not too bad since we're failing instead of silently compiling differently. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess since this is inherently heuristic rather than matching a precise name there's no point doing the |
||
|| "_$cenumFromThenTo" `isSuffixOf` methodName -- [1,3..100] | ||
) | ||
) | ||
|| "enumDeltaToInteger" `isPrefixOf` methodName | ||
-- ^ These are introduced by inlining for Integer ranges in | ||
-- GHC.Enum. This also happens for Char, Word, and Int, but those types | ||
-- aren't supported in Plutus Core. | ||
where methodName = GHC.occNameString (GHC.nameOccName n) | ||
isProbablyBoundedRange _ = False | ||
|
||
-- | Check for literal ranges like [1..] and [1, 5..]. This will also return | ||
-- `True` if there's an explicit use of `enumFrom` or similar. | ||
isProbablyUnboundedRange :: GHC.Id -> Bool | ||
isProbablyUnboundedRange (GHC.getName -> n) | ||
| Just m <- GHC.nameModule_maybe n | ||
, GHC.moduleNameString (GHC.moduleName m) == "GHC.Enum" = | ||
("$fEnum" `isPrefixOf` methodName && | ||
( "_$cenumFrom" `isSuffixOf` methodName -- [1..] | ||
|| "_$cenumFromThen" `isSuffixOf` methodName -- [1,3..] | ||
) | ||
) | ||
|| "enumDeltaInteger" `isPrefixOf` methodName -- Introduced by inlining | ||
where methodName = GHC.occNameString (GHC.nameOccName n) | ||
isProbablyUnboundedRange _ = False | ||
|
||
|
||
{- Note [GHC runtime errors] | ||
GHC has a number of runtime errors for things like pattern matching failures and so on. | ||
|
||
|
@@ -730,6 +764,18 @@ compileExpr e = traceCompilation 2 ("Compiling expr:" GHC.<+> GHC.ppr e) $ do | |
GHC.Var n | ||
| isProbablyBytestringEq n -> | ||
throwPlain $ UnsupportedError "Use of Haskell ByteString equality, possibly via the Haskell Eq typeclass" | ||
GHC.Var n | ||
-- Try to produce a sensible error message if a range like [1..9] is encountered. This works | ||
-- by looking for occurrences of GHC.Enum.enumFromTo and similar functions; the same error | ||
-- occurs if these functions are used explicitly. | ||
| isProbablyBoundedRange n -> | ||
throwPlain $ UnsupportedError $ T.pack ("Use of enumFromTo or enumFromThenTo, possibly via range syntax. " ++ | ||
"Please use PlutusTx.Enum.enumFromTo or PlutusTx.Enum.enumFromThenTo instead.") | ||
-- Throw an error if we find an infinite range like [1..] | ||
GHC.Var n | ||
| isProbablyUnboundedRange n -> | ||
throwPlain $ UnsupportedError $ T.pack ("Use of enumFrom or enumFromThen, possibly via range syntax. " ++ | ||
"Unbounded ranges are not supported.") | ||
-- locally bound vars | ||
GHC.Var (lookupName scope . GHC.getName -> Just var) -> pure $ PIR.mkVar annMayInline var | ||
-- Special kinds of id | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -200,12 +200,12 @@ let | |
in | ||
letrec | ||
!`$fEnumBool_$cenumFromTo` : integer -> integer -> List integer | ||
= \(x : integer) (y : integer) -> | ||
= \(x : integer) (lim : integer) -> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See later for an explanation of why all of these have changed. |
||
ifThenElse | ||
{all dead. List integer} | ||
(lessThanEqualsInteger x y) | ||
(lessThanEqualsInteger x lim) | ||
(/\dead -> | ||
Cons {integer} x (`$fEnumBool_$cenumFromTo` (addInteger 1 x) y)) | ||
Cons {integer} x (`$fEnumBool_$cenumFromTo` (addInteger 1 x) lim)) | ||
(/\dead -> Nil {integer}) | ||
{all dead. dead} | ||
in | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Error: Unsupported feature: Use of enumFrom or enumFromThen, possibly via range syntax. Unbounded ranges are not supported. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Error: Unsupported feature: Use of enumFrom or enumFromThen, possibly via range syntax. Unbounded ranges are not supported. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Error: Unsupported feature: Use of enumFromTo or enumFromThenTo, possibly via range syntax. Please use PlutusTx.Enum.enumFromTo or PlutusTx.Enum.enumFromThenTo instead. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Error: Unsupported feature: Use of enumFromTo or enumFromThenTo, possibly via range syntax. Please use PlutusTx.Enum.enumFromTo or PlutusTx.Enum.enumFromThenTo instead. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Error: Unsupported feature: Infinite ranges are not supported |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Error: Unsupported feature: Infinite ranges are not supported |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Error: Unsupported feature: Literal ranges are not supported: please use PlutusTx.Enum.enumFromTo or PlutusTx.Enum.enumFromThenTo |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Error: Unsupported feature: Literal ranges are not supported: please use PlutusTx.Enum.enumFromTo or PlutusTx.Enum.enumFromThenTo |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,10 +16,10 @@ module Plugin.Errors.Spec where | |
|
||
import Test.Tasty.Extras | ||
|
||
import PlutusCore.Test | ||
import PlutusCore.Test (goldenUPlc) | ||
import PlutusTx.Builtins qualified as Builtins | ||
import PlutusTx.Code | ||
import PlutusTx.Plugin | ||
import PlutusTx.Code (CompiledCode) | ||
import PlutusTx.Plugin.Utils (plc) | ||
import PlutusTx.Test () | ||
|
||
import Data.Proxy | ||
|
@@ -33,19 +33,23 @@ import GHC.Num.Integer | |
{- HLINT ignore -} | ||
|
||
errors :: TestNested | ||
errors = testNestedGhc "Errors" [ | ||
goldenUPlc "machInt" machInt | ||
-- FIXME: This fails differently in nix, possibly due to slightly different optimization settings | ||
-- , goldenPlc "negativeInt" negativeInt | ||
, goldenUPlc "caseInt" caseInt | ||
, goldenUPlc "stringLiteral" stringLiteral | ||
, goldenUPlc "recursiveNewtype" recursiveNewtype | ||
, goldenUPlc "mutualRecursionUnfoldingsLocal" mutualRecursionUnfoldingsLocal | ||
, goldenUPlc "literalCaseInt" literalCaseInt | ||
, goldenUPlc "literalCaseBs" literalCaseBs | ||
, goldenUPlc "literalAppendBs" literalAppendBs | ||
, goldenUPlc "literalCaseOther" literalCaseOther | ||
] | ||
errors = testNestedGhc "Errors" | ||
[ goldenUPlc "machInt" machInt | ||
-- FIXME: This fails differently in nix, possibly due to slightly different optimization settings | ||
-- , goldenPlc "negativeInt" negativeInt | ||
, goldenUPlc "caseInt" caseInt | ||
, goldenUPlc "stringLiteral" stringLiteral | ||
, goldenUPlc "recursiveNewtype" recursiveNewtype | ||
, goldenUPlc "mutualRecursionUnfoldingsLocal" mutualRecursionUnfoldingsLocal | ||
, goldenUPlc "literalCaseInt" literalCaseInt | ||
, goldenUPlc "literalCaseBs" literalCaseBs | ||
, goldenUPlc "literalAppendBs" literalAppendBs | ||
, goldenUPlc "literalCaseOther" literalCaseOther | ||
, goldenUPlc "rangeEnumFromTo" rangeEnumFromTo | ||
, goldenUPlc "rangeEnumFromThenTo" rangeEnumFromThenTo | ||
, goldenUPlc "rangeEnumFrom" rangeEnumFrom | ||
, goldenUPlc "rangeEnumFromThen" rangeEnumFromThen | ||
] | ||
|
||
machInt :: CompiledCode Int | ||
machInt = plc (Proxy @"machInt") (1::Int) | ||
|
@@ -95,3 +99,17 @@ instance Eq AType where | |
|
||
literalCaseOther :: CompiledCode (AType -> AType) | ||
literalCaseOther = plc (Proxy @"literalCaseOther") (\x -> case x of { "abc" -> ""; x -> x}) | ||
|
||
-- Tests for literal ranges (and the corresponding methods in GHC.Enum). These | ||
-- should all fail with informative error messages. | ||
rangeEnumFromTo :: CompiledCode [Integer] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice tests. |
||
rangeEnumFromTo = plc (Proxy @"rangeEnumFromTo") [1..50] | ||
|
||
rangeEnumFromThenTo :: CompiledCode [Integer] | ||
rangeEnumFromThenTo = plc (Proxy @"rangeEnumFromThenTo") [1,7..50] | ||
|
||
rangeEnumFrom :: CompiledCode [Integer] | ||
rangeEnumFrom = plc (Proxy @"rangeEnumFrom") [1..] | ||
|
||
rangeEnumFromThen :: CompiledCode [Integer] | ||
rangeEnumFromThen = plc (Proxy @"rangeEnumFromThen") [1,5..] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this because
plutus-tx
containsplutus-tx-test
and I was getting very confused.