diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c48bbbc5..78375a289 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - ghc: [ghc965, ghc982, ghc9101] + ghc: [ghc984, ghc9101, ghc9121] name: Build and test on ${{ matrix.ghc }} runs-on: ubuntu-latest steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 14dc14242..4a7383876 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,19 @@ * Correctly format multi-line parentheses in arrow `do` blocks. [Issue 1144](https://github.com/tweag/ormolu/issues/1144). +* Switched to `ghc-lib-parser-9.12`, with the following new syntactic features: + * GHC proposal [#522](https://github.com/ghc-proposals/ghc-proposals/blob/c9401f037cb22d1661931b2ec621925101052997/proposals/0522-or-patterns.rst): `OrPatterns` (enabled by default) + * GHC proposal [#569](https://github.com/ghc-proposals/ghc-proposals/blob/c9401f037cb22d1661931b2ec621925101052997/proposals/0569-multiline-strings.rst): `MultilineStrings` (disabled by default) + * GHC proposal [#409](https://github.com/ghc-proposals/ghc-proposals/blob/f79438cf8dbfcd90187f7af3a380515ffe45dbdc/proposals/0409-exportable-named-default.rst): `NamedDefaults` (enabled by default) + * GHC proposal [#281](https://github.com/ghc-proposals/ghc-proposals/blob/c9401f037cb22d1661931b2ec621925101052997/proposals/0281-visible-forall.rst): accept more types in terms: `forall` quantifications, constraint arrows `=>`, type arrows `->` (enabled by default) + * Part of GHC proposal [#425](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0425-decl-invis-binders.rst): wildcard binders (enabled by default) + +* Correctly format non-promoted type-level tuples with `NoListTuplePuns`. [Issue + 1146](https://github.com/tweag/ormolu/issues/1146). + +* Updated to `Cabal-syntax-3.14`. [Issue + 1152](https://github.com/tweag/ormolu/issues/1152). + ## Ormolu 0.7.7.0 * Use single-line layout for parens around single-line content. [Issue diff --git a/cabal.project b/cabal.project index 126477571..9a5a4f553 100644 --- a/cabal.project +++ b/cabal.project @@ -1,5 +1,6 @@ packages: . extract-hackage-info tests: True +multi-repl: True constraints: ormolu +dev diff --git a/data/examples/declaration/data/wildcard-binders-out.hs b/data/examples/declaration/data/wildcard-binders-out.hs new file mode 100644 index 000000000..20d13113f --- /dev/null +++ b/data/examples/declaration/data/wildcard-binders-out.hs @@ -0,0 +1 @@ +data Proxy _ = Proxy diff --git a/data/examples/declaration/data/wildcard-binders.hs b/data/examples/declaration/data/wildcard-binders.hs new file mode 100644 index 000000000..20d13113f --- /dev/null +++ b/data/examples/declaration/data/wildcard-binders.hs @@ -0,0 +1 @@ +data Proxy _ = Proxy diff --git a/data/examples/declaration/default/default-out.hs b/data/examples/declaration/default/default-out.hs index 681fe59d2..383e99367 100644 --- a/data/examples/declaration/default/default-out.hs +++ b/data/examples/declaration/default/default-out.hs @@ -1,3 +1,5 @@ +module MyModule (default Monoid) where + default (Int, Foo, Bar) default @@ -5,3 +7,9 @@ default Foo, Bar ) + +default Num (Int, Float) + +default IsList ([], Vector) + +default IsString (Text.Text, Foundation.String, String) diff --git a/data/examples/declaration/default/default.hs b/data/examples/declaration/default/default.hs index e0bda6b8d..86d2064d0 100644 --- a/data/examples/declaration/default/default.hs +++ b/data/examples/declaration/default/default.hs @@ -1,6 +1,13 @@ +module MyModule (default Monoid) where + default ( Int , Foo , Bar ) default ( Int , Foo, Bar ) + +default Num (Int, Float) +default IsList ([], Vector) + +default IsString (Text.Text, Foundation.String, String) diff --git a/data/examples/declaration/type/promotion-no-puns-out.hs b/data/examples/declaration/type/promotion-no-puns-out.hs new file mode 100644 index 000000000..8579e5389 --- /dev/null +++ b/data/examples/declaration/type/promotion-no-puns-out.hs @@ -0,0 +1,5 @@ +{-# LANGUAGE NoListTuplePuns #-} + +type X = (Int, String) + +type Y = [String, Int] diff --git a/data/examples/declaration/type/promotion-no-puns.hs b/data/examples/declaration/type/promotion-no-puns.hs new file mode 100644 index 000000000..16574ebbb --- /dev/null +++ b/data/examples/declaration/type/promotion-no-puns.hs @@ -0,0 +1,5 @@ +{-# Language NoListTuplePuns #-} + +type X = (Int, String) + +type Y = [String, Int] diff --git a/data/examples/declaration/type/wildcard-binders-out.hs b/data/examples/declaration/type/wildcard-binders-out.hs new file mode 100644 index 000000000..07d23972c --- /dev/null +++ b/data/examples/declaration/type/wildcard-binders-out.hs @@ -0,0 +1 @@ +type Const a _ = a diff --git a/data/examples/declaration/type/wildcard-binders.hs b/data/examples/declaration/type/wildcard-binders.hs new file mode 100644 index 000000000..07d23972c --- /dev/null +++ b/data/examples/declaration/type/wildcard-binders.hs @@ -0,0 +1 @@ +type Const a _ = a diff --git a/data/examples/declaration/value/function/multiline-strings-0-out.hs b/data/examples/declaration/value/function/multiline-strings-0-out.hs new file mode 100644 index 000000000..c8d25a693 --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-0-out.hs @@ -0,0 +1,29 @@ +{-# LANGUAGE MultilineStrings #-} + +s = + """Line 1 + Line 2 + Line 3 + """ + +s_2 = + """Line 1 + Line 2 + Line 3 + """ + +-- equivalent to +s' = "Line 1\n Line 2\nLine 3" + +-- the following are equivalent +s = """hello world""" + +s' = "hello world" + +s = + """ hello + world + """ + +-- equivalent to +s' = " hello\nworld" diff --git a/data/examples/declaration/value/function/multiline-strings-0.hs b/data/examples/declaration/value/function/multiline-strings-0.hs new file mode 100644 index 000000000..b3d8a389a --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-0.hs @@ -0,0 +1,31 @@ +{-# LANGUAGE MultilineStrings #-} + +s = + """Line 1 + Line 2 + Line 3 + """ + +s_2 = + """\ + \Line 1 + Line 2 + Line 3 + """ + +-- equivalent to +s' = "Line 1\n Line 2\nLine 3" + + +-- the following are equivalent +s = """hello world""" +s' = "hello world" + + +s = + """ hello + world + """ + +-- equivalent to +s' = " hello\nworld" diff --git a/data/examples/declaration/value/function/multiline-strings-1-out.hs b/data/examples/declaration/value/function/multiline-strings-1-out.hs new file mode 100644 index 000000000..e8ba128b3 --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-1-out.hs @@ -0,0 +1,10 @@ +{-# LANGUAGE MultilineStrings #-} + +s = + """ + a b c d e + f g + """ + +-- equivalent to +s' = "a b c d e\nf g" diff --git a/data/examples/declaration/value/function/multiline-strings-1.hs b/data/examples/declaration/value/function/multiline-strings-1.hs new file mode 100644 index 000000000..113f6e8d9 --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-1.hs @@ -0,0 +1,11 @@ +{-# LANGUAGE MultilineStrings #-} + +s = + """ + a b\ + \ c d e + f g + """ + +-- equivalent to +s' = "a b c d e\nf g" diff --git a/data/examples/declaration/value/function/multiline-strings-2-out.hs b/data/examples/declaration/value/function/multiline-strings-2-out.hs new file mode 100644 index 000000000..0260a6e9f --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-2-out.hs @@ -0,0 +1,11 @@ +{-# LANGUAGE MultilineStrings #-} + +s = + """ + a + b + c + """ + +-- equivalent to +s' = "a\nb\nc" diff --git a/data/examples/declaration/value/function/multiline-strings-2.hs b/data/examples/declaration/value/function/multiline-strings-2.hs new file mode 100644 index 000000000..b33dc5f6b --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-2.hs @@ -0,0 +1,11 @@ +{-# LANGUAGE MultilineStrings #-} + +s = + """ + a + b + c + """ + +-- equivalent to +s' = "a\nb\nc" diff --git a/data/examples/declaration/value/function/multiline-strings-3-out.hs b/data/examples/declaration/value/function/multiline-strings-3-out.hs new file mode 100644 index 000000000..a8b7e6e4f --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-3-out.hs @@ -0,0 +1,32 @@ +{-# LANGUAGE MultilineStrings #-} + +s = + """ + + a + b + c + """ + +-- equivalent to +s' = "\na\nb\nc" + +s1 = + """ a + b + c + """ + +s2 = + """ + a + b + c + """ + +-- In the current proposal, these are equivalent to +-- the below. If leading newline were removed at the +-- beginning, both would result in s1'. +s1' = " a\nb\nc" + +s2' = "a\nb\nc" diff --git a/data/examples/declaration/value/function/multiline-strings-3.hs b/data/examples/declaration/value/function/multiline-strings-3.hs new file mode 100644 index 000000000..f79ab72dd --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-3.hs @@ -0,0 +1,32 @@ +{-# LANGUAGE MultilineStrings #-} + +s = + """ + + a + b + c + """ + +-- equivalent to +s' = "\na\nb\nc" + + +s1 = + """ a + b + c + """ + +s2 = + """ + a + b + c + """ + +-- In the current proposal, these are equivalent to +-- the below. If leading newline were removed at the +-- beginning, both would result in s1'. +s1' = " a\nb\nc" +s2' = "a\nb\nc" diff --git a/data/examples/declaration/value/function/multiline-strings-4-out.hs b/data/examples/declaration/value/function/multiline-strings-4-out.hs new file mode 100644 index 000000000..fede4af91 --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-4-out.hs @@ -0,0 +1,25 @@ +{-# LANGUAGE MultilineStrings #-} + +s = + """ + a + b + + """ + +-- equivalent to +s' = "a\nb\n" + +s1 = + """ + line 1 + line 2 + """ + +s2 = "line 3" + +s3 = + """ + line 4 + line 5 + """ diff --git a/data/examples/declaration/value/function/multiline-strings-4.hs b/data/examples/declaration/value/function/multiline-strings-4.hs new file mode 100644 index 000000000..417c8fa70 --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-4.hs @@ -0,0 +1,26 @@ +{-# LANGUAGE MultilineStrings #-} + +s = + """ + a + b + + """ + +-- equivalent to +s' = "a\nb\n" + + +s1 = + """ + line 1 + line 2 + """ + +s2 = "line 3" + +s3 = + """ + line 4 + line 5 + """ diff --git a/data/examples/declaration/value/function/multiline-strings-5-out.hs b/data/examples/declaration/value/function/multiline-strings-5-out.hs new file mode 100644 index 000000000..47f5c6c0e --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-5-out.hs @@ -0,0 +1,26 @@ +{-# LANGUAGE MultilineStrings #-} + +s1 = + """ + a + b + c + """ + +s1' = "a\nb\nc" + +s2 = + """ + \& a + b + c + """ + +s2_2 = + """ + \& a + \& b + \& c + """ + +s2' = " a\n b\n c" diff --git a/data/examples/declaration/value/function/multiline-strings-5.hs b/data/examples/declaration/value/function/multiline-strings-5.hs new file mode 100644 index 000000000..71d85e867 --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-5.hs @@ -0,0 +1,26 @@ +{-# LANGUAGE MultilineStrings #-} + +s1 = + """ + a + b + c + """ + +s1' = "a\nb\nc" + +s2 = + """ + \& a + b + c + """ + +s2_2 = + """ + \& a + \& b + \& c + """ + +s2' = " a\n b\n c" diff --git a/data/examples/declaration/value/function/multiline-strings-6-out.hs b/data/examples/declaration/value/function/multiline-strings-6-out.hs new file mode 100644 index 000000000..bf96e7c0e --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-6-out.hs @@ -0,0 +1,10 @@ +{-# LANGUAGE MultilineStrings #-} + +x = + """ + This is a literal multiline string: + \"\"\" + Hello + world! + \""" + """ diff --git a/data/examples/declaration/value/function/multiline-strings-6.hs b/data/examples/declaration/value/function/multiline-strings-6.hs new file mode 100644 index 000000000..bf96e7c0e --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-6.hs @@ -0,0 +1,10 @@ +{-# LANGUAGE MultilineStrings #-} + +x = + """ + This is a literal multiline string: + \"\"\" + Hello + world! + \""" + """ diff --git a/data/examples/declaration/value/function/multiline-strings-7-out.hs b/data/examples/declaration/value/function/multiline-strings-7-out.hs new file mode 100644 index 000000000..74960d6de --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-7-out.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE MultilineStrings #-} + +printf + """ + instance Aeson.FromJSON %s where + parseJSON = + Aeson.withText "%s" $ \\s -> + either Aeson.parseFail pure $ + parsePrinterOptType (Text.unpack s) + + instance PrinterOptsFieldType %s where + parsePrinterOptType s = + case s of + %s + _ -> + Left . unlines $ + [ "unknown value: " <> show s + , "Valid values are: %s" + ] + + """ + fieldTypeName + fieldTypeName + fieldTypeName + ( unlines_ + [ printf " \"%s\" -> Right %s" val con + | (con, val) <- enumOptions + ] + ) + (renderEnumOptions enumOptions) diff --git a/data/examples/declaration/value/function/multiline-strings-7.hs b/data/examples/declaration/value/function/multiline-strings-7.hs new file mode 100644 index 000000000..74960d6de --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-7.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE MultilineStrings #-} + +printf + """ + instance Aeson.FromJSON %s where + parseJSON = + Aeson.withText "%s" $ \\s -> + either Aeson.parseFail pure $ + parsePrinterOptType (Text.unpack s) + + instance PrinterOptsFieldType %s where + parsePrinterOptType s = + case s of + %s + _ -> + Left . unlines $ + [ "unknown value: " <> show s + , "Valid values are: %s" + ] + + """ + fieldTypeName + fieldTypeName + fieldTypeName + ( unlines_ + [ printf " \"%s\" -> Right %s" val con + | (con, val) <- enumOptions + ] + ) + (renderEnumOptions enumOptions) diff --git a/data/examples/declaration/value/function/multiline-strings-8-out.hs b/data/examples/declaration/value/function/multiline-strings-8-out.hs new file mode 100644 index 000000000..53285d0b2 --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-8-out.hs @@ -0,0 +1,12 @@ +{-# LANGUAGE MultilineStrings #-} + +type Foo = + """ + yeah + yeah""" + +foo = + foo + @"""yeah + yeah + """ diff --git a/data/examples/declaration/value/function/multiline-strings-8.hs b/data/examples/declaration/value/function/multiline-strings-8.hs new file mode 100644 index 000000000..4a81a0a58 --- /dev/null +++ b/data/examples/declaration/value/function/multiline-strings-8.hs @@ -0,0 +1,9 @@ +{-# LANGUAGE MultilineStrings #-} + +type Foo = """ + yeah + yeah""" + +foo = foo @"""yeah + yeah + """ diff --git a/data/examples/declaration/value/function/pattern/or-patterns-out.hs b/data/examples/declaration/value/function/pattern/or-patterns-out.hs new file mode 100644 index 000000000..8da9465e9 --- /dev/null +++ b/data/examples/declaration/value/function/pattern/or-patterns-out.hs @@ -0,0 +1,36 @@ +{-# LANGUAGE PatternSynonyms #-} + +tasty (Cupcake; Cookie) = True +tasty (Liquorice; Raisins) = False + +f :: (Eq a, Show a) => a -> a -> Bool +f a ((== a) -> True; show -> "yes") = True +f _ _ = False + +small (abs -> (0; 1; 2); 3) = True -- -3 is not small +small _ = False + +type Coll a = Either [a] (Set a) + +pattern None <- (Left []; Right (toList -> [])) + +case e of + 1; 2; 3 -> x + 4; (5; 6) -> y + +sane e = case e of + 1 + 2 + 3 -> a + 4 + 5 + 6 -> b + 7; 8 -> c + +insane e = case e of + A _ _ + B _ + C -> 3 + (D; E (Just _) Nothing) -> + 4 + F -> 5 diff --git a/data/examples/declaration/value/function/pattern/or-patterns.hs b/data/examples/declaration/value/function/pattern/or-patterns.hs new file mode 100644 index 000000000..565b6ccb7 --- /dev/null +++ b/data/examples/declaration/value/function/pattern/or-patterns.hs @@ -0,0 +1,33 @@ +{-# LANGUAGE PatternSynonyms #-} + +tasty (Cupcake; Cookie) = True +tasty (Liquorice; Raisins) = False + +f :: (Eq a, Show a) => a -> a -> Bool +f a ((== a) -> True; show -> "yes") = True +f _ _ = False + +small (abs -> (0; 1; 2); 3) = True -- -3 is not small +small _ = False + +type Coll a = Either [a] (Set a) +pattern None <- (Left []; Right (toList -> [])) + +case e of + 1; 2; 3 -> x + 4; (5; 6) -> y + +sane e = case e of + 1 + 2 + 3 -> a + 4 + 5;6 -> b + 7;8 -> c + +insane e = case e of + A _ _; B _ + C -> 3 + (D; E (Just _) Nothing) + -> 4 + F -> 5 diff --git a/data/examples/declaration/value/function/required-type-arguments-2-out.hs b/data/examples/declaration/value/function/required-type-arguments-2-out.hs new file mode 100644 index 000000000..972dd5341 --- /dev/null +++ b/data/examples/declaration/value/function/required-type-arguments-2-out.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE LinearTypes #-} +{-# LANGUAGE UnicodeSyntax #-} + +ex1 = f (forall a. Proxy a) + +ex2 = f ((ctx) => Int) + +ex2' = f ((ctx, ctx') => Int) + +ex3 = f (String -> Bool) + +long = + f + ( forall m a. + (A a, M m) => + String -> + Bool %1 -> + Maybe Int -> + Maybe + (String, Int) %1 -> + Word %m -> Text + ) diff --git a/data/examples/declaration/value/function/required-type-arguments-2.hs b/data/examples/declaration/value/function/required-type-arguments-2.hs new file mode 100644 index 000000000..ed9fd626a --- /dev/null +++ b/data/examples/declaration/value/function/required-type-arguments-2.hs @@ -0,0 +1,14 @@ +{-# LANGUAGE UnicodeSyntax #-} +{-# LANGUAGE LinearTypes #-} + +ex1 = f (forall a. Proxy a) +ex2 = f (ctx => Int) +ex2' = f ((ctx,ctx') => Int) +ex3 = f (String -> Bool) + +long = f (forall m a. (A a, M m) => String + -> Bool %1 -> + Maybe Int + -> Maybe + (String,Int) + ⊸ Word %m -> Text ) diff --git a/expected-failures/esqueleto.txt b/expected-failures/esqueleto.txt index a549c52a7..02d3d8ce5 100644 --- a/expected-failures/esqueleto.txt +++ b/expected-failures/esqueleto.txt @@ -1,3 +1,3 @@ -src/Database/Esqueleto/Internal/Internal.hs:434:1 +src/Database/Esqueleto/Internal/Internal.hs:(433,5)-(434,0) The GHC parser (in Haddock mode) failed: - [GHC-21231] lexical error in string/character literal at character 's' + [GHC-21231] lexical error at character 's' diff --git a/expected-failures/hlint.txt b/expected-failures/hlint.txt index dcc5d466b..f929bcedf 100644 --- a/expected-failures/hlint.txt +++ b/expected-failures/hlint.txt @@ -1,5 +1,5 @@ src/Extension.hs -@@ -17,7 +17,8 @@ +@@ -19,7 +19,8 @@ UnboxedTuples, UnboxedSums, -- breaks (#) lens operator QuasiQuotes, -- breaks [x| ...], making whitespace free list comps break @@ -13,7 +13,7 @@ src/Extension.hs Formatting is not idempotent. Please, consider reporting the bug. src/Hint/Bracket.hs -@@ -265,8 +265,11 @@ +@@ -294,8 +294,11 @@ let y = noLocA $ HsApp EpAnnNotUsed a1 (nlHsPar a2), let r = Replace Expr (toSSA e) [("a", toSSA a1), ("b", toSSA a2)] "a (b)" ] diff --git a/expected-failures/pandoc.txt b/expected-failures/pandoc.txt index ed912b44f..66d775d80 100644 --- a/expected-failures/pandoc.txt +++ b/expected-failures/pandoc.txt @@ -1,5 +1,18 @@ +src/Text/Pandoc/ImageSize.hs +@@ -131,6 +131,7 @@ + "\x01\x00\x00\x00" + | B.take 4 (B.drop 40 img) == " EMF" -> + return Emf +- "\xEF\xBB\xBF<" -> -- BOM before svg ++ "\xEF\xBB\xBF<" -> ++ -- BOM before svg + imageType (B.drop 3 img) + _ -> mzero + + Formatting is not idempotent. + Please, consider reporting the bug. src/Text/Pandoc/Readers/Org/Inlines.hs -@@ -186,7 +186,8 @@ +@@ -182,7 +182,8 @@ cs' <- cs case cs' of [] -> return [] diff --git a/expected-failures/postgrest.txt b/expected-failures/postgrest.txt index ee4704b3a..6c7765bb8 100644 --- a/expected-failures/postgrest.txt +++ b/expected-failures/postgrest.txt @@ -1,5 +1,5 @@ src/PostgREST/Plan.hs -@@ -273,13 +273,12 @@ +@@ -596,13 +596,12 @@ && ( -- /projects?select=clients!projects_client_id_fkey(*) matchConstraint hnt relCardinality diff --git a/extract-hackage-info/extract-hackage-info.cabal b/extract-hackage-info/extract-hackage-info.cabal index 756d69bc6..fe849d482 100644 --- a/extract-hackage-info/extract-hackage-info.cabal +++ b/extract-hackage-info/extract-hackage-info.cabal @@ -16,7 +16,7 @@ executable extract-hackage-info -Wunused-packages build-depends: - Cabal-syntax >=3.12 && <3.13, + Cabal-syntax >=3.14 && <3.15, aeson >=2.2 && <3, base >=4.12 && <5, binary >=0.8 && <0.9, diff --git a/extract-hackage-info/src/Main.hs b/extract-hackage-info/src/Main.hs index 8ebd8d2d4..ea97b4b9d 100644 --- a/extract-hackage-info/src/Main.hs +++ b/extract-hackage-info/src/Main.hs @@ -17,7 +17,6 @@ import Data.Binary.Get qualified as Binary import Data.Binary.Put qualified as Binary import Data.ByteString qualified as ByteString import Data.ByteString.Lazy qualified as BL -import Data.List import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map import Data.Maybe diff --git a/flake.lock b/flake.lock index 5b8cdd5ba..38cefbd95 100644 --- a/flake.lock +++ b/flake.lock @@ -121,11 +121,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -139,11 +139,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1726560853, - "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -176,11 +176,11 @@ }, "locked": { "host": "gitlab.haskell.org", - "lastModified": 1729816601, - "narHash": "sha256-/GrnRM/5f8DdC+8kuGLJijMBsUULZW5QJU3JcJFGJoc=", + "lastModified": 1736814899, + "narHash": "sha256-5ecjTrtyFRjFSSt39aUUfvBAiLmmR0f3IuhhMcI4kXE=", "owner": "ghc", "repo": "ghc-wasm-meta", - "rev": "64e6c8942a7805c0b3a2a7b74846ad962d142208", + "rev": "a9102d59d00bc87550dda902c8084a74f9742c00", "type": "gitlab" }, "original": { @@ -190,43 +190,6 @@ "type": "gitlab" } }, - "ghc910X": { - "flake": false, - "locked": { - "lastModified": 1714520650, - "narHash": "sha256-4uz6RA1hRr0RheGNDM49a/B3jszqNNU8iHIow4mSyso=", - "ref": "ghc-9.10", - "rev": "2c6375b9a804ac7fca1e82eb6fcfc8594c67c5f5", - "revCount": 62663, - "submodules": true, - "type": "git", - "url": "https://gitlab.haskell.org/ghc/ghc" - }, - "original": { - "ref": "ghc-9.10", - "submodules": true, - "type": "git", - "url": "https://gitlab.haskell.org/ghc/ghc" - } - }, - "ghc911": { - "flake": false, - "locked": { - "lastModified": 1714817013, - "narHash": "sha256-m2je4UvWfkgepMeUIiXHMwE6W+iVfUY38VDGkMzjCcc=", - "ref": "refs/heads/master", - "rev": "fc24c5cf6c62ca9e3c8d236656e139676df65034", - "revCount": 62816, - "submodules": true, - "type": "git", - "url": "https://gitlab.haskell.org/ghc/ghc" - }, - "original": { - "submodules": true, - "type": "git", - "url": "https://gitlab.haskell.org/ghc/ghc" - } - }, "gitignore": { "inputs": { "nixpkgs": [ @@ -251,11 +214,11 @@ "hackage": { "flake": false, "locked": { - "lastModified": 1718066301, - "narHash": "sha256-OzApzFFTxu5imt/viC7ZFD95dGtYgsLXOnLwP/527+Y=", + "lastModified": 1737073656, + "narHash": "sha256-sM5qDxlqe5XRHuHH0aOf6o3hfsLicYRg/zVUdTewJMA=", "owner": "input-output-hk", "repo": "hackage.nix", - "rev": "3d6f1394cc3b73717fa0409dfb1047904c4f90a7", + "rev": "7805f7fb86811110baf8ace616798e5a5342ee9d", "type": "github" }, "original": { @@ -273,8 +236,6 @@ "cardano-shell": "cardano-shell", "flake-compat": "flake-compat", "ghc-8.6.5-iohk": "ghc-8.6.5-iohk", - "ghc910X": "ghc910X", - "ghc911": "ghc911", "hackage": "hackage", "hls-1.10": "hls-1.10", "hls-2.0": "hls-2.0", @@ -285,6 +246,7 @@ "hls-2.6": "hls-2.6", "hls-2.7": "hls-2.7", "hls-2.8": "hls-2.8", + "hls-2.9": "hls-2.9", "hpc-coveralls": "hpc-coveralls", "hydra": "hydra", "iserv-proxy": "iserv-proxy", @@ -299,16 +261,17 @@ "nixpkgs-2211": "nixpkgs-2211", "nixpkgs-2305": "nixpkgs-2305", "nixpkgs-2311": "nixpkgs-2311", + "nixpkgs-2405": "nixpkgs-2405", "nixpkgs-unstable": "nixpkgs-unstable", "old-ghc-nix": "old-ghc-nix", "stackage": "stackage" }, "locked": { - "lastModified": 1718067014, - "narHash": "sha256-xxE0E8GEbSe2gerTE/USPTOQ5mLBV9OPVNxpGQq3LQI=", + "lastModified": 1737075090, + "narHash": "sha256-MCrMqEn4xKrKYRGTV42pX2xM8XB2Qd97CkJSxKp0Qgk=", "owner": "input-output-hk", "repo": "haskell.nix", - "rev": "d34c941a8f5fa631661fdf1f6606a6e65791b8a4", + "rev": "6db6357ad3692be6933067618d2b42e54d067d8a", "type": "github" }, "original": { @@ -470,6 +433,23 @@ "type": "github" } }, + "hls-2.9": { + "flake": false, + "locked": { + "lastModified": 1720003792, + "narHash": "sha256-qnDx8Pk0UxtoPr7BimEsAZh9g2WuTuMB/kGqnmdryKs=", + "owner": "haskell", + "repo": "haskell-language-server", + "rev": "0c1817cb2babef0765e4e72dd297c013e8e3d12b", + "type": "github" + }, + "original": { + "owner": "haskell", + "ref": "2.9.0.1", + "repo": "haskell-language-server", + "type": "github" + } + }, "hpc-coveralls": { "flake": false, "locked": { @@ -565,11 +545,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1729665710, - "narHash": "sha256-AlcmCXJZPIlO5dmFzV3V2XF6x/OpNWUV8Y/FMPGd8Z4=", + "lastModified": 1736701207, + "narHash": "sha256-jG/+MvjVY7SlTakzZ2fJ5dC3V1PrKKrUEOEE30jrOKA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2768c7d042a37de65bb1b5b3268fc987e534c49d", + "rev": "ed4a395ea001367c1f13d34b1e01aa10290f67d6", "type": "github" }, "original": { @@ -661,11 +641,11 @@ }, "nixpkgs-2305": { "locked": { - "lastModified": 1701362232, - "narHash": "sha256-GVdzxL0lhEadqs3hfRLuj+L1OJFGiL/L7gCcelgBlsw=", + "lastModified": 1705033721, + "narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d2332963662edffacfddfad59ff4f709dde80ffe", + "rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea", "type": "github" }, "original": { @@ -677,11 +657,11 @@ }, "nixpkgs-2311": { "locked": { - "lastModified": 1701386440, - "narHash": "sha256-xI0uQ9E7JbmEy/v8kR9ZQan6389rHug+zOtZeZFiDJk=", + "lastModified": 1719957072, + "narHash": "sha256-gvFhEf5nszouwLAkT9nWsDzocUTqLWHuL++dvNjMp9I=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "293822e55ec1872f715a66d0eda9e592dc14419f", + "rev": "7144d6241f02d171d25fba3edeaf15e0f2592105", "type": "github" }, "original": { @@ -691,51 +671,51 @@ "type": "github" } }, - "nixpkgs-regression": { + "nixpkgs-2405": { "locked": { - "lastModified": 1643052045, - "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "lastModified": 1729242558, + "narHash": "sha256-VgcLDu4igNT0eYua6OAl9pWCI0cYXhDbR+pWP44tte0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "rev": "4a3f2d3195b60d07530574988df92e049372c10e", "type": "github" }, "original": { "owner": "NixOS", + "ref": "nixpkgs-24.05-darwin", "repo": "nixpkgs", - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", "type": "github" } }, - "nixpkgs-stable": { + "nixpkgs-regression": { "locked": { - "lastModified": 1720386169, - "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=", + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "194846768975b7ad2c4988bdb82572c00222c0d7", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-24.05", "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", "type": "github" } }, "nixpkgs-unstable": { "locked": { - "lastModified": 1694822471, - "narHash": "sha256-6fSDCj++lZVMZlyqOe9SIOL8tYSBz1bI8acwovRwoX8=", + "lastModified": 1729980323, + "narHash": "sha256-eWPRZAlhf446bKSmzw6x7RWEE4IuZgAp8NW3eXZwRAY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "47585496bcb13fb72e4a90daeea2f434e2501998", + "rev": "86e78d3d2084ff87688da662cf78c2af085d8e73", "type": "github" }, "original": { "owner": "NixOS", + "ref": "nixpkgs-unstable", "repo": "nixpkgs", - "rev": "47585496bcb13fb72e4a90daeea2f434e2501998", "type": "github" } }, @@ -757,11 +737,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1719082008, - "narHash": "sha256-jHJSUH619zBQ6WdC21fFAlDxHErKVDJ5fpN0Hgx4sjs=", + "lastModified": 1730768919, + "narHash": "sha256-8AKquNnnSaJRXZxc5YmF/WfmxiHX6MMZZasRP6RRQkE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9693852a2070b398ee123a329e68f0dab5526681", + "rev": "a04d33c0c3f1a59a2c1cb0c6e34cd24500e5a1dc", "type": "github" }, "original": { @@ -792,15 +772,14 @@ "inputs": { "flake-compat": "flake-compat_2", "gitignore": "gitignore", - "nixpkgs": "nixpkgs_3", - "nixpkgs-stable": "nixpkgs-stable" + "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1720524665, - "narHash": "sha256-ni/87oHPZm6Gv0ECYxr1f6uxB0UKBWJ6HvS7lwLU6oY=", + "lastModified": 1735882644, + "narHash": "sha256-3FZAG+pGt3OElQjesCAWeMkQ7C/nB1oTHLRQ8ceP110=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "8d6a17d0cdf411c55f12602624df6368ad86fac1", + "rev": "a5a961387e75ae44cc20f0a57ae463da5e959656", "type": "github" }, "original": { @@ -818,7 +797,8 @@ "haskellNix", "nixpkgs-unstable" ], - "pre-commit-hooks": "pre-commit-hooks" + "pre-commit-hooks": "pre-commit-hooks", + "weeder": "weeder" } }, "stackage": { @@ -866,6 +846,22 @@ "repo": "default", "type": "github" } + }, + "weeder": { + "flake": false, + "locked": { + "lastModified": 1730808740, + "narHash": "sha256-62OfAhdHLve7seZgQ6NIoq7K57r2NqJESM3VTOZlY9Q=", + "owner": "ocharles", + "repo": "weeder", + "rev": "6c78e137033025c6b33e35fb8f9e681d55c43427", + "type": "github" + }, + "original": { + "owner": "ocharles", + "repo": "weeder", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 29bfaf64d..2eae47782 100644 --- a/flake.nix +++ b/flake.nix @@ -8,6 +8,7 @@ nixpkgs.follows = "haskellNix/nixpkgs-unstable"; flake-utils.url = "github:numtide/flake-utils"; pre-commit-hooks.url = "github:cachix/git-hooks.nix"; + weeder = { url = "github:ocharles/weeder"; flake = false; }; # for Ormolu Live ghc-wasm-meta.url = "gitlab:ghc/ghc-wasm-meta?host=gitlab.haskell.org"; @@ -23,7 +24,7 @@ inherit (pkgs) lib haskell-nix; inherit (haskell-nix) haskellLib; - ghcVersions = [ "ghc965" "ghc982" "ghc9101" ]; + ghcVersions = [ "ghc9101" "ghc984" "ghc9121" ]; defaultGHCVersion = builtins.head ghcVersions; perGHC = lib.genAttrs ghcVersions (ghcVersion: let @@ -41,10 +42,7 @@ hackageTests = import ./expected-failures { inherit pkgs ormolu; }; regionTests = import ./region-tests { inherit pkgs ormolu; }; fixityTests = import ./fixity-tests { inherit pkgs ormolu; }; - weeder = hsPkgs.tool "weeder" { - version = "2.6.0"; - modules = [{ reinstallableLibGhc = false; }]; - }; + weeder = hsPkgs.tool "weeder" { src = inputs.weeder; }; packages = lib.recurseIntoAttrs ({ inherit ormolu; ormoluTests = haskellLib.collectChecks' hsPkgs; @@ -143,7 +141,7 @@ tools = { cabal = "latest"; haskell-language-server = { - src = inputs.haskellNix.inputs."hls-2.8"; + src = inputs.haskellNix.inputs."hls-2.9"; configureArgs = "--disable-benchmarks --disable-tests"; }; }; diff --git a/ormolu-live/build.sh b/ormolu-live/build.sh index 90f3119ce..fd0cf74dc 100755 --- a/ormolu-live/build.sh +++ b/ormolu-live/build.sh @@ -28,19 +28,19 @@ else "$ORMOLU_WASM" -o "$WDIR/ormolu-init.wasm" ORMOLU_WASM_FINAL="$WDIR/ormolu-opt.wasm" wasm-opt "$WDIR/ormolu-init.wasm" -o "$ORMOLU_WASM_FINAL" -Oz - wasm-strip "$ORMOLU_WASM_FINAL" + wasm-tools strip "$ORMOLU_WASM_FINAL" -o "$ORMOLU_WASM_FINAL" fi rm -rf dist mkdir -p dist cp "$ORMOLU_WASM_FINAL" dist/ormolu-live.wasm -wasmedge --dir /:. "$(wasm32-wasi-cabal list-bin exe:pregen)" \ +wasmtime --dir .::/ "$(wasm32-wasi-cabal list-bin exe:pregen)" \ www/jsaddle.js dist/index.html esbuild_args=(--platform=browser --format=esm) [[ $dev_mode == false ]] && esbuild_args+=(--minify) -esbuild www/{index,worker}.js --outdir=dist --bundle "${esbuild_args[@]}" +esbuild www/{index,worker}.js --external:node:timers --outdir=dist --bundle "${esbuild_args[@]}" esbuild www/jsaddle.js --outdir=dist "${esbuild_args[@]}" cp node_modules/bulma/css/bulma.min.css dist/ diff --git a/ormolu-live/cabal.project b/ormolu-live/cabal.project index 65941c68f..29432a2f3 100644 --- a/ormolu-live/cabal.project +++ b/ormolu-live/cabal.project @@ -1,12 +1,8 @@ packages: . .. -index-state: 2024-10-26T13:20:54Z +index-state: 2025-01-16T23:07:37Z if arch(wasm32) - -- Older versions of time don't build on WASM. - constraints: time installed - allow-newer: time - package ghc-lib-parser -- The WASM backend does not support the threaded RTS. flags: -threaded-rts @@ -15,4 +11,4 @@ if arch(wasm32) source-repository-package type: git location: https://github.com/amesgen/splitmix - tag: 5f5b766d97dc735ac228215d240a3bb90bc2ff75 + tag: cea9e31bdd849eb0c17611bb99e33d590e126164 diff --git a/ormolu-live/default.nix b/ormolu-live/default.nix index cba18eb43..20784760a 100644 --- a/ormolu-live/default.nix +++ b/ormolu-live/default.nix @@ -2,6 +2,7 @@ let pkgs = inputs.ghc-wasm-meta.inputs.nixpkgs.legacyPackages.${system}; + inherit (pkgs) lib; in { shell = pkgs.mkShell { @@ -11,5 +12,13 @@ in pkgs.npm-check-updates pkgs.miniserve ]; + + # Otherwise there are `happy` errors in GHA CI. + shellHook = '' + export LANG="en_US.UTF-8" + '' + lib.optionalString + (pkgs.glibcLocales != null && pkgs.stdenv.hostPlatform.libc == "glibc") '' + export LOCALE_ARCHIVE="${pkgs.glibcLocales}/lib/locale/locale-archive" + ''; }; } diff --git a/ormolu.cabal b/ormolu.cabal index 3c0831479..989a83638 100644 --- a/ormolu.cabal +++ b/ormolu.cabal @@ -5,9 +5,9 @@ license: BSD-3-Clause license-file: LICENSE.md maintainer: Mark Karpov tested-with: - ghc ==9.6.5 - ghc ==9.8.2 + ghc ==9.8.4 ghc ==9.10.1 + ghc ==9.12.1 homepage: https://github.com/tweag/ormolu bug-reports: https://github.com/tweag/ormolu/issues @@ -70,6 +70,7 @@ library Ormolu.Printer.Meat.Declaration.Rule Ormolu.Printer.Meat.Declaration.Signature Ormolu.Printer.Meat.Declaration.Splice + Ormolu.Printer.Meat.Declaration.StringLiteral Ormolu.Printer.Meat.Declaration.Type Ormolu.Printer.Meat.Declaration.TypeFamily Ormolu.Printer.Meat.Declaration.Value @@ -94,7 +95,7 @@ library other-modules: GHC.DynFlags default-language: GHC2021 build-depends: - Cabal-syntax >=3.12 && <3.13, + Cabal-syntax >=3.14 && <3.15, Diff >=0.4 && <2, MemoTrie >=0.6 && <0.7, ansi-terminal >=0.10 && <1.2, @@ -107,7 +108,7 @@ library directory ^>=1.3, file-embed >=0.0.15 && <0.1, filepath >=1.2 && <1.6, - ghc-lib-parser >=9.10 && <9.11, + ghc-lib-parser >=9.12 && <9.13, megaparsec >=9, mtl >=2 && <3, syb >=0.7 && <0.8, @@ -132,12 +133,12 @@ executable ormolu autogen-modules: Paths_ormolu default-language: GHC2021 build-depends: - Cabal-syntax >=3.12 && <3.13, + Cabal-syntax >=3.14 && <3.15, base >=4.12 && <5, containers >=0.5 && <0.8, directory ^>=1.3, filepath >=1.2 && <1.6, - ghc-lib-parser >=9.10 && <9.11, + ghc-lib-parser >=9.12 && <9.13, optparse-applicative >=0.14 && <0.19, ormolu, text >=2.1 && <3, @@ -183,14 +184,14 @@ test-suite tests default-language: GHC2021 build-depends: - Cabal-syntax >=3.12 && <3.13, + Cabal-syntax >=3.14 && <3.15, QuickCheck >=2.14, base >=4.14 && <5, choice >=0.2.4.1 && <0.3, containers >=0.5 && <0.8, directory ^>=1.3, filepath >=1.2 && <1.6, - ghc-lib-parser >=9.10 && <9.11, + ghc-lib-parser >=9.12 && <9.13, hspec >=2 && <3, hspec-megaparsec >=2.2, megaparsec >=9, diff --git a/src/Ormolu/Diff/ParseResult.hs b/src/Ormolu/Diff/ParseResult.hs index c852b807e..ee00623e6 100644 --- a/src/Ormolu/Diff/ParseResult.hs +++ b/src/Ormolu/Diff/ParseResult.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeepSubsumption #-} +{-# LANGUAGE GADTs #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-} @@ -14,12 +15,14 @@ import Data.ByteString (ByteString) import Data.Foldable import Data.Function import Data.Generics +import GHC.Data.FastString (FastString) import GHC.Hs import GHC.Types.SourceText import GHC.Types.SrcLoc import Ormolu.Parser.CommentStream import Ormolu.Parser.Result import Ormolu.Utils +import Type.Reflection qualified as TR -- | Result of comparing two 'ParseResult's. data ParseResultDiff @@ -61,16 +64,8 @@ diffCommentStream (CommentStream cs) (CommentStream cs') where commentLines = concatMap (toList . unComment . unLoc) --- | Compare two modules for equality disregarding the following aspects: --- --- * 'SrcSpan's --- * ordering of import lists --- * style (ASCII vs Unicode) of arrows, colons --- * LayoutInfo (brace style) in extension fields --- * Empty contexts in type classes --- * Parens around derived type classes --- * 'TokenLocation' (in 'LHsToken'/'LHsUniToken') --- * 'EpaLocation' +-- | Compare two modules for equality disregarding certain semantically +-- irrelevant features like exact print annotations. diffHsModule :: HsModule GhcPs -> HsModule GhcPs -> ParseResultDiff diffHsModule = genericQuery where @@ -83,35 +78,60 @@ diffHsModule = genericQuery if x' == (y' :: ByteString) then Same else Different [] + | Just rep <- isEpTokenish x, + Just rep' <- isEpTokenish y = + -- Only check whether the Ep(Uni)Tokens are of the same type; don't + -- look at the actual payload (e.g. the location). + if rep == rep' then Same else Different [] | typeOf x == typeOf y, toConstr x == toConstr y = mconcat $ gzipWithQ ( genericQuery + -- EPA-related `extQ` considerEqual @SrcSpan `ext1Q` epAnnEq `extQ` considerEqual @SourceText - `extQ` hsDocStringEq - `extQ` importDeclQualifiedStyleEq - `extQ` classDeclCtxEq - `extQ` derivedTyClsParensEq `extQ` considerEqual @EpAnnComments -- ~ XCGRHSs GhcPs - `extQ` considerEqual @TokenLocation -- in LHs(Uni)Token `extQ` considerEqual @EpaLocation `extQ` considerEqual @EpLayout - `extQ` considerEqual @[AddEpAnn] `extQ` considerEqual @AnnSig `extQ` considerEqual @HsRuleAnn - `ext2Q` forLocated - -- unicode-related - `extQ` considerEqual @(EpUniToken "->" "→") - `extQ` considerEqual @(EpUniToken "::" "∷") `extQ` considerEqual @EpLinearArrow + `extQ` considerEqual @AnnSynDecl + -- FastString (for example for string literals) + `extQ` considerEqualVia' ((==) @FastString) + -- Haddock strings + `extQ` hsDocStringEq + -- Whether imports are pre- or post-qualified + `extQ` importDeclQualifiedStyleEq + -- Whether a class has an empty context + `extQ` classDeclCtxEq + -- Whether there are parens around a derived type class + `extQ` derivedTyClsParensEq + -- For better error messages + `ext2Q` forLocated ) x y | otherwise = Different [] + -- Return the 'TR.SomeTypeRep' of the type of the given value if it is an + -- 'EpToken', an 'EpUniToken', or a list of these. + isEpTokenish :: (Typeable a) => a -> Maybe TR.SomeTypeRep + isEpTokenish = fmap TR.SomeTypeRep . go . TR.typeOf + where + go :: TR.TypeRep a -> Maybe (TR.TypeRep a) + go rep = case rep of + TR.App t t' + | Just HRefl <- TR.eqTypeRep t (TR.typeRep @[]) -> + TR.App t <$> go t' + TR.App (TR.App t _) _ -> + rep <$ TR.eqTypeRep t (TR.typeRep @EpUniToken) + TR.App t _ -> + rep <$ TR.eqTypeRep t (TR.typeRep @EpToken) + _ -> Nothing + considerEqualVia :: forall a. (Typeable a) => diff --git a/src/Ormolu/Imports.hs b/src/Ormolu/Imports.hs index 71140eb06..ef1165368 100644 --- a/src/Ormolu/Imports.hs +++ b/src/Ormolu/Imports.hs @@ -17,6 +17,7 @@ import Data.Function (on) import Data.List (nubBy, sortBy, sortOn) import Data.Map.Strict (Map) import Data.Map.Strict qualified as M +import Data.Ord (comparing) import GHC.Data.FastString import GHC.Hs import GHC.Hs.ImpExp as GHC @@ -155,7 +156,7 @@ normalizeLies = sortOn (getIewn . unLoc) . M.elems . foldl' combine M.empty IEVar _ _ _ -> error "Ormolu.Imports broken presupposition" IEThingAbs x _ _ -> - IEThingWith x n wildcard g Nothing + IEThingWith (x, noAnn) n wildcard g Nothing IEThingAll x n' _ -> IEThingAll x n' Nothing IEThingWith x n' wildcard' g' _ -> @@ -207,15 +208,14 @@ compareLIewn = compareIewn `on` unLoc -- | Compare two @'IEWrapppedName' 'GhcPs'@ things. compareIewn :: IEWrappedName GhcPs -> IEWrappedName GhcPs -> Ordering -compareIewn (IEName _ x) (IEName _ y) = unLoc x `compareRdrName` unLoc y -compareIewn (IEName _ _) (IEPattern _ _) = LT -compareIewn (IEName _ _) (IEType _ _) = LT -compareIewn (IEPattern _ _) (IEName _ _) = GT -compareIewn (IEPattern _ x) (IEPattern _ y) = unLoc x `compareRdrName` unLoc y -compareIewn (IEPattern _ _) (IEType _ _) = LT -compareIewn (IEType _ _) (IEName _ _) = GT -compareIewn (IEType _ _) (IEPattern _ _) = GT -compareIewn (IEType _ x) (IEType _ y) = unLoc x `compareRdrName` unLoc y +compareIewn = (comparing fst <> (compareRdrName `on` unLoc . snd)) `on` classify + where + classify :: IEWrappedName GhcPs -> (Int, LocatedN RdrName) + classify = \case + IEName _ x -> (0, x) + IEDefault _ x -> (1, x) + IEPattern _ x -> (2, x) + IEType _ x -> (3, x) compareRdrName :: RdrName -> RdrName -> Ordering compareRdrName x y = diff --git a/src/Ormolu/Parser.hs b/src/Ormolu/Parser.hs index 10a43456f..87c7b2a29 100644 --- a/src/Ormolu/Parser.hs +++ b/src/Ormolu/Parser.hs @@ -175,7 +175,11 @@ parseModuleSnippet Config {..} modFixityMap dynFlags path rawInput = liftIO $ do normalizeModule :: HsModule GhcPs -> HsModule GhcPs normalizeModule hsmod = everywhere - (mkT dropBlankTypeHaddocks `extT` dropBlankDataDeclHaddocks `extT` patchContext) + ( mkT dropBlankTypeHaddocks + `extT` dropBlankDataDeclHaddocks + `extT` patchContext + `extT` patchExprContext + ) hsmod { hsmodImports = normalizeImports (hsmodImports hsmod), @@ -209,11 +213,18 @@ normalizeModule hsmod = | isBlankDocString s -> ConDeclH98 {con_doc = Nothing, ..} a -> a + -- For constraint contexts (both in types and in expressions), normalize + -- parenthesis as decided in https://github.com/tweag/ormolu/issues/264. patchContext :: LHsContext GhcPs -> LHsContext GhcPs patchContext = fmap $ \case [x@(L _ (HsParTy _ _))] -> [x] [x@(L lx _)] -> [L lx (HsParTy noAnn x)] xs -> xs + patchExprContext :: LHsExpr GhcPs -> LHsExpr GhcPs + patchExprContext = fmap $ \case + x@(HsQual _ (L _ [L _ HsPar {}]) _) -> x + HsQual l0 (L l1 [x@(L lx _)]) e -> HsQual l0 (L l1 [L lx (HsPar noAnn x)]) e + x -> x -- | Enable all language extensions that we think should be enabled by -- default for ease of use. @@ -253,7 +264,8 @@ manualExts = OverloadedRecordDot, -- f.g parses differently OverloadedRecordUpdate, -- qualified fields are not supported OverloadedLabels, -- a#b is parsed differently - ExtendedLiterals -- 1#Word32 is parsed differently + ExtendedLiterals, -- 1#Word32 is parsed differently + MultilineStrings -- """""" is parsed differently ] -- | Run a 'GHC.P' computation. diff --git a/src/Ormolu/Printer/Meat/Common.hs b/src/Ormolu/Printer/Meat/Common.hs index 6b8d00c89..73664570a 100644 --- a/src/Ormolu/Printer/Meat/Common.hs +++ b/src/Ormolu/Printer/Meat/Common.hs @@ -15,6 +15,7 @@ module Ormolu.Printer.Meat.Common p_hsDocName, p_sourceText, p_namespaceSpec, + p_arrow, ) where @@ -33,6 +34,7 @@ import GHC.Types.Name.Occurrence (OccName (..), occNameString) import GHC.Types.Name.Reader import GHC.Types.SourceText import GHC.Types.SrcLoc +import Language.Haskell.Syntax (HsArrowOf (..)) import Language.Haskell.Syntax.Module.Name import Ormolu.Config (SourceType (..)) import Ormolu.Printer.Combinators @@ -58,6 +60,10 @@ p_hsmodName mname = do p_ieWrappedName :: IEWrappedName GhcPs -> R () p_ieWrappedName = \case IEName _ x -> p_rdrName x + IEDefault _ x -> do + txt "default" + space + p_rdrName x IEPattern _ x -> do txt "pattern" space @@ -73,13 +79,13 @@ p_rdrName l = located l $ \x -> do unboxedSums <- isExtensionEnabled UnboxedSums let wrapper EpAnn {anns} = case anns of NameAnnQuote {nann_quoted} -> tickPrefix . wrapper nann_quoted - NameAnn {nann_adornment = NameParens} -> + NameAnn {nann_adornment = NameParens {}} -> parens N . handleUnboxedSumsAndHashInteraction - NameAnn {nann_adornment = NameBackquotes} -> backticks + NameAnn {nann_adornment = NameBackquotes {}} -> backticks -- whether the `->` identifier is parenthesized NameAnnRArrow {nann_mopen = Just _} -> parens N -- special case for unboxed unit tuples - NameAnnOnly {nann_adornment = NameParensHash} -> const $ txt "(# #)" + NameAnnOnly {nann_adornment = NameParensHash {}} -> const $ txt "(# #)" _ -> id -- When UnboxedSums is enabled, `(#` is a single lexeme, so we have to @@ -201,3 +207,13 @@ p_namespaceSpec = \case NoNamespaceSpecifier -> pure () TypeNamespaceSpecifier _ -> txt "type" *> space DataNamespaceSpecifier _ -> txt "data" *> space + +p_arrow :: (mult -> R ()) -> HsArrowOf mult GhcPs -> R () +p_arrow p_mult = \case + HsUnrestrictedArrow _ -> txt "->" + HsLinearArrow _ -> txt "%1 ->" + HsExplicitMult _ mult -> do + txt "%" + p_mult mult + space + txt "->" diff --git a/src/Ormolu/Printer/Meat/Declaration.hs b/src/Ormolu/Printer/Meat/Declaration.hs index d3d17ae56..28fbf2424 100644 --- a/src/Ormolu/Printer/Meat/Declaration.hs +++ b/src/Ormolu/Printer/Meat/Declaration.hs @@ -313,6 +313,7 @@ warnSigRdrNames _ = Nothing patBindNames :: Pat GhcPs -> [RdrName] patBindNames (TuplePat _ ps _) = concatMap (patBindNames . unLoc) ps +patBindNames (OrPat _ ps) = foldMap (patBindNames . unLoc) ps patBindNames (VarPat _ (L _ n)) = [n] patBindNames (WildPat _) = [] patBindNames (LazyPat _ (L _ p)) = patBindNames p diff --git a/src/Ormolu/Printer/Meat/Declaration/Data.hs b/src/Ormolu/Printer/Meat/Declaration/Data.hs index 31e56b8a2..916ee2190 100644 --- a/src/Ormolu/Printer/Meat/Declaration/Data.hs +++ b/src/Ormolu/Printer/Meat/Declaration/Data.hs @@ -18,9 +18,8 @@ import Data.Choice (Choice, pattern Is, pattern Isn't, pattern With) import Data.Choice qualified as Choice import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty qualified as NE -import Data.Maybe (isJust, isNothing, mapMaybe, maybeToList) +import Data.Maybe (isJust, isNothing, maybeToList) import Data.Void -import GHC.Data.Strict qualified as Strict import GHC.Hs import GHC.Types.Fixity import GHC.Types.ForeignCall @@ -238,10 +237,7 @@ p_conDecl singleRecCon ConDeclH98 {..} = forM_ con_mb_cxt p_lhsContext conNameWithContextSpn = - [ RealSrcSpan real Strict.Nothing - | EpaSpan (RealSrcSpan real _) <- - mapMaybe (matchAddEpAnn AnnForall) con_ext - ] + [getHasLoc $ acdh_forall con_ext] <> fmap getLocA con_ex_tvs <> maybeToList (fmap getLocA con_mb_cxt) <> [conNameSpn] diff --git a/src/Ormolu/Printer/Meat/Declaration/Default.hs b/src/Ormolu/Printer/Meat/Declaration/Default.hs index d4c3a4ef3..b52ddca01 100644 --- a/src/Ormolu/Printer/Meat/Declaration/Default.hs +++ b/src/Ormolu/Printer/Meat/Declaration/Default.hs @@ -5,13 +5,18 @@ module Ormolu.Printer.Meat.Declaration.Default ) where +import GHC.Data.Maybe (whenIsJust) import GHC.Hs import Ormolu.Printer.Combinators +import Ormolu.Printer.Meat.Common import Ormolu.Printer.Meat.Type p_defaultDecl :: DefaultDecl GhcPs -> R () -p_defaultDecl (DefaultDecl _ ts) = do +p_defaultDecl (DefaultDecl _ mclass ts) = do txt "default" + whenIsJust mclass $ \c -> do + breakpoint + p_rdrName c breakpoint inci . parens N $ sep commaDel (sitcc . located' p_hsType) ts diff --git a/src/Ormolu/Printer/Meat/Declaration/OpTree.hs b/src/Ormolu/Printer/Meat/Declaration/OpTree.hs index e953fcc8e..04a9e15f5 100644 --- a/src/Ormolu/Printer/Meat/Declaration/OpTree.hs +++ b/src/Ormolu/Printer/Meat/Declaration/OpTree.hs @@ -169,7 +169,7 @@ p_exprOpTree s t@(OpBranches exprs@(firstExpr :| otherExprs) ops) = do -- intermediate representation. cmdOpTree :: LHsCmdTop GhcPs -> OpTree (LHsCmdTop GhcPs) (LHsExpr GhcPs) cmdOpTree = \case - (L _ (HsCmdTop _ (L _ (HsCmdArrForm _ op Infix _ [x, y])))) -> + (L _ (HsCmdTop _ (L _ (HsCmdArrForm _ op Infix [x, y])))) -> BinaryOpBranches (cmdOpTree x) op (cmdOpTree y) n -> OpNode n diff --git a/src/Ormolu/Printer/Meat/Declaration/Signature.hs b/src/Ormolu/Printer/Meat/Declaration/Signature.hs index 6f876e29d..bdf95716d 100644 --- a/src/Ormolu/Printer/Meat/Declaration/Signature.hs +++ b/src/Ormolu/Printer/Meat/Declaration/Signature.hs @@ -93,7 +93,7 @@ p_fixSig :: FixitySig GhcPs -> R () p_fixSig = \case - FixitySig namespace names (Fixity _ n dir) -> do + FixitySig namespace names (Fixity n dir) -> do txt $ case dir of InfixL -> "infixl" InfixR -> "infixr" diff --git a/src/Ormolu/Printer/Meat/Declaration/StringLiteral.hs b/src/Ormolu/Printer/Meat/Declaration/StringLiteral.hs new file mode 100644 index 000000000..2c3474d0a --- /dev/null +++ b/src/Ormolu/Printer/Meat/Declaration/StringLiteral.hs @@ -0,0 +1,152 @@ +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ViewPatterns #-} + +module Ormolu.Printer.Meat.Declaration.StringLiteral (p_stringLit) where + +import Control.Applicative (Alternative (..)) +import Control.Category ((>>>)) +import Control.Monad ((>=>)) +import Data.Semigroup (Min (..)) +import Data.Text (Text) +import Data.Text qualified as T +import GHC.Data.FastString +import GHC.Parser.CharClass (is_space) +import Ormolu.Printer.Combinators +import Ormolu.Utils + +-- | Print the source text of a string literal while indenting gaps and newlines +-- correctly. +p_stringLit :: FastString -> R () +p_stringLit src = case parseStringLiteral $ T.pack $ unpackFS src of + Nothing -> error $ "Internal Ormolu error: couldn't parse string literal: " <> show src + Just ParsedStringLiteral {..} -> sitcc do + txt startMarker + case stringLiteralKind of + RegularStringLiteral -> do + let singleLine = + txt $ T.concat segments + multiLine = + sep breakpoint f (attachRelativePos segments) + where + f :: (RelativePos, Text) -> R () + f (pos, s) = case pos of + SinglePos -> txt s + FirstPos -> txt s *> txt "\\" + MiddlePos -> txt "\\" *> txt s *> txt "\\" + LastPos -> txt "\\" *> txt s + vlayout singleLine multiLine + MultilineStringLiteral -> + sep breakpoint' txt segments + txt endMarker + +-- | The start/end marker of the literal, whether it is a regular or a multiline +-- literal, and the segments of the literals (separated by gaps for a regular +-- literal, and separated by newlines for a multiline literal). +data ParsedStringLiteral = ParsedStringLiteral + { startMarker, endMarker :: Text, + stringLiteralKind :: StringLiteralKind, + segments :: [Text] + } + deriving stock (Show, Eq) + +-- | A regular or a multiline string literal. +data StringLiteralKind = RegularStringLiteral | MultilineStringLiteral + deriving stock (Show, Eq) + +-- | Turn a string literal (as it exists in the source) into a more structured +-- form for printing. This should never return 'Nothing' for literals that the +-- GHC parser accepted. +parseStringLiteral :: Text -> Maybe ParsedStringLiteral +parseStringLiteral = \s -> do + psl <- + (stripStartEndMarker MultilineStringLiteral "\"\"\"" s) + <|> (stripStartEndMarker RegularStringLiteral "\"" s) + let splitSegments = case stringLiteralKind psl of + RegularStringLiteral -> splitGaps + MultilineStringLiteral -> splitMultilineString + pure psl {segments = concatMap splitSegments $ segments psl} + where + -- Remove the given marker from the start and the end (at the end, + -- optionally also remove a #). + stripStartEndMarker :: + StringLiteralKind -> Text -> Text -> Maybe ParsedStringLiteral + stripStartEndMarker stringLiteralKind marker s = do + let startMarker = marker + suffix <- T.stripPrefix startMarker s + let markerWithHash = marker <> "#" + (endMarker, infix_) <- + ((markerWithHash,) <$> T.stripSuffix markerWithHash suffix) + <|> ((marker,) <$> T.stripSuffix marker suffix) + pure ParsedStringLiteral {segments = [infix_], ..} + + -- Split a string on gaps (backslash delimited whitespaces). + -- + -- > splitGaps "bar\\ \\fo\\&o" == ["bar", "fo\\&o"] + splitGaps :: Text -> [Text] + splitGaps s = go $ T.breakOnAll "\\" s + where + go [] = [s] + go ((pre, suf) : bs) = case T.uncons suf of + Just ('\\', T.uncons -> Just (c, s')) + | is_space c, + let rest = T.drop 1 $ T.dropWhile (/= '\\') s' -> + pre : splitGaps rest + | otherwise -> go $ (if c == '\\' then drop 1 else id) bs + _ -> go bs + + -- See the the MultilineStrings GHC proposal and 'lexMultilineString' from + -- "GHC.Parser.String" for reference. + -- + -- https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0569-multiline-strings.rst#proposed-change-specification + splitMultilineString :: Text -> [Text] + splitMultilineString = + splitGaps + -- There is no reason to use gaps with multiline string literals, so + -- we collapse them. + >>> T.concat + >>> splitNewlines + >>> fmap expandLeadingTabs + >>> rmCommonWhitespacePrefixAndBlank + + -- See the definition of newlines on + -- . + splitNewlines :: Text -> [Text] + splitNewlines = T.splitOn "\r\n" >=> T.split isNewlineish + where + isNewlineish c = c == '\n' || c == '\r' || c == '\f' + + -- See GHC's 'lexMultilineString'. + expandLeadingTabs :: Text -> Text + expandLeadingTabs = T.concat . go 0 + where + go :: Int -> Text -> [Text] + go col s = case T.breakOn "\t" s of + (pre, T.uncons -> Just (_, suf)) -> + let col' = col + T.length pre + fill = 8 - (col' `mod` 8) + in pre : T.replicate fill " " : go (col' + fill) suf + _ -> [s] + + -- Don't touch the first line, and remove common whitespace from all + -- remaining lines as well as convert those consisting only of whitespace to + -- empty lines. + rmCommonWhitespacePrefixAndBlank :: [Text] -> [Text] + rmCommonWhitespacePrefixAndBlank = \case + [] -> [] + hd : tl -> hd : tl' + where + (leadingSpaces, tl') = unzip $ countLeadingAndBlank <$> tl + + commonWs :: Int + commonWs = maybe 0 getMin $ mconcat leadingSpaces + + countLeadingAndBlank :: Text -> (Maybe (Min Int), Text) + countLeadingAndBlank l + | T.all is_space l = (Nothing, "") + | otherwise = (Just $ Min leadingSpace, T.drop commonWs l) + where + leadingSpace = T.length $ T.takeWhile is_space l diff --git a/src/Ormolu/Printer/Meat/Declaration/Value.hs b/src/Ormolu/Printer/Meat/Declaration/Value.hs index cba7b70df..44f80307f 100644 --- a/src/Ormolu/Printer/Meat/Declaration/Value.hs +++ b/src/Ormolu/Printer/Meat/Declaration/Value.hs @@ -9,7 +9,6 @@ module Ormolu.Printer.Meat.Declaration.Value p_pat, p_hsExpr, p_hsUntypedSplice, - p_stringLit, IsApplicand (..), p_hsExpr', p_hsCmdTop, @@ -20,24 +19,19 @@ where import Control.Monad import Data.Bool (bool) -import Data.Coerce (coerce) import Data.Data hiding (Infix, Prefix) import Data.Function (on) import Data.Functor ((<&>)) import Data.Generics.Schemes (everything) -import Data.List (intersperse, sortBy) +import Data.List (intersperse, sortBy, unsnoc) import Data.List.NonEmpty (NonEmpty (..), (<|)) import Data.List.NonEmpty qualified as NE import Data.Maybe import Data.Text (Text) -import Data.Text qualified as Text import Data.Void -import GHC.Data.Bag (bagToList) -import GHC.Data.FastString import GHC.Data.Strict qualified as Strict import GHC.Hs import GHC.LanguageExtensions.Type (Extension (NegativeLiterals)) -import GHC.Parser.CharClass (is_space) import GHC.Types.Basic import GHC.Types.Fixity import GHC.Types.Name.Reader @@ -49,6 +43,7 @@ import Ormolu.Printer.Meat.Common import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.OpTree import Ormolu.Printer.Meat.Declaration.Signature +import Ormolu.Printer.Meat.Declaration.StringLiteral import Ormolu.Printer.Meat.Type import Ormolu.Printer.Operators import Ormolu.Utils @@ -119,7 +114,8 @@ p_matchGroup' placer render style mg@MG {..} = do (isInfixMatch m) (HsNoMultAnn NoExtField) (matchStrictness m) - m_pats + -- We use the spans of the individual patterns. + (unLoc m_pats) m_grhss -- | Function id obtained through pattern matching on 'FunBind' should not @@ -355,19 +351,19 @@ p_hsCmd' isApp s = \case (HsHigherOrderApp, False) -> txt ">>-" placeHanging (exprPlacement (unLoc input)) $ located r p_hsExpr - HsCmdArrForm _ form Prefix _ cmds -> banana s $ do + HsCmdArrForm _ form Prefix cmds -> banana s $ do located form p_hsExpr unless (null cmds) $ do breakpoint inci (sequence_ (intersperse breakpoint (located' (p_hsCmdTop N) <$> cmds))) - HsCmdArrForm _ form Infix _ [left, right] -> do + HsCmdArrForm _ form Infix [left, right] -> do modFixityMap <- askModuleFixityMap debug <- askDebug let opTree = BinaryOpBranches (cmdOpTree left) form (cmdOpTree right) p_cmdOpTree s (reassociateOpTree debug (getOpName . unLoc) modFixityMap opTree) - HsCmdArrForm _ _ Infix _ _ -> notImplemented "HsCmdArrForm" + HsCmdArrForm _ _ Infix _ -> notImplemented "HsCmdArrForm" HsCmdApp _ cmd expr -> do located cmd (p_hsCmd' Applicand s) breakpoint @@ -421,8 +417,9 @@ p_stmt :: Stmt GhcPs (LHsExpr GhcPs) -> R () p_stmt = p_stmt' N exprPlacement (p_hsExpr' NotApplicand) p_stmt' :: - ( Anno (Stmt GhcPs (LocatedA body)) ~ SrcSpanAnnA, - Anno [LocatedA (Stmt GhcPs (LocatedA body))] ~ SrcSpanAnnL + ( Anno [LStmt GhcPs (XRec GhcPs body)] ~ SrcSpanAnnLW, + Anno (Stmt GhcPs (XRec GhcPs body)) ~ SrcSpanAnnA, + Anno body ~ SrcSpanAnnA ) => BracketStyle -> -- | Placer @@ -430,7 +427,7 @@ p_stmt' :: -- | Render (BracketStyle -> body -> R ()) -> -- | Statement to render - Stmt GhcPs (LocatedA body) -> + Stmt GhcPs (XRec GhcPs body) -> R () p_stmt' s placer render = \case LastStmt _ body _ _ -> located body (render s) @@ -444,7 +441,6 @@ p_stmt' s placer render = \case | otherwise = Normal switchLayout [loc, l] $ placeHanging placement (located f (render N)) - ApplicativeStmt {} -> notImplemented "ApplicativeStmt" -- generated by renamer BodyStmt _ body _ _ -> located body (render s) LetStmt _ binds -> do txt "let" @@ -490,8 +486,9 @@ p_stmt' s placer render = \case sitcc . located recS_stmts $ sepSemi (withSpacing (p_stmt' s placer render)) p_stmts :: - ( Anno (Stmt GhcPs (LocatedA body)) ~ SrcSpanAnnA, - Anno [LocatedA (Stmt GhcPs (LocatedA body))] ~ SrcSpanAnnL + ( Anno [LStmt GhcPs (XRec GhcPs body)] ~ SrcSpanAnnLW, + Anno (Stmt GhcPs (XRec GhcPs body)) ~ SrcSpanAnnA, + Anno body ~ SrcSpanAnnA ) => BracketStyle -> IsApplicand -> @@ -500,7 +497,7 @@ p_stmts :: -- | Render (BracketStyle -> body -> R ()) -> -- | Statements to render - LocatedL [LocatedA (Stmt GhcPs (LocatedA body))] -> + XRec GhcPs [LStmt GhcPs (XRec GhcPs body)] -> R () p_stmts s isApp placer render es = do breakpoint @@ -520,7 +517,7 @@ p_stmts s isApp placer render es = do p_hsLocalBinds :: HsLocalBinds GhcPs -> R () p_hsLocalBinds = \case - HsValBinds epAnn (ValBinds _ bag lsigs) -> pseudoLocated epAnn $ do + HsValBinds epAnn (ValBinds _ binds lsigs) -> pseudoLocated epAnn $ do -- When in a single-line layout, there is a chance that the inner -- elements will also contain semicolons and they will confuse the -- parser. so we request braces around every element except the last. @@ -528,7 +525,7 @@ p_hsLocalBinds = \case let items = let injectLeft (L l x) = L l (Left x) injectRight (L l x) = L l (Right x) - in (injectLeft <$> bagToList bag) ++ (injectRight <$> lsigs) + in (injectLeft <$> binds) ++ (injectRight <$> lsigs) positionToBracing = \case SinglePos -> id FirstPos -> br @@ -537,8 +534,8 @@ p_hsLocalBinds = \case p_item' (p, item) = positionToBracing p $ withSpacing (either p_valDecl p_sigDecl) item - binds = sortBy (leftmost_smallest `on` getLocA) items - sitcc $ sepSemi p_item' (attachRelativePos binds) + items' = sortBy (leftmost_smallest `on` getLocA) items + sitcc $ sepSemi p_item' (attachRelativePos items') HsValBinds _ _ -> notImplemented "HsValBinds" HsIPBinds epAnn (IPBinds _ xs) -> pseudoLocated epAnn $ do let p_ipBind (IPBind _ (L _ name) expr) = do @@ -560,12 +557,12 @@ p_hsLocalBinds = \case located (L al_anchor ()) . const _ -> id -p_ldotFieldOcc :: XRec GhcPs (DotFieldOcc GhcPs) -> R () -p_ldotFieldOcc = - located' $ p_rdrName . fmap (mkVarUnqual . field_label) . dfoLabel +p_dotFieldOcc :: DotFieldOcc GhcPs -> R () +p_dotFieldOcc = + p_rdrName . fmap (mkVarUnqual . field_label) . dfoLabel -p_ldotFieldOccs :: [XRec GhcPs (DotFieldOcc GhcPs)] -> R () -p_ldotFieldOccs = sep (txt ".") p_ldotFieldOcc +p_dotFieldOccs :: [DotFieldOcc GhcPs] -> R () +p_dotFieldOccs = sep (txt ".") p_dotFieldOcc p_fieldOcc :: FieldOcc GhcPs -> R () p_fieldOcc FieldOcc {..} = p_rdrName foLabel @@ -607,8 +604,7 @@ p_hsExpr' :: IsApplicand -> BracketStyle -> HsExpr GhcPs -> R () p_hsExpr' isApp s = \case HsVar _ name -> p_rdrName name HsUnboundVar _ occ -> atom occ - HsRecSel _ fldOcc -> p_fieldOcc fldOcc - HsOverLabel _ sourceText _ -> do + HsOverLabel sourceText _ -> do txt "#" p_sourceText sourceText HsIPVar _ (HsIPName name) -> do @@ -619,6 +615,7 @@ p_hsExpr' isApp s = \case case lit of HsString (SourceText stxt) _ -> p_stringLit stxt HsStringPrim (SourceText stxt) _ -> p_stringLit stxt + HsMultilineString (SourceText stxt) _ -> p_stringLit stxt r -> atom r HsLam _ variant mgroup -> p_lam isApp variant exprPlacement p_hsExpr mgroup @@ -767,25 +764,22 @@ p_hsExpr' isApp s = \case RecordUpd {..} -> do located rupd_expr p_hsExpr breakpoint - let p_updLbl = - located' $ - p_rdrName . \case - (Unambiguous NoExtField n :: AmbiguousFieldOcc GhcPs) -> n - Ambiguous NoExtField n -> n - p_recFields p_lbl = + let p_recFields p_lbl = sep commaDel (sitcc . located' (p_hsFieldBind p_lbl)) + p_fieldLabelStrings (FieldLabelStrings flss) = + p_dotFieldOccs $ unLoc <$> flss inci . braces N $ case rupd_flds of RegularRecUpdFields {..} -> - p_recFields p_updLbl recUpdFields + p_recFields (located' p_fieldOcc) recUpdFields OverloadedRecUpdFields {..} -> - p_recFields (located' (coerce p_ldotFieldOccs)) olRecUpdFields + p_recFields (located' p_fieldLabelStrings) olRecUpdFields HsGetField {..} -> do located gf_expr p_hsExpr txt "." - p_ldotFieldOcc gf_field + located gf_field p_dotFieldOcc HsProjection {..} -> parens N $ do txt "." - p_ldotFieldOccs (NE.toList proj_flds) + p_dotFieldOccs (NE.toList proj_flds) ExprWithTySig _ x HsWC {hswc_body} -> sitcc $ do located x p_hsExpr space @@ -820,7 +814,7 @@ p_hsExpr' isApp s = \case located expr p_hsExpr breakpoint' txt "||]" - HsUntypedBracket anns x -> p_hsQuote anns x + HsUntypedBracket _ x -> p_hsQuote x HsTypedSplice _ expr -> p_hsSpliceTH True expr DollarSplice HsUntypedSplice _ untySplice -> p_hsUntypedSplice DollarSplice untySplice HsProc _ p e -> do @@ -848,11 +842,30 @@ p_hsExpr' isApp s = \case txt "type" space located hswc_body p_hsType + -- similar to HsForAllTy + HsForAll _ tele e -> do + p_hsForAllTelescope tele + breakpoint + located e p_hsExpr + -- similar to HsQualTy + HsQual _ qs e -> do + located qs $ p_hsContext' p_hsExpr + space + txt "=>" + breakpoint + located e p_hsExpr + -- similar to HsFunTy + HsFunArr _ arrow x y -> do + located x p_hsExpr + space + p_arrow (located' p_hsExpr) arrow + breakpoint + located y p_hsExpr -- | Print a list comprehension. -- -- BracketStyle should be N except in a do-block, which must be S or else it's a parse error. -p_listComp :: BracketStyle -> GenLocated SrcSpanAnnL [ExprLStmt GhcPs] -> R () +p_listComp :: BracketStyle -> XRec GhcPs [ExprLStmt GhcPs] -> R () p_listComp s es = sitcc (vlayout singleLine multiLine) where singleLine = do @@ -869,10 +882,9 @@ p_listComp s es = sitcc (vlayout singleLine multiLine) body = located es p_body p_body xs = do let (stmts, yield) = - -- TODO: use unsnoc when require GHC 9.8+ - case xs of - [] -> error $ "list comprehension unexpectedly had no expressions" - _ -> (init xs, last xs) + case unsnoc xs of + Nothing -> error $ "list comprehension unexpectedly had no expressions" + Just (ys, y) -> (ys, y) sitcc $ located yield p_stmt breakpoint txt "|" @@ -1157,6 +1169,8 @@ p_pat = \case Boxed -> parens S Unboxed -> parensHash S parens' $ sep commaDel (sitcc . located' p_pat) pats + OrPat _ pats -> + sepSemi (located' p_pat) (NE.toList pats) SumPat _ pat tag arity -> p_unboxedSum S tag arity (located pat p_pat) ConPat _ pat details -> @@ -1167,7 +1181,7 @@ p_pat = \case inci . sitcc $ sep breakpoint (sitcc . either p_hsConPatTyArg (located' p_pat)) $ (Left <$> tys) <> (Right <$> xs) - RecCon (HsRecFields fields dotdot) -> do + RecCon (HsRecFields _ fields dotdot) -> do p_rdrName pat breakpoint let f = \case @@ -1274,12 +1288,12 @@ p_hsSpliceTH isTyped expr = \case where decoSymbol = if isTyped then "$$" else "$" -p_hsQuote :: [AddEpAnn] -> HsQuote GhcPs -> R () -p_hsQuote anns = \case - ExpBr _ expr -> do - let name - | any (isJust . matchAddEpAnn AnnOpenEQ) anns = "" - | otherwise = "e" +p_hsQuote :: HsQuote GhcPs -> R () +p_hsQuote = \case + ExpBr (bracketAnn, _) expr -> do + let name = case bracketAnn of + BracketNoE {} -> "" + BracketHasE {} -> "e" quote name (located expr p_hsExpr) PatBr _ pat -> located pat (quote "p" . p_pat) DecBrL _ decls -> quote "d" (handleStarIsType decls (p_hsDecls Free decls)) @@ -1313,47 +1327,6 @@ p_hsQuote anns = \case Just HsStarTy {} -> True _ -> False --- | Print the source text of a string literal while indenting gaps correctly. -p_stringLit :: FastString -> R () -p_stringLit src = - let s = splitGaps (unpackFS src) - singleLine = - txt $ Text.pack (mconcat s) - multiLine = - sitcc $ sep breakpoint (txt . Text.pack) (backslashes s) - in vlayout singleLine multiLine - where - -- Split a string on gaps (backslash delimited whitespaces) - -- - -- > splitGaps "bar\\ \\fo\\&o" == ["bar", "fo\\&o"] - splitGaps :: String -> [String] - splitGaps "" = [] - splitGaps s = - let -- A backslash and a whitespace starts a "gap" - p (Just '\\', _, _) = True - p (_, '\\', Just c) | ghcSpace c = False - p _ = True - in case span p (zipPrevNext s) of - (l, r) -> - let -- drop the initial '\', any amount of 'ghcSpace', and another '\' - r' = drop 1 . dropWhile ghcSpace . drop 1 $ map orig r - in map orig l : splitGaps r' - -- GHC's definition of whitespaces in strings - -- See: https://gitlab.haskell.org/ghc/ghc/blob/86753475/compiler/parser/Lexer.x#L1653 - ghcSpace :: Char -> Bool - ghcSpace c = c <= '\x7f' && is_space c - -- Add backslashes to the inner side of the strings - -- - -- > backslashes ["a", "b", "c"] == ["a\\", "\\b\\", "\\c"] - backslashes :: [String] -> [String] - backslashes (x : y : xs) = (x ++ "\\") : backslashes (('\\' : y) : xs) - backslashes xs = xs - -- Attaches previous and next items to each list element - zipPrevNext :: [a] -> [(Maybe a, a, Maybe a)] - zipPrevNext xs = - zip3 (Nothing : map Just xs) xs (map Just (drop 1 xs) ++ [Nothing]) - orig (_, x, _) = x - ---------------------------------------------------------------------------- -- Helpers @@ -1394,7 +1367,7 @@ exprPlacement = \case -- Only hang lambdas with single line parameter lists HsLam _ variant mg -> case variant of LamSingle -> case mg of - MG _ (L _ [L _ (Match _ _ (x : xs) _)]) + MG _ (L _ [L _ (Match _ _ (L _ (x : xs)) _)]) | isOneLineSpan (combineSrcSpans' $ fmap getLocA (x :| xs)) -> Hanging _ -> Normal diff --git a/src/Ormolu/Printer/Meat/Declaration/Value.hs-boot b/src/Ormolu/Printer/Meat/Declaration/Value.hs-boot index 857683601..0a689d6c2 100644 --- a/src/Ormolu/Printer/Meat/Declaration/Value.hs-boot +++ b/src/Ormolu/Printer/Meat/Declaration/Value.hs-boot @@ -3,7 +3,6 @@ module Ormolu.Printer.Meat.Declaration.Value p_pat, p_hsExpr, p_hsUntypedSplice, - p_stringLit, p_hsExpr', p_hsCmdTop, exprPlacement, @@ -11,7 +10,6 @@ module Ormolu.Printer.Meat.Declaration.Value ) where -import GHC.Data.FastString import GHC.Hs import Ormolu.Printer.Combinators @@ -19,7 +17,6 @@ p_valDecl :: HsBindLR GhcPs GhcPs -> R () p_pat :: Pat GhcPs -> R () p_hsExpr :: HsExpr GhcPs -> R () p_hsUntypedSplice :: SpliceDecoration -> HsUntypedSplice GhcPs -> R () -p_stringLit :: FastString -> R () data IsApplicand diff --git a/src/Ormolu/Printer/Meat/Type.hs b/src/Ormolu/Printer/Meat/Type.hs index cc61be66a..e23ca4b0c 100644 --- a/src/Ormolu/Printer/Meat/Type.hs +++ b/src/Ormolu/Printer/Meat/Type.hs @@ -10,12 +10,14 @@ module Ormolu.Printer.Meat.Type ( p_hsType, hasDocStrings, p_hsContext, + p_hsContext', p_hsTyVarBndr, ForAllVisibility (..), p_forallBndrs, p_conDeclFields, p_lhsTypeArg, p_hsSigType, + p_hsForAllTelescope, hsOuterTyVarBndrsToHsType, lhsTypeToSigType, ) @@ -30,7 +32,8 @@ import GHC.Types.Var import Ormolu.Printer.Combinators import Ormolu.Printer.Meat.Common import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.OpTree (p_tyOpTree, tyOpTree) -import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.Value (p_hsUntypedSplice, p_stringLit) +import Ormolu.Printer.Meat.Declaration.StringLiteral +import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.Value (p_hsUntypedSplice) import Ormolu.Printer.Operators import Ormolu.Utils @@ -40,9 +43,7 @@ p_hsType t = p_hsType' (hasDocStrings t) t p_hsType' :: Bool -> HsType GhcPs -> R () p_hsType' multilineArgs = \case HsForAllTy _ tele t -> do - case tele of - HsForAllInvis _ bndrs -> p_forallBndrs ForAllInvis p_hsTyVarBndr bndrs - HsForAllVis _ bndrs -> p_forallBndrs ForAllVis p_hsTyVarBndr bndrs + p_hsForAllTelescope tele interArgBreak located t p_hsType HsQualTy _ qs t -> do @@ -89,14 +90,7 @@ p_hsType' multilineArgs = \case HsFunTy _ arrow x y@(L _ y') -> do located x p_hsType space - case arrow of - HsUnrestrictedArrow _ -> txt "->" - HsLinearArrow _ -> txt "%1 ->" - HsExplicitMult _ mult -> do - txt "%" - p_hsTypeR (unLoc mult) - space - txt "->" + p_arrow (located' p_hsTypeR) arrow interArgBreak case y' of HsFunTy {} -> p_hsTypeR y' @@ -140,7 +134,7 @@ p_hsType' multilineArgs = \case HsDocTy _ t str -> do p_hsDoc Pipe (With #endNewline) str located t p_hsType - HsBangTy _ (HsSrcBang _ u s) t -> do + HsBangTy _ (HsBang u s) t -> do case u of SrcUnpack -> txt "{-# UNPACK #-}" >> space SrcNoUnpack -> txt "{-# NOUNPACK #-}" >> space @@ -163,11 +157,15 @@ p_hsType' multilineArgs = \case (IsPromoted, L _ t : _) | startsWithSingleQuote t -> space _ -> return () sep commaDel (sitcc . located' p_hsType) xs - HsExplicitTupleTy _ xs -> do - txt "'" + HsExplicitTupleTy _ p xs -> do + case p of + IsPromoted -> txt "'" + NotPromoted -> return () parens N $ do - case xs of - L _ t : _ | startsWithSingleQuote t -> space + -- If this tuple is promoted and the first element starts with a single + -- quote, we need to put a space in between or it fails to parse. + case (p, xs) of + (IsPromoted, L _ t : _) | startsWithSingleQuote t -> space _ -> return () sep commaDel (located' p_hsType) xs HsTyLit _ t -> @@ -201,10 +199,13 @@ hasDocStrings = \case _ -> False p_hsContext :: HsContext GhcPs -> R () -p_hsContext = \case +p_hsContext = p_hsContext' p_hsType + +p_hsContext' :: (HasLoc (Anno a)) => (a -> R ()) -> [XRec GhcPs a] -> R () +p_hsContext' f = \case [] -> txt "()" - [x] -> located x p_hsType - xs -> parens N $ sep commaDel (sitcc . located' p_hsType) xs + [x] -> located x f + xs -> parens N $ sep commaDel (sitcc . located' f) xs class IsTyVarBndrFlag flag where isInferred :: flag -> Bool @@ -226,18 +227,24 @@ instance IsTyVarBndrFlag (HsBndrVis GhcPs) where HsBndrInvisible _ -> txt "@" p_hsTyVarBndr :: (IsTyVarBndrFlag flag) => HsTyVarBndr flag GhcPs -> R () -p_hsTyVarBndr = \case - UserTyVar _ flag x -> do - p_tyVarBndrFlag flag - (if isInferred flag then braces N else id) $ p_rdrName x - KindedTyVar _ flag l k -> do - p_tyVarBndrFlag flag - (if isInferred flag then braces else parens) N $ do - located l atom - space - txt "::" - breakpoint - inci (located k p_hsType) +p_hsTyVarBndr HsTvb {..} = do + p_tyVarBndrFlag tvb_flag + let wrap + | isInferred tvb_flag = braces N + | otherwise = case tvb_kind of + HsBndrKind {} -> parens N + HsBndrNoKind {} -> id + wrap $ do + case tvb_var of + HsBndrVar _ x -> p_rdrName x + HsBndrWildCard _ -> txt "_" + case tvb_kind of + HsBndrKind _ k -> do + space + txt "::" + breakpoint + inci (located k p_hsType) + HsBndrNoKind _ -> pure () data ForAllVisibility = ForAllInvis | ForAllVis @@ -290,6 +297,11 @@ p_hsSigType :: HsSigType GhcPs -> R () p_hsSigType HsSig {..} = p_hsType $ hsOuterTyVarBndrsToHsType sig_bndrs sig_body +p_hsForAllTelescope :: HsForAllTelescope GhcPs -> R () +p_hsForAllTelescope = \case + HsForAllInvis _ bndrs -> p_forallBndrs ForAllInvis p_hsTyVarBndr bndrs + HsForAllVis _ bndrs -> p_forallBndrs ForAllVis p_hsTyVarBndr bndrs + ---------------------------------------------------------------------------- -- Conversion functions diff --git a/src/Ormolu/Utils.hs b/src/Ormolu/Utils.hs index b0a5c0119..2b9837dd3 100644 --- a/src/Ormolu/Utils.hs +++ b/src/Ormolu/Utils.hs @@ -13,7 +13,6 @@ module Ormolu.Utils separatedByBlank, separatedByBlankNE, onTheSameLine, - matchAddEpAnn, textToStringBuffer, ghcModuleNameToCabal, ) @@ -139,13 +138,6 @@ onTheSameLine :: SrcSpan -> SrcSpan -> Bool onTheSameLine a b = isOneLineSpan (mkSrcSpan (srcSpanEnd a) (srcSpanStart b)) --- | Check whether the given 'AnnKeywordId' or its Unicode variant is in an --- 'AddEpAnn', and return the 'EpaLocation' if so. -matchAddEpAnn :: AnnKeywordId -> AddEpAnn -> Maybe EpaLocation -matchAddEpAnn annId (AddEpAnn annId' loc) - | annId == annId' || unicodeAnn annId == annId' = Just loc - | otherwise = Nothing - -- | Convert 'Text' to a 'StringBuffer' by making a copy. textToStringBuffer :: Text -> StringBuffer textToStringBuffer txt = unsafePerformIO $ do diff --git a/src/Ormolu/Utils/Cabal.hs b/src/Ormolu/Utils/Cabal.hs index af1f0b553..0541a6f95 100644 --- a/src/Ormolu/Utils/Cabal.hs +++ b/src/Ormolu/Utils/Cabal.hs @@ -189,17 +189,17 @@ getExtensionAndDepsMap cabalFile GenericPackageDescription {..} = extractFromLibrary Library {..} = extractFromBuildInfo (ModuleName.toFilePath <$> exposedModules) libBuildInfo extractFromExecutable Executable {..} = - extractFromBuildInfo [modulePath] buildInfo + extractFromBuildInfo [getSymbolicPath modulePath] buildInfo extractFromTestSuite TestSuite {..} = extractFromBuildInfo mainPath testBuildInfo where mainPath = case testInterface of - TestSuiteExeV10 _ p -> [p] + TestSuiteExeV10 _ p -> [getSymbolicPath p] TestSuiteLibV09 _ p -> [ModuleName.toFilePath p] TestSuiteUnsupported {} -> [] extractFromBenchmark Benchmark {..} = extractFromBuildInfo mainPath benchmarkBuildInfo where mainPath = case benchmarkInterface of - BenchmarkExeV10 _ p -> [p] + BenchmarkExeV10 _ p -> [getSymbolicPath p] BenchmarkUnsupported {} -> [] diff --git a/stack.yaml b/stack.yaml index 12967a937..43e8c0a64 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,13 +1,21 @@ -resolver: nightly-2024-05-10 +resolver: nightly-2025-01-15 packages: - '.' - extract-hackage-info extra-deps: -- ghc-lib-parser-9.10.1.20240511 -- Cabal-syntax-3.12.0.0 -- choice-0.2.4.1 +- ghc-lib-parser-9.12.1.20250105 +- Cabal-syntax-3.14.1.0 +- path-0.9.6 +- path-io-1.8.2 + +allow-newer: true +allow-newer-deps: [path] + +flags: + path: + os-string: true nix: packages: