tilia 0.1.2.0 → 0.1.3.0
raw patch · 132 files changed
+3877/−1875 lines, 132 filesdep ~Cabal-syntaxPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: Cabal-syntax
API changes (from Hackage documentation)
- Tilia.Cabal.Package: benchmarkBranches :: CondTree ConfVar c Benchmark -> [BranchSections]
- Tilia.Cabal.Package: executableBranches :: CondTree ConfVar c Executable -> [BranchSections]
- Tilia.Cabal.Package: instance GHC.Classes.Eq Tilia.Cabal.Package.PackageProblem
- Tilia.Cabal.Package: libraryBranches :: CondTree ConfVar c Library -> [BranchSections]
- Tilia.Cabal.Package: suiteBranches :: CondTree ConfVar c TestSuite -> [BranchSections]
- Tilia.Cabal.Plan: instance GHC.Classes.Eq Tilia.Cabal.Plan.BuildPlan
- Tilia.Cabal.Plan: instance GHC.Classes.Eq Tilia.Cabal.Plan.PackageSource
- Tilia.Cabal.Plan: instance GHC.Classes.Eq Tilia.Cabal.Plan.PlanComponent
- Tilia.Cabal.Plan: instance GHC.Classes.Eq Tilia.Cabal.Plan.PlanPackage
- Tilia.Cabal.Plan: instance GHC.Classes.Eq Tilia.Cabal.Plan.Readiness
- Tilia.Cabal.Plan: instance GHC.Classes.Eq Tilia.Cabal.Plan.RepoProvenance
- Tilia.Cabal.Plan: instance GHC.Classes.Ord Tilia.Cabal.Plan.PlanComponent
- Tilia.Cabal.Plan: spellPlanComponent :: PlanComponent -> Text
- Tilia.Cabal.Project: instance GHC.Classes.Eq Tilia.Cabal.Project.Marker
- Tilia.Cabal.Project: instance GHC.Classes.Eq Tilia.Cabal.Project.ProjectRoot
- Tilia.Cabal.Target: NoPackages :: FilePath -> TargetProblem
- Tilia.Cabal.Target: NoSuchTarget :: Text -> [Text] -> TargetProblem
- Tilia.Cabal.Target: UnreadablePackage :: PackageProblem -> TargetProblem
- Tilia.Cabal.Target: instance GHC.Classes.Eq Tilia.Cabal.Target.Component
- Tilia.Cabal.Target: instance GHC.Classes.Eq Tilia.Cabal.Target.Kind
- Tilia.Cabal.Target: instance GHC.Classes.Eq Tilia.Cabal.Target.Target
- Tilia.Cabal.Target: instance GHC.Classes.Eq Tilia.Cabal.Target.TargetProblem
- Tilia.Cabal.Target: instance GHC.Classes.Eq Tilia.Cabal.Target.Wanted
- Tilia.Cabal.Target: instance GHC.Classes.Ord Tilia.Cabal.Target.Kind
- Tilia.Cabal.Target: instance GHC.Internal.Show.Show Tilia.Cabal.Target.Kind
- Tilia.Comments: instance GHC.Classes.Eq Tilia.Comments.Above
- Tilia.Comments: instance GHC.Classes.Eq Tilia.Comments.Comment
- Tilia.Comments: instance GHC.Classes.Eq Tilia.Comments.CommentStyle
- Tilia.Comments: instance GHC.Classes.Eq Tilia.Comments.Pragma
- Tilia.Comments.Attach: noMargin :: Margin
- Tilia.Comments.Place: instance GHC.Classes.Eq Tilia.Comments.Place.Alignment
- Tilia.Comments.Place: instance GHC.Classes.Eq Tilia.Comments.Place.Position
- Tilia.Comments.Place: instance GHC.Classes.Eq Tilia.Comments.Place.Shape
- Tilia.Cpp.Directives: AfterElse :: Malformation
- Tilia.Cpp.Directives: NeverClosed :: Malformation
- Tilia.Cpp.Directives: NothingOpen :: Malformation
- Tilia.Cpp.Directives: instance GHC.Classes.Eq Tilia.Cpp.Directives.Configurations
- Tilia.Cpp.Directives: instance GHC.Classes.Eq Tilia.Cpp.Directives.Directive
- Tilia.Cpp.Directives: instance GHC.Classes.Eq Tilia.Cpp.Directives.Guard
- Tilia.Cpp.Directives: instance GHC.Classes.Eq Tilia.Cpp.Directives.Varied
- Tilia.Cpp.Directives: instance GHC.Classes.Ord Tilia.Cpp.Directives.Guard
- Tilia.Cpp.Directives: resolved :: Text -> Either CppError Text
- Tilia.Cpp.Macros: instance GHC.Classes.Eq Tilia.Cpp.Macros.Macros
- Tilia.Cpp.Macros: instance GHC.Classes.Eq Tilia.Cpp.Macros.Token
- Tilia.Cpp.Merge: instance GHC.Classes.Eq Tilia.Cpp.Merge.PartStart
- Tilia.Cpp.Merge: instance GHC.Classes.Eq Tilia.Cpp.Merge.Space
- Tilia.Cpp.Merge: instance GHC.Classes.Ord Tilia.Cpp.Merge.Space
- Tilia.Cpp.Place: instance GHC.Classes.Eq Tilia.Cpp.Place.Printed
- Tilia.Diff: instance GHC.Classes.Eq Tilia.Diff.Mark
- Tilia.Doc.Combinators: bracket :: Text -> Text -> Doc -> Doc
- Tilia.Doc.Combinators: instance GHC.Classes.Eq Tilia.Doc.Combinators.ClosingIndent
- Tilia.Doc.Combinators: instance GHC.Classes.Eq Tilia.Doc.Combinators.Placement
- Tilia.Doc.Internal: instance GHC.Classes.Eq Tilia.Doc.Internal.Conditional
- Tilia.Doc.Internal: instance GHC.Classes.Eq Tilia.Doc.Internal.Doc
- Tilia.Doc.Internal: instance GHC.Classes.Eq Tilia.Doc.Internal.Layout
- Tilia.Doc.Internal: instance GHC.Classes.Eq Tilia.Doc.Internal.LineStart
- Tilia.Doc.Internal: instance GHC.Classes.Eq Tilia.Doc.Internal.Spill
- Tilia.Doc.Internal: instance GHC.Classes.Eq Tilia.Doc.Internal.TrailingWhitespace
- Tilia.Doc.Internal: instance GHC.Classes.Eq Tilia.Doc.Internal.Wrapper
- Tilia.Doc.Internal: instance GHC.Classes.Ord Tilia.Doc.Internal.Conditional
- Tilia.Doc.Internal: render :: Doc -> Text
- Tilia.Fixity: declaredNames :: HsModule GhcPs -> Set (Namespace, OpName)
- Tilia.Fixity: inBothNamespaces :: Map OpName Fixity -> Fixities
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.Certain
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.Direction
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.Established
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.ExportItem
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.Fixity
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.Import
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.ImportItem
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.ModuleChain
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.ModuleSummary
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.Namespace
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.OpName
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.Provenance
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.Reach
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.Resolution
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.Scope
- Tilia.Fixity: instance GHC.Classes.Eq Tilia.Fixity.Unknown
- Tilia.Fixity: instance GHC.Classes.Ord Tilia.Fixity.Namespace
- Tilia.Fixity: instance GHC.Classes.Ord Tilia.Fixity.OpName
- Tilia.Fixity.Cabal: containedModules :: Text -> [Text]
- Tilia.Fixity.Cabal: declaredExtensions :: Text -> [Extension]
- Tilia.Fixity.Cabal: sourceDirs :: Text -> [Text]
- Tilia.Fixity.Cache: instance GHC.Classes.Eq Tilia.Fixity.Cache.PlanToken
- Tilia.Fixity.Debug: FixityNotes :: [ImportNote] -> [OperatorNote] -> [(Text, Fixity)] -> FixityNotes
- Tilia.Fixity.Debug: ImportNote :: Text -> Maybe Text -> Bool -> Maybe Int -> [[Text]] -> ImportNote
- Tilia.Fixity.Debug: OperatorNote :: Text -> Resolution -> Maybe (NonEmpty (Text, Fixity)) -> OperatorNote
- Tilia.Fixity.Debug: [noteAlias] :: ImportNote -> Maybe Text
- Tilia.Fixity.Debug: [noteDisagreement] :: OperatorNote -> Maybe (NonEmpty (Text, Fixity))
- Tilia.Fixity.Debug: [noteModule] :: ImportNote -> Text
- Tilia.Fixity.Debug: [noteOperatorCount] :: ImportNote -> Maybe Int
- Tilia.Fixity.Debug: [noteQualified] :: ImportNote -> Bool
- Tilia.Fixity.Debug: [noteResolution] :: OperatorNote -> Resolution
- Tilia.Fixity.Debug: [noteSpelling] :: OperatorNote -> Text
- Tilia.Fixity.Debug: [noteUnsettled] :: ImportNote -> [[Text]]
- Tilia.Fixity.Debug: [notedDeclarations] :: FixityNotes -> [(Text, Fixity)]
- Tilia.Fixity.Debug: [notedImports] :: FixityNotes -> [ImportNote]
- Tilia.Fixity.Debug: [notedOperators] :: FixityNotes -> [OperatorNote]
- Tilia.Fixity.Debug: data ImportNote
- Tilia.Fixity.Debug: data OperatorNote
- Tilia.Fixity.Debug: instance GHC.Classes.Eq Tilia.Fixity.Debug.FixityNotes
- Tilia.Fixity.Debug: instance GHC.Classes.Eq Tilia.Fixity.Debug.ImportNote
- Tilia.Fixity.Debug: instance GHC.Classes.Eq Tilia.Fixity.Debug.OperatorNote
- Tilia.Fixity.HiFile: instance GHC.Classes.Eq Tilia.Fixity.HiFile.HiExport
- Tilia.Fixity.HiFile: instance GHC.Classes.Eq Tilia.Fixity.HiFile.HiFile
- Tilia.Fixity.HiFile: instance GHC.Classes.Eq Tilia.Fixity.HiFile.HiName
- Tilia.Fixity.HiFile: instance GHC.Classes.Eq Tilia.Fixity.HiFile.Series
- Tilia.Fixity.Interface: instance GHC.Classes.Eq Tilia.Fixity.Interface.Interface
- Tilia.Fixity.PackageDb: instance GHC.Classes.Eq Tilia.Fixity.PackageDb.Installed
- Tilia.Fixity.PackageDb: instance GHC.Classes.Eq Tilia.Fixity.PackageDb.InstalledPackage
- Tilia.Fixity.Plan: instance GHC.Classes.Eq Tilia.Fixity.Plan.Route
- Tilia.Ignore: belowRoot :: FilePath -> FilePath -> Maybe [FilePath]
- Tilia.Ignore: ignoreFilesAbove :: FilePath -> [[FilePath]] -> IO (Map [FilePath] IgnoreFile)
- Tilia.Imports: instance GHC.Classes.Eq Tilia.Imports.ImportIdentity
- Tilia.Imports: instance GHC.Classes.Eq Tilia.Imports.NameIdentity
- Tilia.Imports: instance GHC.Classes.Ord Tilia.Imports.ImportIdentity
- Tilia.Imports: instance GHC.Classes.Ord Tilia.Imports.NameIdentity
- Tilia.Newline: instance GHC.Classes.Eq Tilia.Newline.NewlineStyle
- Tilia.Palette: instance GHC.Classes.Eq Tilia.Palette.Color
- Tilia.Palette: instance GHC.Classes.Eq Tilia.Palette.Palette
- Tilia.Parser: [pcExtensions] :: ParserConfig -> [Extension]
- Tilia.Pragma: applyExtensionName :: [Extension] -> Text -> [Extension]
- Tilia.Render.Context: instance GHC.Classes.Eq Tilia.Render.Context.FamilyStyle
- Tilia.Render.Context: instance GHC.Classes.Eq Tilia.Render.Context.Site
- Tilia.Render.Declaration: instance GHC.Classes.Eq Tilia.Render.Declaration.Grouping
- Tilia.Render.Declaration: instance GHC.Classes.Eq Tilia.Render.Declaration.Kind
- Tilia.Render.Expression: instance GHC.Classes.Eq Tilia.Render.Expression.GuardStyle
- Tilia.Render.Haddock: instance GHC.Classes.Eq Tilia.Render.Haddock.DocStyle
- Tilia.Render.Haddock: instance GHC.Classes.Eq Tilia.Render.Haddock.Ending
- Tilia.Render.Header: instance GHC.Classes.Eq Tilia.Render.Header.ExtensionClass
- Tilia.Render.Header: instance GHC.Classes.Eq Tilia.Render.Header.HeaderPragma
- Tilia.Render.Header: instance GHC.Classes.Eq Tilia.Render.Header.PragmaOrder
- Tilia.Render.Header: instance GHC.Classes.Ord Tilia.Render.Header.ExtensionClass
- Tilia.Render.Header: instance GHC.Classes.Ord Tilia.Render.Header.PragmaOrder
- Tilia.Render.Header: instance GHC.Internal.Show.Show Tilia.Render.Header.ExtensionClass
- Tilia.Render.Layout: instance GHC.Classes.Eq Tilia.Render.Layout.Bracing
- Tilia.Render.Layout: instance GHC.Classes.Eq Tilia.Render.Layout.Position
- Tilia.Render.Literal: instance GHC.Classes.Eq Tilia.Render.Literal.Literal
- Tilia.Render.Literal: instance GHC.Classes.Eq Tilia.Render.Literal.LiteralKind
- Tilia.Render.Operator: instance (GHC.Classes.Eq a, GHC.Classes.Eq op) => GHC.Classes.Eq (Tilia.Render.Operator.OpChain a op)
- Tilia.Render.Type: instance GHC.Classes.Eq Tilia.Render.Type.Visibility
- Tilia.Run: instance GHC.Classes.Eq Tilia.Run.Report
- Tilia.Source: instance GHC.Classes.Eq Tilia.Source.SourceType
- Tilia.Source.Lines: instance GHC.Classes.Eq Tilia.Source.Lines.Written
- Tilia.Span: blankBetween :: Maybe Span -> Maybe Span -> Bool
- Tilia.Span: instance GHC.Classes.Eq Tilia.Span.Span
- Tilia.Span: instance GHC.Classes.Ord Tilia.Span.Span
- Tilia.Utils: readFileText :: FilePath -> IO (Maybe Text)
+ Tilia.Cabal.Package: Bench :: Kind
+ Tilia.Cabal.Package: Exe :: Kind
+ Tilia.Cabal.Package: Lib :: Kind
+ Tilia.Cabal.Package: Setup :: Kind
+ Tilia.Cabal.Package: Test :: Kind
+ Tilia.Cabal.Package: data Kind
+ Tilia.Cabal.Package: instance GHC.Internal.Classes.Eq Tilia.Cabal.Package.Kind
+ Tilia.Cabal.Package: instance GHC.Internal.Classes.Eq Tilia.Cabal.Package.PackageProblem
+ Tilia.Cabal.Package: instance GHC.Internal.Classes.Ord Tilia.Cabal.Package.Kind
+ Tilia.Cabal.Package: instance GHC.Internal.Show.Show Tilia.Cabal.Package.Kind
+ Tilia.Cabal.Package: packageComponents :: GenericPackageDescription -> [(Kind, Text, [BranchSections])]
+ Tilia.Cabal.Package: packageExtensions :: GenericPackageDescription -> Defaults
+ Tilia.Cabal.Package: packageOf :: GenericPackageDescription -> Text
+ Tilia.Cabal.Package: packageSourceDirs :: GenericPackageDescription -> [FilePath]
+ Tilia.Cabal.Package: parsePackageDescription :: FilePath -> ByteString -> Either PackageProblem GenericPackageDescription
+ Tilia.Cabal.Plan: instance GHC.Internal.Classes.Eq Tilia.Cabal.Plan.BuildPlan
+ Tilia.Cabal.Plan: instance GHC.Internal.Classes.Eq Tilia.Cabal.Plan.PackageSource
+ Tilia.Cabal.Plan: instance GHC.Internal.Classes.Eq Tilia.Cabal.Plan.PlanComponent
+ Tilia.Cabal.Plan: instance GHC.Internal.Classes.Eq Tilia.Cabal.Plan.PlanPackage
+ Tilia.Cabal.Plan: instance GHC.Internal.Classes.Eq Tilia.Cabal.Plan.Readiness
+ Tilia.Cabal.Plan: instance GHC.Internal.Classes.Eq Tilia.Cabal.Plan.RepoProvenance
+ Tilia.Cabal.Plan: instance GHC.Internal.Classes.Ord Tilia.Cabal.Plan.PlanComponent
+ Tilia.Cabal.Project: instance GHC.Internal.Classes.Eq Tilia.Cabal.Project.Marker
+ Tilia.Cabal.Project: instance GHC.Internal.Classes.Eq Tilia.Cabal.Project.ProjectRoot
+ Tilia.Cabal.Project: startingDirectory :: FilePath -> IO FilePath
+ Tilia.Cabal.Target: instance GHC.Internal.Classes.Eq Tilia.Cabal.Target.Component
+ Tilia.Cabal.Target: instance GHC.Internal.Classes.Eq Tilia.Cabal.Target.Target
+ Tilia.Cabal.Target: instance GHC.Internal.Classes.Eq Tilia.Cabal.Target.TargetProblem
+ Tilia.Cabal.Target: instance GHC.Internal.Classes.Eq Tilia.Cabal.Target.Wanted
+ Tilia.Cabal.Target: packageFilesOf :: ProjectRoot -> IO [FilePath]
+ Tilia.Comments: [pragmaCommented] :: Pragma -> Bool
+ Tilia.Comments: instance GHC.Internal.Classes.Eq Tilia.Comments.Above
+ Tilia.Comments: instance GHC.Internal.Classes.Eq Tilia.Comments.Comment
+ Tilia.Comments: instance GHC.Internal.Classes.Eq Tilia.Comments.CommentStyle
+ Tilia.Comments: instance GHC.Internal.Classes.Eq Tilia.Comments.Pragma
+ Tilia.Comments.Place: instance GHC.Internal.Classes.Eq Tilia.Comments.Place.Alignment
+ Tilia.Comments.Place: instance GHC.Internal.Classes.Eq Tilia.Comments.Place.Position
+ Tilia.Comments.Place: instance GHC.Internal.Classes.Eq Tilia.Comments.Place.Shape
+ Tilia.Cpp.Directives: instance GHC.Internal.Classes.Eq Tilia.Cpp.Directives.Configurations
+ Tilia.Cpp.Directives: instance GHC.Internal.Classes.Eq Tilia.Cpp.Directives.Directive
+ Tilia.Cpp.Directives: instance GHC.Internal.Classes.Eq Tilia.Cpp.Directives.Guard
+ Tilia.Cpp.Directives: instance GHC.Internal.Classes.Eq Tilia.Cpp.Directives.Varied
+ Tilia.Cpp.Directives: instance GHC.Internal.Classes.Ord Tilia.Cpp.Directives.Guard
+ Tilia.Cpp.Macros: instance GHC.Internal.Classes.Eq Tilia.Cpp.Macros.Macros
+ Tilia.Cpp.Macros: instance GHC.Internal.Classes.Eq Tilia.Cpp.Macros.Token
+ Tilia.Cpp.Macros: isNameChar :: Char -> Bool
+ Tilia.Cpp.Merge: instance GHC.Internal.Classes.Eq Tilia.Cpp.Merge.PartStart
+ Tilia.Cpp.Merge: instance GHC.Internal.Classes.Eq Tilia.Cpp.Merge.Space
+ Tilia.Cpp.Merge: instance GHC.Internal.Classes.Ord Tilia.Cpp.Merge.Space
+ Tilia.Cpp.Place: instance GHC.Internal.Classes.Eq Tilia.Cpp.Place.Printed
+ Tilia.Diff: instance GHC.Internal.Classes.Eq Tilia.Diff.Mark
+ Tilia.Doc.Combinators: instance GHC.Internal.Classes.Eq Tilia.Doc.Combinators.ClosingIndent
+ Tilia.Doc.Combinators: instance GHC.Internal.Classes.Eq Tilia.Doc.Combinators.Placement
+ Tilia.Doc.Combinators: verbatimLines :: LineStart -> TrailingWhitespace -> [Text] -> Doc
+ Tilia.Doc.Internal: instance GHC.Internal.Classes.Eq Tilia.Doc.Internal.Conditional
+ Tilia.Doc.Internal: instance GHC.Internal.Classes.Eq Tilia.Doc.Internal.Doc
+ Tilia.Doc.Internal: instance GHC.Internal.Classes.Eq Tilia.Doc.Internal.Layout
+ Tilia.Doc.Internal: instance GHC.Internal.Classes.Eq Tilia.Doc.Internal.LineStart
+ Tilia.Doc.Internal: instance GHC.Internal.Classes.Eq Tilia.Doc.Internal.Spill
+ Tilia.Doc.Internal: instance GHC.Internal.Classes.Eq Tilia.Doc.Internal.TrailingWhitespace
+ Tilia.Doc.Internal: instance GHC.Internal.Classes.Eq Tilia.Doc.Internal.Wrapper
+ Tilia.Doc.Internal: instance GHC.Internal.Classes.Ord Tilia.Doc.Internal.Conditional
+ Tilia.Doc.Internal: printDoc :: Doc -> Text
+ Tilia.Fixity: disagreement :: Scope -> Namespace -> Maybe Text -> OpName -> Maybe (NonEmpty (Text, Fixity))
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.Certain
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.Direction
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.Established
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.ExportItem
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.Fixity
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.Import
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.ImportItem
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.ModuleChain
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.ModuleSummary
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.Namespace
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.OpName
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.Provenance
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.Reach
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.Resolution
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.Scope
+ Tilia.Fixity: instance GHC.Internal.Classes.Eq Tilia.Fixity.Unknown
+ Tilia.Fixity: instance GHC.Internal.Classes.Ord Tilia.Fixity.Namespace
+ Tilia.Fixity: instance GHC.Internal.Classes.Ord Tilia.Fixity.OpName
+ Tilia.Fixity: opName :: RdrName -> OpName
+ Tilia.Fixity: qualifierOf :: RdrName -> Maybe Text
+ Tilia.Fixity.Cabal: ownCabalFile :: Entry -> Maybe GenericPackageDescription
+ Tilia.Fixity.Cache: instance GHC.Internal.Classes.Eq Tilia.Fixity.Cache.PlanToken
+ Tilia.Fixity.Debug: instance GHC.Internal.Classes.Eq Tilia.Fixity.Debug.FixityNotes
+ Tilia.Fixity.Debug: instance GHC.Internal.Classes.Eq Tilia.Fixity.Debug.ImportNote
+ Tilia.Fixity.Debug: instance GHC.Internal.Classes.Eq Tilia.Fixity.Debug.OperatorNote
+ Tilia.Fixity.HiFile: instance GHC.Internal.Classes.Eq Tilia.Fixity.HiFile.HiExport
+ Tilia.Fixity.HiFile: instance GHC.Internal.Classes.Eq Tilia.Fixity.HiFile.HiFile
+ Tilia.Fixity.HiFile: instance GHC.Internal.Classes.Eq Tilia.Fixity.HiFile.HiName
+ Tilia.Fixity.HiFile: instance GHC.Internal.Classes.Eq Tilia.Fixity.HiFile.Series
+ Tilia.Fixity.Interface: instance GHC.Internal.Classes.Eq Tilia.Fixity.Interface.Interface
+ Tilia.Fixity.PackageDb: instance GHC.Internal.Classes.Eq Tilia.Fixity.PackageDb.Installed
+ Tilia.Fixity.PackageDb: instance GHC.Internal.Classes.Eq Tilia.Fixity.PackageDb.InstalledPackage
+ Tilia.Fixity.Plan: instance GHC.Internal.Classes.Eq Tilia.Fixity.Plan.Route
+ Tilia.Format: Threw :: FilePath -> Text -> FormatError
+ Tilia.Ignore: withoutIgnored :: FilePath -> [FilePath] -> IO [FilePath]
+ Tilia.Imports: instance GHC.Internal.Classes.Eq Tilia.Imports.ImportIdentity
+ Tilia.Imports: instance GHC.Internal.Classes.Eq Tilia.Imports.NameIdentity
+ Tilia.Imports: instance GHC.Internal.Classes.Ord Tilia.Imports.ImportIdentity
+ Tilia.Imports: instance GHC.Internal.Classes.Ord Tilia.Imports.NameIdentity
+ Tilia.Newline: instance GHC.Internal.Classes.Eq Tilia.Newline.NewlineStyle
+ Tilia.Palette: instance GHC.Internal.Classes.Eq Tilia.Palette.Color
+ Tilia.Palette: instance GHC.Internal.Classes.Eq Tilia.Palette.Palette
+ Tilia.Parser: [pcDefaults] :: ParserConfig -> Defaults
+ Tilia.Parser: writtenInHeader :: Maybe Span -> Span -> Bool
+ Tilia.Pragma: data Defaults
+ Tilia.Pragma: defaultsOf :: [Extension] -> [OnOff Extension] -> Defaults
+ Tilia.Pragma: flagSwitches :: Text -> [OnOff Extension]
+ Tilia.Pragma: instance GHC.Internal.Classes.Eq Tilia.Pragma.Defaults
+ Tilia.Pragma: instance GHC.Internal.Show.Show Tilia.Pragma.Defaults
+ Tilia.Pragma: nameSwitch :: Text -> Maybe (OnOff Extension)
+ Tilia.Pragma: optionFlags :: Text -> [Text]
+ Tilia.Pragma: pragmaFlags :: Text -> Text -> [Text]
+ Tilia.Pragma: sameInForce :: [(Text, Text)] -> [(Text, Text)] -> Bool
+ Tilia.Pragma: withoutSkippedComments :: Text -> Text
+ Tilia.Pragma.Ghc: InForce :: Map Extension Bool -> Maybe Language -> SafeHaskellMode -> InForce
+ Tilia.Pragma.Ghc: [inForceEdition] :: InForce -> Maybe Language
+ Tilia.Pragma.Ghc: [inForceSafety] :: InForce -> SafeHaskellMode
+ Tilia.Pragma.Ghc: [inForceSwitches] :: InForce -> Map Extension Bool
+ Tilia.Pragma.Ghc: data InForce
+ Tilia.Pragma.Ghc: inForceAfter :: [Text] -> InForce
+ Tilia.Pragma.Ghc: readFlags :: [Text] -> DynFlags
+ Tilia.Render.Context: instance GHC.Internal.Classes.Eq Tilia.Render.Context.FamilyStyle
+ Tilia.Render.Context: instance GHC.Internal.Classes.Eq Tilia.Render.Context.Site
+ Tilia.Render.Declaration: instance GHC.Internal.Classes.Eq Tilia.Render.Declaration.Grouping
+ Tilia.Render.Declaration: instance GHC.Internal.Classes.Eq Tilia.Render.Declaration.Kind
+ Tilia.Render.Expression: instance GHC.Internal.Classes.Eq Tilia.Render.Expression.GuardStyle
+ Tilia.Render.Haddock: instance GHC.Internal.Classes.Eq Tilia.Render.Haddock.DocStyle
+ Tilia.Render.Haddock: instance GHC.Internal.Classes.Eq Tilia.Render.Haddock.Ending
+ Tilia.Render.Header: [hpWritten] :: HeaderPragma -> Maybe (NonEmpty Text)
+ Tilia.Render.Header: instance GHC.Internal.Classes.Eq Tilia.Render.Header.HeaderPragma
+ Tilia.Render.Header: instance GHC.Internal.Classes.Eq Tilia.Render.Header.PragmaOrder
+ Tilia.Render.Header: instance GHC.Internal.Classes.Ord Tilia.Render.Header.PragmaOrder
+ Tilia.Render.Layout: instance GHC.Internal.Classes.Eq Tilia.Render.Layout.Bracing
+ Tilia.Render.Layout: instance GHC.Internal.Classes.Eq Tilia.Render.Layout.Position
+ Tilia.Render.Literal: hsLit :: HsLit GhcPs -> Doc
+ Tilia.Render.Literal: instance GHC.Internal.Classes.Eq Tilia.Render.Literal.Literal
+ Tilia.Render.Literal: instance GHC.Internal.Classes.Eq Tilia.Render.Literal.LiteralKind
+ Tilia.Render.Literal: writtenString :: StringLiteral -> Doc
+ Tilia.Render.Operator: instance (GHC.Internal.Classes.Eq a, GHC.Internal.Classes.Eq op) => GHC.Internal.Classes.Eq (Tilia.Render.Operator.OpChain a op)
+ Tilia.Render.Type: instance GHC.Internal.Classes.Eq Tilia.Render.Type.Visibility
+ Tilia.Render.Type: typeBreak :: Bool -> Doc
+ Tilia.Run: instance GHC.Internal.Classes.Eq Tilia.Run.Report
+ Tilia.Source: blankAcross :: (Int, Int) -> Lines -> Bool
+ Tilia.Source: instance GHC.Internal.Classes.Eq Tilia.Source.SourceType
+ Tilia.Source: offsetOf :: Text -> Int -> Int
+ Tilia.Source.Lines: blankAcross :: (Int, Int) -> Lines -> Bool
+ Tilia.Source.Lines: columnOf :: Text -> Int -> Int
+ Tilia.Source.Lines: contentColumn :: Text -> Int
+ Tilia.Source.Lines: instance GHC.Internal.Classes.Eq Tilia.Source.Lines.Written
+ Tilia.Source.Lines: offsetOf :: Text -> Int -> Int
+ Tilia.Span: instance GHC.Internal.Classes.Eq Tilia.Span.Span
+ Tilia.Span: instance GHC.Internal.Classes.Ord Tilia.Span.Span
+ Tilia.Span.Ghc: inSourceOrder :: HasLoc l => [[GenLocated l a]] -> [GenLocated l a]
+ Tilia.Utils: blocksOf :: [Text] -> [(Text, [Text])]
+ Tilia.Utils: indented :: Text -> Bool
+ Tilia.Utils: looksLikeModule :: Text -> Bool
+ Tilia.Utils: pathUnder :: FilePath -> FilePath -> Maybe [FilePath]
+ Tilia.Utils: readUtf8File :: FilePath -> IO (Maybe Text)
+ Tilia.Utils: sha256OfFile :: FilePath -> IO Text
+ Tilia.Utils: wrappedUnder :: Int -> Text -> Text -> [Text]
+ Tilia.Utils: writeUtf8File :: FilePath -> Text -> IO ()
- Tilia.Cabal.Package: newPackageReader :: Maybe Version -> IO PackageReader
+ Tilia.Cabal.Package: newPackageReader :: Maybe Version -> [FilePath] -> IO PackageReader
- Tilia.Cabal.Package: type PackageReader = FilePath -> IO Either PackageProblem [Extension]
+ Tilia.Cabal.Package: type PackageReader = FilePath -> IO Either PackageProblem Defaults
- Tilia.Comments: ContentAt :: !Int -> Above
+ Tilia.Comments: ContentAt :: Int -> Above
- Tilia.Comments: Pragma :: Text -> Text -> Pragma
+ Tilia.Comments: Pragma :: Text -> Text -> Bool -> Pragma
- Tilia.Cpp.Directives: Directive :: !Int -> !Int -> !Text -> !Text -> Directive
+ Tilia.Cpp.Directives: Directive :: Int -> Int -> Text -> Text -> Directive
- Tilia.Cpp.Directives: [dKeyword] :: Directive -> !Text
+ Tilia.Cpp.Directives: [dKeyword] :: Directive -> Text
- Tilia.Cpp.Directives: [dLastLine] :: Directive -> !Int
+ Tilia.Cpp.Directives: [dLastLine] :: Directive -> Int
- Tilia.Cpp.Directives: [dLine] :: Directive -> !Int
+ Tilia.Cpp.Directives: [dLine] :: Directive -> Int
- Tilia.Cpp.Directives: [dText] :: Directive -> !Text
+ Tilia.Cpp.Directives: [dText] :: Directive -> Text
- Tilia.Doc.Internal: DAlign :: !Doc -> Doc
+ Tilia.Doc.Internal: DAlign :: Doc -> Doc
- Tilia.Doc.Internal: DCat :: !Doc -> !Doc -> Doc
+ Tilia.Doc.Internal: DCat :: Doc -> Doc -> Doc
- Tilia.Doc.Internal: DCloseLineUnlessAfterOpener :: !Bool -> Doc
+ Tilia.Doc.Internal: DCloseLineUnlessAfterOpener :: Bool -> Doc
- Tilia.Doc.Internal: DCppChoice :: ![Conditional] -> ![(Text, Doc)] -> !Doc -> Doc
+ Tilia.Doc.Internal: DCppChoice :: [Conditional] -> [(Text, Doc)] -> Doc -> Doc
- Tilia.Doc.Internal: DCppDirective :: !Span -> !Text -> Doc
+ Tilia.Doc.Internal: DCppDirective :: Span -> Text -> Doc
- Tilia.Doc.Internal: DCppMarginNote :: !Doc -> Doc
+ Tilia.Doc.Internal: DCppMarginNote :: Doc -> Doc
- Tilia.Doc.Internal: DFence :: !Span -> !Doc -> Doc
+ Tilia.Doc.Internal: DFence :: Span -> Doc -> Doc
- Tilia.Doc.Internal: DGroup :: !Layout -> !Doc -> Doc
+ Tilia.Doc.Internal: DGroup :: Layout -> Doc -> Doc
- Tilia.Doc.Internal: DHoldBack :: !Spill -> !Text -> Doc
+ Tilia.Doc.Internal: DHoldBack :: Spill -> Text -> Doc
- Tilia.Doc.Internal: DLocated :: !Span -> !Doc -> Doc
+ Tilia.Doc.Internal: DLocated :: Span -> Doc -> Doc
- Tilia.Doc.Internal: DNest :: !Int -> !Doc -> Doc
+ Tilia.Doc.Internal: DNest :: Int -> Doc -> Doc
- Tilia.Doc.Internal: DText :: !Text -> Doc
+ Tilia.Doc.Internal: DText :: Text -> Doc
- Tilia.Doc.Internal: DVerbatimBreak :: !LineStart -> !TrailingWhitespace -> Doc
+ Tilia.Doc.Internal: DVerbatimBreak :: LineStart -> TrailingWhitespace -> Doc
- Tilia.Doc.Internal: WFence :: !Span -> Wrapper
+ Tilia.Doc.Internal: WFence :: Span -> Wrapper
- Tilia.Doc.Internal: WGroup :: !Layout -> Wrapper
+ Tilia.Doc.Internal: WGroup :: Layout -> Wrapper
- Tilia.Doc.Internal: WLocated :: !Span -> Wrapper
+ Tilia.Doc.Internal: WLocated :: Span -> Wrapper
- Tilia.Doc.Internal: WNest :: !Int -> Wrapper
+ Tilia.Doc.Internal: WNest :: Int -> Wrapper
- Tilia.Fixity.Archive: Archive :: Maybe Text -> [(FilePath, ByteString)] -> Archive
+ Tilia.Fixity.Archive: Archive :: Maybe GenericPackageDescription -> [(FilePath, ByteString)] -> Archive
- Tilia.Fixity.Archive: [archiveCabal] :: Archive -> Maybe Text
+ Tilia.Fixity.Archive: [archiveCabal] :: Archive -> Maybe GenericPackageDescription
- Tilia.Fixity.Cabal: cabalFileInArchive :: Entries e -> Maybe Text
+ Tilia.Fixity.Cabal: cabalFileInArchive :: Entries e -> Maybe GenericPackageDescription
- Tilia.Fixity.Interface: Interface :: Fixities -> [(Text, OpName)] -> Map OpName (Set OpName) -> Maybe Certain -> Interface
+ Tilia.Fixity.Interface: Interface :: Fixities -> [(Text, Namespace, OpName)] -> Map OpName (Set OpName) -> Maybe Certain -> Interface
- Tilia.Fixity.Interface: [interfaceReexports] :: Interface -> [(Text, OpName)]
+ Tilia.Fixity.Interface: [interfaceReexports] :: Interface -> [(Text, Namespace, OpName)]
- Tilia.Parser: ParserConfig :: [Extension] -> ParserConfig
+ Tilia.Parser: ParserConfig :: Defaults -> ParserConfig
- Tilia.Pragma: effectiveExtensions :: [Extension] -> Text -> [Extension]
+ Tilia.Pragma: effectiveExtensions :: Defaults -> Text -> [Extension]
- Tilia.Render.Context: Knot :: (Ctx -> Site -> LHsExpr GhcPs -> Doc) -> (Ctx -> SpliceDecoration -> HsUntypedSplice GhcPs -> Doc) -> (Ctx -> Sig GhcPs -> Doc) -> (Ctx -> FamilyStyle -> [LHsDecl GhcPs] -> Doc) -> (Ctx -> FamilyStyle -> [LHsDecl GhcPs] -> Doc) -> Knot
+ Tilia.Render.Context: Knot :: (Ctx -> Site -> LHsExpr GhcPs -> Doc) -> (Ctx -> SpliceDecoration -> HsUntypedSplice GhcPs -> Doc) -> (Ctx -> Sig GhcPs -> Doc) -> (Ctx -> [LHsDecl GhcPs] -> Doc) -> (Ctx -> [LHsDecl GhcPs] -> Doc) -> Knot
- Tilia.Render.Context: [knotDeclsGrouped] :: Knot -> Ctx -> FamilyStyle -> [LHsDecl GhcPs] -> Doc
+ Tilia.Render.Context: [knotDeclsGrouped] :: Knot -> Ctx -> [LHsDecl GhcPs] -> Doc
- Tilia.Render.Context: [knotDecls] :: Knot -> Ctx -> FamilyStyle -> [LHsDecl GhcPs] -> Doc
+ Tilia.Render.Context: [knotDecls] :: Knot -> Ctx -> [LHsDecl GhcPs] -> Doc
- Tilia.Render.Context: writtenHaddock :: Ctx -> Maybe Span -> Maybe (NonEmpty Text)
+ Tilia.Render.Context: writtenHaddock :: Ctx -> Maybe Span -> Maybe Comment
- Tilia.Render.Declaration: decls :: Ctx -> FamilyStyle -> [LHsDecl GhcPs] -> Doc
+ Tilia.Render.Declaration: decls :: Ctx -> [LHsDecl GhcPs] -> Doc
- Tilia.Render.Declaration: declsKeepingGroups :: Ctx -> FamilyStyle -> [LHsDecl GhcPs] -> Doc
+ Tilia.Render.Declaration: declsKeepingGroups :: Ctx -> [LHsDecl GhcPs] -> Doc
- Tilia.Render.Header: HeaderPragma :: Span -> Int -> Text -> Text -> HeaderPragma
+ Tilia.Render.Header: HeaderPragma :: Span -> Int -> Text -> Text -> Maybe (NonEmpty Text) -> HeaderPragma
- Tilia.Span: Span :: !Int -> !Int -> !Int -> !Int -> Span
+ Tilia.Span: Span :: Int -> Int -> Int -> Int -> Span
- Tilia.Span: [spanEndColumn] :: Span -> !Int
+ Tilia.Span: [spanEndColumn] :: Span -> Int
- Tilia.Span: [spanEndLine] :: Span -> !Int
+ Tilia.Span: [spanEndLine] :: Span -> Int
- Tilia.Span: [spanStartColumn] :: Span -> !Int
+ Tilia.Span: [spanStartColumn] :: Span -> Int
- Tilia.Span: [spanStartLine] :: Span -> !Int
+ Tilia.Span: [spanStartLine] :: Span -> Int
Files
- CHANGELOG.md +174/−0
- HACKING.md +185/−0
- bench/Main.hs +16/−6
- bench/Tilia/Bench/Cases.hs +3/−3
- bench/Tilia/Bench/Fixity.hs +1/−2
- bench/Tilia/Bench/Record.hs +5/−7
- bench/bench.record +92/−92
- corpora/hackage/hackage.manifest +109/−109
- corpora/vendored/declaration/class/empty-context-out.hs +10/−0
- corpora/vendored/declaration/class/empty-context.hs +10/−0
- corpora/vendored/declaration/data/empty-context-out.hs +13/−0
- corpora/vendored/declaration/data/empty-context.hs +13/−0
- corpora/vendored/declaration/value/typed-quote-written-across-lines-out.hs +25/−0
- corpora/vendored/declaration/value/typed-quote-written-across-lines.hs +18/−0
- corpora/vendored/import/sorting-the-prelude-under-rebindable-syntax-out.hs +11/−0
- corpora/vendored/import/sorting-the-prelude-under-rebindable-syntax.hs +11/−0
- corpora/vendored/other/comment-before-closing-quote-out.hs +3/−3
- corpora/vendored/other/comment-inside-a-language-pragma-out.hs +16/−0
- corpora/vendored/other/comment-inside-a-language-pragma.hs +15/−0
- corpora/vendored/other/cpp/conditional-from-a-where-clause-indented-with-a-tab-out.hs +21/−0
- corpora/vendored/other/cpp/conditional-from-a-where-clause-indented-with-a-tab.hs +21/−0
- corpora/vendored/other/cpp/macro-among-statements-out.hs +27/−0
- corpora/vendored/other/cpp/macro-among-statements.hs +27/−0
- corpora/vendored/other/cpp/macro-between-equations-out.hs +12/−0
- corpora/vendored/other/cpp/macro-between-equations.hs +12/−0
- corpora/vendored/other/cpp/macro-indented-with-a-tab-out.hs +22/−0
- corpora/vendored/other/cpp/macro-indented-with-a-tab.hs +20/−0
- corpora/vendored/other/cpp/preprocessor-turned-on-by-a-flag-out.hs +10/−0
- corpora/vendored/other/cpp/preprocessor-turned-on-by-a-flag.hs +9/−0
- corpora/vendored/other/edition-named-by-the-module-out.hs +10/−0
- corpora/vendored/other/edition-named-by-the-module.hs +9/−0
- corpora/vendored/other/editions-in-written-order-out.hs +8/−0
- corpora/vendored/other/editions-in-written-order.hs +7/−0
- corpora/vendored/other/extension-taken-back-from-the-edition-out.hs +1/−1
- corpora/vendored/other/extension-turned-off-above-one-implying-it-out.hs +9/−0
- corpora/vendored/other/extension-turned-off-above-one-implying-it.hs +8/−0
- corpora/vendored/other/extension-turned-off-and-on-again-out.hs +10/−0
- corpora/vendored/other/extension-turned-off-and-on-again.hs +9/−0
- corpora/vendored/other/extension-turned-on-under-an-option-turning-it-off-out.hs +11/−0
- corpora/vendored/other/extension-turned-on-under-an-option-turning-it-off.hs +10/−0
- corpora/vendored/other/extensions-turned-on-by-flags-out.hs +15/−0
- corpora/vendored/other/extensions-turned-on-by-flags.hs +14/−0
- corpora/vendored/other/old-options-pragma-in-written-order-out.hs +13/−0
- corpora/vendored/other/old-options-pragma-in-written-order.hs +12/−0
- corpora/vendored/other/options-in-written-order-out.hs +4/−2
- corpora/vendored/other/options-in-written-order.hs +3/−2
- corpora/vendored/other/pragma-below-the-module-line-out.hs +8/−0
- corpora/vendored/other/pragma-below-the-module-line.hs +7/−0
- corpora/vendored/other/prelude-asked-for-above-rebindable-syntax-out.hs +12/−0
- corpora/vendored/other/prelude-asked-for-above-rebindable-syntax.hs +11/−0
- corpora/vendored/other/splices-the-author-grouped-out.hs +14/−0
- corpora/vendored/other/splices-the-author-grouped.hs +14/−0
- corpora/vendored/other/string-literals-as-written-out.hs +41/−0
- corpora/vendored/other/string-literals-as-written.hs +37/−0
- src/Tilia/Cabal/Package.hs +126/−80
- src/Tilia/Cabal/Plan.hs +87/−72
- src/Tilia/Cabal/Project.hs +19/−11
- src/Tilia/Cabal/Target.hs +29/−59
- src/Tilia/Comments.hs +27/−36
- src/Tilia/Comments/Attach.hs +0/−1
- src/Tilia/Cpp.hs +2/−3
- src/Tilia/Cpp/Directives.hs +29/−35
- src/Tilia/Cpp/Macros.hs +2/−0
- src/Tilia/Cpp/Merge.hs +11/−14
- src/Tilia/Cpp/Place.hs +48/−34
- src/Tilia/Diff.hs +2/−1
- src/Tilia/Doc.hs +1/−6
- src/Tilia/Doc/Body.hs +2/−1
- src/Tilia/Doc/Combinators.hs +7/−18
- src/Tilia/Doc/Internal.hs +3/−3
- src/Tilia/Equivalence.hs +76/−8
- src/Tilia/Fixity.hs +33/−54
- src/Tilia/Fixity/Archive.hs +11/−9
- src/Tilia/Fixity/Cabal.hs +25/−75
- src/Tilia/Fixity/Cache.hs +5/−9
- src/Tilia/Fixity/Debug.hs +7/−13
- src/Tilia/Fixity/HiFile.hs +3/−2
- src/Tilia/Fixity/Interface.hs +44/−54
- src/Tilia/Fixity/PackageDb.hs +3/−17
- src/Tilia/Fixity/Plan.hs +60/−61
- src/Tilia/Format.hs +24/−16
- src/Tilia/Ignore.hs +28/−22
- src/Tilia/Imports.hs +0/−1
- src/Tilia/Parser.hs +34/−40
- src/Tilia/Pragma.hs +242/−48
- src/Tilia/Pragma/Ghc.hs +91/−0
- src/Tilia/Render/Class.hs +7/−11
- src/Tilia/Render/Context.hs +10/−17
- src/Tilia/Render/Data.hs +5/−7
- src/Tilia/Render/Declaration.hs +18/−36
- src/Tilia/Render/Expression.hs +47/−54
- src/Tilia/Render/Haddock.hs +15/−22
- src/Tilia/Render/Header.hs +57/−63
- src/Tilia/Render/Literal.hs +22/−5
- src/Tilia/Render/Pattern.hs +13/−20
- src/Tilia/Render/Pragma.hs +3/−2
- src/Tilia/Render/Signature.hs +7/−12
- src/Tilia/Render/Type.hs +13/−14
- src/Tilia/Run.hs +17/−19
- src/Tilia/Source.hs +2/−0
- src/Tilia/Source/Lines.hs +37/−0
- src/Tilia/Span.hs +0/−6
- src/Tilia/Span/Ghc.hs +9/−1
- src/Tilia/Utils.hs +95/−20
- tests/Tilia/Cabal/PackageSpec.hs +78/−14
- tests/Tilia/Cabal/ProjectSpec.hs +11/−25
- tests/Tilia/Cabal/TargetSpec.hs +86/−10
- tests/Tilia/CommentsSpec.hs +13/−5
- tests/Tilia/Corpus.hs +66/−165
- tests/Tilia/Corpus/Manifest.hs +11/−20
- tests/Tilia/CorpusSpec.hs +28/−9
- tests/Tilia/Cpp/PropertiesSpec.hs +1/−6
- tests/Tilia/CppSpec.hs +35/−9
- tests/Tilia/Doc/InternalSpec.hs +1/−13
- tests/Tilia/Doc/PropertiesSpec.hs +0/−15
- tests/Tilia/EditorSpec.hs +29/−50
- tests/Tilia/EquivalenceSpec.hs +77/−0
- tests/Tilia/Fixity/CabalSpec.hs +92/−14
- tests/Tilia/Fixity/CacheSpec.hs +9/−1
- tests/Tilia/Fixity/DebugSpec.hs +28/−9
- tests/Tilia/Fixity/DependenciesSpec.hs +2/−17
- tests/Tilia/Fixity/HiFileSpec.hs +4/−2
- tests/Tilia/Fixity/InterfaceSpec.hs +31/−8
- tests/Tilia/Fixity/PlanSpec.hs +145/−61
- tests/Tilia/FixitySpec.hs +21/−1
- tests/Tilia/IgnoreSpec.hs +16/−13
- tests/Tilia/PragmaSpec.hs +210/−21
- tests/Tilia/RunSpec.hs +9/−4
- tests/Tilia/SpanSpec.hs +33/−0
- tests/Tilia/TestConfig.hs +5/−7
- tests/Tilia/TestUtils.hs +150/−19
- tilia.cabal +20/−18
CHANGELOG.md view
@@ -1,3 +1,177 @@+## Tilia 0.1.3.0++* An empty context comes out as written in a class or a data declaration,+ as in `class () => C a`, and before a constructor, as in+ `forall a. () => C a`, rather than being dropped, as it was already kept+ in a signature and in a GADT constructor.+ [Issue 299](https://github.com/mrkkrp/tilia/issues/299).+* Extensions turned on or off with flags in an `OPTIONS_GHC` pragma, or in+ the older `OPTIONS` pragma, such as `-cpp`, `-XLambdaCase`,+ `-fglasgow-exts` and `-fbang-patterns`, are taken into account, so a+ module that needs them is parsed as GHC parses it.+ [Issue 280](https://github.com/mrkkrp/tilia/issues/280).+* An `OPTIONS` pragma in the header is sorted with the `OPTIONS_GHC`+ pragmas, keeping the order the two are written in among each other, rather+ than being left where it was written.+ [PR 319](https://github.com/mrkkrp/tilia/pull/319).+* An extension that turns another off, as `RebindableSyntax` turns off+ `ImplicitPrelude`, does so where it is turned on, as with GHC, so under+ `RebindableSyntax` the Prelude is not taken to be imported, and an import+ of it sorts among the others, as under `NoImplicitPrelude`.+ [PR 319](https://github.com/mrkkrp/tilia/pull/319).+* The pragmas of a header keep the order they are written in where sorting+ them would leave other extensions in force, as with `NoMonoLocalBinds`+ written above `TypeFamilies`, which turns `MonoLocalBinds` back on, or+ with an option turning off an extension a later `LANGUAGE` pragma turns+ on; and editions, of which GHC takes the last, keep their order among+ each other. [PR 319](https://github.com/mrkkrp/tilia/pull/319).+* An options pragma written twice in a row is kept twice rather than+ written once, since a flag such as `-fplugin-opt` counts each time it is+ given. [PR 322](https://github.com/mrkkrp/tilia/pull/322).+* A pragma written below the `module` line, which GHC does not read, stays+ where it is written rather than being moved above the line, where it+ takes effect, and the module is parsed without what it names, as GHC+ parses it. [Issue 288](https://github.com/mrkkrp/tilia/issues/288).+* `--check-ast` reads the pragmas of the header as GHC reads them, and+ reports a module whose formatting would change the order of the flags of+ its options, its `OPTIONS_HADDOCK` pragmas, or how GHC's own flag reader+ takes them to switch extensions, the edition or the Safe Haskell mode.+ [PR 322](https://github.com/mrkkrp/tilia/pull/322).+* An edition of the language a module names in its header, as in+ `{-# LANGUAGE GHC2024 #-}` or with `-XGHC2024`, takes the place of the+ `default-language` of its component, as with GHC, and what+ `default-extensions` and the module's pragmas switch is switched on top+ of it. [Issue 321](https://github.com/mrkkrp/tilia/issues/321).+* An extension turned off after one that implies it, as `NoExplicitForAll`+ after `RankNTypes`, stays off when the module is parsed, as with GHC,+ rather than being turned back on.+ [PR 323](https://github.com/mrkkrp/tilia/pull/323).+* The `LANGUAGE` pragmas of a header are sorted by name after the+ editions, rather than with the names that turn an extension off, and then+ `ImplicitPrelude` and `CUSKs`, after the rest; where sorting them would+ change what is in force, they keep the order they are written in, as+ before. [PR 323](https://github.com/mrkkrp/tilia/pull/323).+* A `LANGUAGE` pragma holding a comment, as in `, PolyKinds -- for T` on+ one of its lines, is read past the comment, as GHC reads it, and kept as+ written, rather than taking the name and the comment together for a name+ and putting the comment before the `#-}` of a pragma of its own, which GHC+ could not read. [Issue 324](https://github.com/mrkkrp/tilia/issues/324).+* `--debug-fixity` says that the imports disagree about a constructor+ written among types, as a promoted constructor is, where a run declines+ the module over it, rather than showing its fixity settled.+ [Issue 304](https://github.com/mrkkrp/tilia/issues/304).+* `.tiliaignore` files and the cache are read and written as UTF-8 whatever+ the locale, so under `LANG=C` a `.tiliaignore` holding a character+ outside ASCII no longer makes `check` and `inplace` crash, nor+ `for-editor` format what it ignores, and what the cache holds about an+ operator or a path with such a character is no longer worked out again on+ every run. The cache starts anew, so the first run after upgrading works+ everything out again. [Issue 290](https://github.com/mrkkrp/tilia/issues/290).+* Where the plan gives no hash to check a dependency's tarball against, a+ tarball that is not gzip, or is cut short, leaves its package unread, so+ that a module needing its fixities is declined, rather than ending the+ run. [Issue 303](https://github.com/mrkkrp/tilia/issues/303).+* The body of a typed Template Haskell quote written over several lines, as+ in `[|| … ||]`, is indented under its opening bracket together with its+ closing one, as that of an untyped quote is, rather than coming out at+ the column of the brackets.+ [Issue 300](https://github.com/mrkkrp/tilia/issues/300).+* A name an as-pattern binds at the top level, as `plus` in+ `plus@_ = (+)`, is taken for the module's own, rather than a module using+ it being declined where an import could not be read; a name bound inside+ the function of a view pattern, on the other hand, is not.+ [Issue 301](https://github.com/mrkkrp/tilia/issues/301).+* A module that re-exports the members of a type without the type, such as+ the methods of a class named by an operator, is no longer taken to declare+ the type's fixity, and a name a decoded interface file re-exports takes+ the fixity it has in the namespace it is exported in, as a type or as a+ term, rather than in both.+ [Issue 302](https://github.com/mrkkrp/tilia/issues/302).+* A conditional that begins among the bindings of a `where` clause and goes+ on into the declarations after it stays whole where the bindings are+ indented with a tab, as it already did where they are indented with+ spaces, rather than coming out in two.+ [Issue 297](https://github.com/mrkkrp/tilia/issues/297).+* A line that holds nothing but a use of a function-like macro and is+ indented with a tab is taken to be at the column the tab moves it to, as+ GHC takes it, rather than at the count of characters before it. One at the+ column of the code under it is put back as written, rather than the module+ being declined where the use does not read as code, and one further in is+ read as code that carries on the line above, rather than put back at the+ level of the code under it.+ [PR 333](https://github.com/mrkkrp/tilia/pull/333).+* A sub-library is looked for in the build plan under `lib:` and its name,+ as the plan writes it, rather than as the main library, so a package+ whose only library is a named one no longer makes the first run under a+ plan `cabal` solved ask it to solve again, and a sub-library the plan+ leaves out no longer counts as planned where the main library is.+ [Issue 296](https://github.com/mrkkrp/tilia/issues/296).+* A source directory that two packages name through different paths, as+ `shared` in one and `../a/shared` in the other, is taken to be one+ directory, so `check` and `inplace` format each module in it once, rather+ than once under each path.+ [Issue 295](https://github.com/mrkkrp/tilia/issues/295).+* A module that no `.cabal` file above it names, as one in a directory+ outside every package that packages of the project share through `..`,+ takes what a package of the project that names it puts in force, rather+ than failing for want of a `.cabal` file, and the fixities it declares+ are known to the modules importing it, rather than their being declined.+ [Issue 295](https://github.com/mrkkrp/tilia/issues/295).+* A character literal holding a double quote, as in `'"'`, is read as one,+ rather than as the start of a string, so a `{-# LINE #-}` written in a+ string after it no longer has the module declined, and one written in the+ code after it is no longer missed.+ [Issue 292](https://github.com/mrkkrp/tilia/issues/292).+* A line that holds nothing but a use of a function-like macro is put back+ at the indentation of the code under it, as it is written, rather than a+ step further in wherever what is around it begins on a line above. One+ among the statements of a `do` block, or between the equations of a+ function, no longer comes out carrying on the line above it.+ [Issue 279](https://github.com/mrkkrp/tilia/issues/279).+* A file whose formatting throws an exception is reported as failing, with+ the exception, rather than the exception ending the run with "thread+ blocked indefinitely in an MVar operation" and leaving every other file+ unwritten and unreported.+ [Issue 291](https://github.com/mrkkrp/tilia/issues/291).+* A dependency whose source is still missing after a download is no+ longer downloaded again on every run, nor is `cabal` asked twice where it+ keeps sources, which formatting on save paid for each time: what a+ download does not bring in is remembered under the plan, as it already+ was where the plan did not cover a component.+ [Issue 289](https://github.com/mrkkrp/tilia/issues/289).+* An entry of the `packages` field of a `cabal.project` that begins with+ dots or a slash, as `../lib`, `/abs/lib` or `.hidden`, is looked for where+ it points, as `cabal` looks for it, rather than under the project with the+ dots and the slash taken off, where it was usually not found and passed+ over. [Issue 294](https://github.com/mrkkrp/tilia/issues/294).+* A string literal comes out as written wherever it is: its gaps line up+ under its opening quote in a pattern and in a `WARNING` or `DEPRECATED`+ message, as they already did in an expression, a rule's name is no longer+ escaped again, as `"f/∘"` was into `"f/\8728"`, and an `SCC` label+ written without quotes no longer gains them.+ [PR 344](https://github.com/mrkkrp/tilia/pull/344).+* Top-level splices written one after another stay together across a+ comment or a preprocessor line between them, as standalone `deriving`+ declarations already did, rather than having blank lines put between+ them. [PR 344](https://github.com/mrkkrp/tilia/pull/344).+* An interrupt arriving while a package or a file is read stops the run,+ rather than being taken for that read failing.+ [PR 344](https://github.com/mrkkrp/tilia/pull/344).+* A run reads the build plan once where nothing is missing, and the sources+ of each dependency and the `.cabal` file of each package of the project+ once rather than once per component.+ [PR 344](https://github.com/mrkkrp/tilia/pull/344).+* A package declaring `cabal-version: 3.14`, `3.16` or `3.18` can be+ formatted, as `.cabal` files are read with Cabal-syntax 3.18 whichever+ compiler builds Tilia, where the release binaries, built with GHC 9.10.3+ and so Cabal-syntax 3.12, said its `.cabal` file did not parse. The+ release binaries are now built with GHC 9.14.1.+ [PR 345](https://github.com/mrkkrp/tilia/pull/345).+* The `.cabal` file of a dependency is read with Cabal-syntax, as the+ project's own are, rather than line by line, so a line of its description+ that begins with the name of a field is no longer taken for that field.+ [PR 345](https://github.com/mrkkrp/tilia/pull/345).+ ## Tilia 0.1.2.0 * A comment written beside the `..` of an item of an import or export list,
+ HACKING.md view
@@ -0,0 +1,185 @@+# Hacking++Enter the development shell by either running `direnv allow` or `nix+develop`. Once in the shell, the development is ordinary Cabal:++```console+$ cabal build+$ cabal test+```++All tests are in one test suite and there are a fair number of them. On my+machine the full test suite passes in 140 seconds, but it may be different+for you, so isolating a subset of the test suite may be helpful:++```console+$ cabal test --test-options='--match "Tilia.Fixity"'+```++The test suite will perform downloads the first time you run it and so it+will be a bit slower on that run. It needs various corpora, such as Hackage+packages and GHC's own test suite, which are not checked into this+repository.++The Hackage corpus is exercised in order to ensure that every module+formats, that its AST is preserved, and that formatting it is idempotent.+The results are recorded in `corpora/hackage/hackage.manifest`. Next to it,+`hackage.report` explains the failing cases. The manifest and the report can+be updated like this:++```console+$ TILIA_CORPUS_ACCEPT=1 cabal test+```++Finally, Tilia formats itself, so make sure to run this command before you+open a PR:++```console+$ nix run .#format+```++## Benchmarks++The benchmarks format a fixed sample of the Hackage corpus, check what most+of its modules are printed as the way `--check-ast` does, and format a few+modules made to stress one part of the formatter each. They also work out+fixities the ways a run does: they read every module of a dozen packages of+the corpus out of their tarballs, as a run with a cold cache does, read the+same out of a cache, as a warm run does, and decode the interfaces of a few+packages the compiler ships with. They take about 45 seconds:++```console+$ cabal bench+```++What each benchmark allocates and the instructions it retires are the same+from one run to the next to a hundredth of a percent, however busy the+machine is, so they are kept in `bench/bench.record` and checked like the+corpus is. The run fails where a benchmark allocates or retires 0.5% more+or less than the record says, or where all the benchmarks of a stage+together do 0.05% more or less. Update the record like this, and review the+change to it like any other:++```console+$ TILIA_BENCH_ACCEPT=1 cabal bench+```++What is allocated depends on the compiler, so the record is made with the+base compiler, and CI checks it there. The instructions depend on the+processor as well, so they are checked only on the processor the record+names, where Linux lets a process count them; elsewhere the run says so and+checks the rest. What decoding the interfaces costs depends on what they+hold, which can differ between builds of one compiler, so the record keeps a+digest of their names and sizes, and decoding is checked only where it+matches; the bytes alone differ between two builds in fingerprints that cost+nothing to decode. The benchmarks run with `-A1g -O1g -C1000`, in about 1.2+GiB, so that few collections fall inside one, and no major one or context+switch: the collector's own instructions depend on where in the work it+runs, which any change to what is allocated moves, and a major collection or+a context switch makes the counts depend on the rest of the heap and on the+clock. The benchmarks of one module or package run on their own with+`--match`, and updating the record then changes only their lines:++```console+$ cabal bench --benchmark-options='--match QuickCheck'+```++To compare two versions on another processor, save what one measures and+compare the other with it, which shows the instructions and the time of+all the benchmarks together and the benchmarks that moved most:++```console+$ cabal bench --benchmark-options='--save /tmp/before'+$ cabal bench --benchmark-options='--baseline /tmp/before'+```++Time is not recorded: on a quiet machine the time of all the benchmarks+together moves by about 1% from one run to the next, and the time of one of+them by up to 13%, even with `--runs 3`, which measures each three times and+takes the fastest. On a busy machine it moves by far more.++## Adding a GHC version++Tilia is built and tested with every compiler in the `compilers` list in+`flake.nix`. To add a major version of GHC:++1. Add it to `compilers` in `flake.nix`, where the list goes from the oldest+ version to the newest. If `haskell.nix` does not know the compiler yet,+ update it first with `nix flake update haskellNix`. This also gives the+ compiler a development shell, such as `nix develop .#ghc9161`.+2. Add it to the `build` matrix in `.github/workflows/ci.yaml` and number+ the shards again, so that with `n` entries each takes its own `i/n`. The+ test groups named in `compilerBound` in `tests/Main.hs` run on every+ entry, and the rest of the suite is split between them.+3. Add it to `tested-with` in `tilia.cabal`, and widen the bounds for the+ new versions of boot libraries. Bump `index-state` in `cabal.project` if+ the plan needs newer releases from Hackage.+4. Generate the table of names that the series writes into interface files+ as keys, with the new compiler:++ ```console+ $ ./generate-known-keys.py --ghc /path/to/ghc-9.16.1/bin/ghc+ ```++ This writes `src/Tilia/Fixity/KnownKeys/GhcNNN.hs`, which goes into+ `exposed-modules` in `tilia.cabal`.+5. Teach `Tilia.Fixity.HiFile` to read the interface files of the series: a+ new `Series` constructor, its version in `hiFile`, its table in+ `keyedNames`, its `primops` in `primopFixities`, and whatever the series+ changed in the layout that `hiFile`, `payload`, `pointer`, and `fixity`+ read. GHC's `GHC.Iface.Binary` and `GHC.Unit.Module.ModIface`, compared+ between the two series, show what changed. Tilia passes interface it+ cannot decode to `ghc --show-iface`, which works but costs a process per+ interface. The tests fail until every interface of the project's+ dependencies decodes and agrees with `ghc --show-iface` where the two are+ compared:++ ```console+ $ nix develop .#ghc9161 -c cabal test --test-options='--match "Tilia.Fixity"'+ ```++6. Update `CHANGELOG.md`.++`baseCompiler` in `flake.nix` names the base compiler, the newest of+`compilers`: it builds the release binaries, runs weeder, the self-format+checks and the benchmarks, and provides the default development shell. When+it changes, update `GHC_VERSION` in `.github/workflows/release.yaml` to+match, as well as the `restore-keys` of the `format` job and the compiler+of the `bench` job in `ci.yaml`, and record the benchmarks again with the+new base compiler.++## Switching `ghc-lib-parser`++To switch to another version of `ghc-lib-parser`:++1. Change the bounds on `ghc-lib-parser` in the stanza of that name in+ `tilia.cabal`, and bump `index-state` in `cabal.project` past its+ release. If `haskell.nix`'s index of Hackage does not reach that+ far, update the `haskellNix` input as well.+2. Fix what the new API breaks. Warnings are errors in this project, so a+ constructor that the new syntax tree adds and the printer does not handle+ shows up as an incomplete match. Add new syntax examples in+ `corpora/vendored/`.+3. Point `ghcTestSuite` in `tests/Tilia/Corpus.hs` at the test suite of the+ GHC release the parser comes from: its `corpusName` and the+ `ghc-X.Y.Z-release` tag it is fetched from. Then revise `ghcUnreadable`,+ the files GHC's own parser rejects, and `ghcDeclined`, the files Tilia is+ right to refuse, against that test suite.+4. Regenerate `tests/Tilia/BootFixities.hs`, the fixities the tests take the+ boot packages to export, with the compiler of the same release, after+ checking that `PACKAGES` in the script still names its boot packages:++ ```console+ $ ./generate-boot-fixities.py --ghc /path/to/ghc-9.16.1/bin/ghc+ ```++5. Record the Hackage corpus again with `TILIA_CORPUS_ACCEPT=1 cabal test`+ and review every change to `hackage.manifest` and `hackage.report`: a+ module that changes its outcome or its digest is now parsed differently.+6. The cache keeps summaries of local modules by the version of+ `ghc-lib-parser` that read them, but what it learned about dependencies+ is kept regardless of it. If the new parser reads the sources of+ dependencies differently, bump `formatVersion` in `Tilia.Fixity.Cache`.+7. Record the benchmarks again with `TILIA_BENCH_ACCEPT=1 cabal bench`,+ since parsing takes a share of what they cost.+8. Update `CHANGELOG.md`.
bench/Main.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-} -- | Measure what formatting a fixed sample of modules costs, and compare -- what it allocates and the instructions it retires with the record kept@@ -9,7 +8,8 @@ import Control.Applicative ((<|>)) import Control.Exception (SomeException, try)-import Control.Monad (join, mfilter, unless)+import Control.Monad (join, mfilter, unless, when)+import Data.ByteString qualified as BS import Data.Containers.ListUtils (nubOrd) import Data.Foldable (for_) import Data.Int (Int64)@@ -20,6 +20,7 @@ import Data.Ord (Down (..)) import Data.Text (Text) import Data.Text qualified as T+import Data.Text.Encoding qualified as T import Data.Text.IO qualified as T import Data.Traversable (for) import Data.Version (showVersion)@@ -42,7 +43,6 @@ strOption, value, )-import System.Environment (lookupEnv) import System.Exit (die, exitFailure) import System.IO (BufferMode (..), hSetBuffering, stdout) import System.Info (fullCompilerVersion)@@ -53,6 +53,8 @@ import Tilia.Bench.Measure import Tilia.Bench.Record import Tilia.Corpus (hackagePackages, obtain)+import Tilia.TestUtils (switchedOn)+import Tilia.Utils (writeUtf8File) -- | How the benchmarks were asked to run. data Options = Options@@ -79,7 +81,7 @@ main = do hSetBuffering stdout LineBuffering options <- execParser optionsInfo- accepting <- (== Just "1") <$> lookupEnv "TILIA_BENCH_ACCEPT"+ accepting <- switchedOn "TILIA_BENCH_ACCEPT" examples <- obtain hackagePackages >>= either (die . T.unpack) pure sampled <- corpusSubjects examples >>= either (die . T.unpack) pure let matching name = maybe True (`T.isInfixOf` name) (optMatch options)@@ -108,6 +110,10 @@ ended <- withFixityBenchmarks $ \(resolving, interfaces) -> do let record = comparable processor interfaces written decoding = recordInterfaces written == interfaces+ reheaded =+ recordCompiler written /= compiler+ || recordProcessor written /= fromMaybe "none" processor+ || not decoding checked (stage, _) = decoding || stage /= stageName Decode others <- for [b | b <- resolving, matching (benchmarkName b)] $ \b -> do (_, m) <- measureIO counter (optRuns options) (benchmarkRun b) id@@ -121,6 +127,10 @@ for_ baseline (summarize measured) if accepting then do+ when (partial && reheaded) $+ die+ "Only some benchmarks were run, on another compiler, processor\n\+ \or set of interfaces than the record's: accept them all at once." let entryOf k m = steadied (Map.lookup k (recordEntries record)) $ Entry@@ -428,7 +438,7 @@ -- | Save what was measured. writeBaseline :: FilePath -> [(Key, Measurement)] -> IO () writeBaseline path measured =- T.writeFile path . T.unlines $+ writeUtf8File path . T.unlines $ [ T.unwords [ stage, T.pack (show (measuredTime m)),@@ -441,7 +451,7 @@ -- | Read what 'writeBaseline' saved. readBaseline :: FilePath -> IO Baseline readBaseline path = do- text <- T.readFile path+ text <- T.decodeUtf8Lenient <$> BS.readFile path pure $ Map.fromList [ ((stage, T.unwords rest), (t, readMaybe (T.unpack retired)))
bench/Tilia/Bench/Cases.hs view
@@ -20,11 +20,11 @@ import Data.Text qualified as T import Data.Text.Encoding (decodeUtf8') import Data.Traversable (for)-import GHC.LanguageExtensions.Type (Extension) import Tilia.Corpus (Example (..)) import Tilia.Cpp (usesCpp) import Tilia.Format (rewritten) import Tilia.Parser (parseModule)+import Tilia.Pragma (Defaults) import Tilia.TestConfig (formatCppExample, printExample) import Tilia.TestUtils (effectiveExtensionsFor, parserConfigFor) @@ -203,7 +203,7 @@ -- | A module to format, and to check what that printed where it is -- checked.-subjectOf :: Maybe [Extension] -> Bool -> Text -> Text -> Subject+subjectOf :: Maybe Defaults -> Bool -> Text -> Text -> Subject subjectOf package isChecked name source = Subject { subjectName = name,@@ -224,7 +224,7 @@ -- | What formatting a module prints, as the corpus does it, or 'Nothing' -- where it is declined.-formatted :: Maybe [Extension] -> FilePath -> Text -> Maybe Text+formatted :: Maybe Defaults -> FilePath -> Text -> Maybe Text formatted package path source | usesCpp (effectiveExtensionsFor package source) source = either (const Nothing) Just (formatCppExample package path source)
bench/Tilia/Bench/Fixity.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-}@@ -209,7 +208,7 @@ cache <- cacheUnder (dir </> "cache") interfaces <- interfacesOf decoded let reading kept modules = do- packageReader <- newPackageReader (plannedCompiler plan)+ packageReader <- newPackageReader (plannedCompiler plan) [] resolver <- newResolverWith [FromSource] kept [] tarballs packageReader plan pure $ do answers <- traverse (askModule resolver) modules
bench/Tilia/Bench/Record.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-} -- | What the benchmarks cost when they were last recorded, kept in the -- repository so that a change to it shows in review like any other.@@ -12,15 +11,14 @@ ) where -import Control.Exception (SomeException, try) import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map import Data.Maybe (mapMaybe) import Data.Text (Text) import Data.Text qualified as T-import Data.Text.IO qualified as T import Data.Word (Word64) import Text.Read (readMaybe)+import Tilia.Utils (readUtf8File, writeUtf8File) -- | The bytes each benchmark allocated and the instructions it retired, by -- stage and module.@@ -50,9 +48,9 @@ -- before it is regenerated each say they are not in it. readRecord :: FilePath -> IO Record readRecord path =- try (T.readFile path) >>= \case- Left (_ :: SomeException) -> pure (Record "" "none" "" Map.empty)- Right text ->+ readUtf8File path >>= \case+ Nothing -> pure (Record "" "none" "" Map.empty)+ Just text -> pure Record { recordCompiler = field "compiler " text,@@ -72,7 +70,7 @@ -- shows what changed rather than what moved. writeRecord :: FilePath -> Record -> IO () writeRecord path record =- T.writeFile path . T.unlines $+ writeUtf8File path . T.unlines $ [ "# What each benchmark costs, as far as that does not depend on how", "# busy the machine is. `cabal bench` checks it, and", "# `TILIA_BENCH_ACCEPT=1 cabal bench` writes it again.",
bench/bench.record view
@@ -24,98 +24,98 @@ # they were not counted. # benchmark The module or the package it measures that on. -compiler ghc 9.10.3+compiler ghc 9.14.1 processor AMD Ryzen AI 9 HX 370 w/ Radeon 890M-interfaces 1c28e35fa2432d89+interfaces 78bbcc544104eadc # stage allocated instructions benchmark-check 272354648 575981358 Agda-2.8.0/src/full/Agda/Syntax/Translation/ConcreteToAbstract.hs-check 411823456 855523727 ShellCheck-0.11.0/src/ShellCheck/Analytics.hs-check 260497296 461063479 aeson-2.3.1.0/src/Data/Aeson/KeyMap.hs-check 196550504 511610069 blaze-html-0.9.2.0/src/Text/Blaze/Html5/Attributes.hs-check 63623696 127394098 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Transformations/Alt.hs-check 84949968 171830970 distributed-process-0.7.8/src/Control/Distributed/Process/Node.hs-check 127362696 266504214 esqueleto-3.6.0.3/test/PostgreSQL/Test.hs-check 506858784 923788756 haxl-2.5.1.1/Haxl/Core/Monad.hs-check 215914784 451467945 idris-1.3.4/src/Idris/Elab/Term.hs-check 333601000 572292120 lifted-base-0.2.3.12/Control/Exception/Lifted.hs-check 60171824 128139113 megaparsec-9.8.1/Text/Megaparsec/Internal.hs-check 81898560 173856514 optparse-applicative-0.19.0.0/tests/test.hs-check 51558504 90521497 pandoc-3.10.2/src/Text/Pandoc/Class/PandocPure.hs-check 160564576 336134147 pandoc-3.10.2/src/Text/Pandoc/Readers/Markdown.hs-check 217005264 438646778 pandoc-3.10.2/src/Text/Pandoc/Writers/Powerpoint/Output.hs-check 177263168 360271324 purescript-0.15.15/src/Language/PureScript/Errors.hs-check 86199456 182395610 purescript-0.15.15/src/Language/PureScript/TypeChecker/Types.hs-check 76944368 174894002 recursion-schemes-5.2.3/src/Data/Functor/Foldable.hs-check 90285488 193297667 servant-server-0.20.3.0/src/Servant/Server/Internal.hs-check 99785848 200988911 swagger2-2.9.1/src/Data/Swagger/Internal.hs-check 104406992 193276384 text-2.1.4/tests/Tests/Properties/Folds.hs-check 62082528 128971807 time-1.16.0.1/test/main/Test/Format/ParseTime.hs-check 69973984 137412179 tls-2.4.3/Network/TLS/Extension.hs-check 322427128 758963691 unpacked-containers-0/src/Map/Internal.hs-check 438419048 846475201 vector-0.13.2.0/src/Data/Vector/Generic.hs-check 70657680 148642078 xmonad-0.18.1/src/XMonad/Operations.hs-decode 25613816 43571594 base-4.20.2.0-decode 5830408 7145065 bytestring-0.12.2.0-decode 9126328 11777525 containers-0.7-decode 42252744 55499725 ghc-internal-9.1003.0-decode 11189560 15486875 template-haskell-2.22.0.0-decode 6097120 6605095 text-2.1.3-format 587068744 1136108572 Agda-2.8.0/src/full/Agda/Syntax/Translation/ConcreteToAbstract.hs-format 4060865072 8041341103 QuickCheck-2.18.0.0/src/Test/QuickCheck/Arbitrary.hs-format 907373856 1723990920 ShellCheck-0.11.0/src/ShellCheck/Analytics.hs-format 330450600 571670315 aeson-2.3.1.0/src/Data/Aeson/KeyMap.hs-format 175030840 326850369 blaze-html-0.9.2.0/src/Text/Blaze/Html5/Attributes.hs-format 125920744 239525469 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Transformations/Alt.hs-format 182428296 345883719 distributed-process-0.7.8/src/Control/Distributed/Process/Node.hs-format 313363824 590647912 esqueleto-3.6.0.3/test/PostgreSQL/Test.hs-format 773029152 1543414928 haxl-2.5.1.1/Haxl/Core/Monad.hs-format 505953528 965039048 idris-1.3.4/src/Idris/Elab/Term.hs-format 1340529160 2711040392 lens-5.3.6/src/Control/Lens/Wrapped.hs-format 403596632 737287954 lifted-base-0.2.3.12/Control/Exception/Lifted.hs-format 115350240 221131009 megaparsec-9.8.1/Text/Megaparsec/Internal.hs-format 152138968 278467498 optparse-applicative-0.19.0.0/tests/test.hs-format 54554184 96700081 pandoc-3.10.2/src/Text/Pandoc/Class/PandocPure.hs-format 377676272 719453674 pandoc-3.10.2/src/Text/Pandoc/Readers/Markdown.hs-format 466875216 881809887 pandoc-3.10.2/src/Text/Pandoc/Writers/Powerpoint/Output.hs-format 375024840 709592247 purescript-0.15.15/src/Language/PureScript/Errors.hs-format 157667592 293149746 purescript-0.15.15/src/Language/PureScript/TypeChecker/Types.hs-format 129432696 233813267 recursion-schemes-5.2.3/src/Data/Functor/Foldable.hs-format 173127584 323770122 servant-server-0.20.3.0/src/Servant/Server/Internal.hs-format 175020056 327970368 swagger2-2.9.1/src/Data/Swagger/Internal.hs-format 716528888 1507106872 synthetic/comments-format 107796160 237491710 synthetic/conditionals-format 77608272 114637794 synthetic/imports-format 99934472 207607344 synthetic/nesting-format 839857072 1653646287 text-2.1.4/src/Data/Text/Internal/Fusion/CaseMapping.hs-format 223534696 391051730 text-2.1.4/tests/Tests/Properties/Folds.hs-format 139006432 258193642 time-1.16.0.1/test/main/Test/Format/ParseTime.hs-format 139928120 263889581 tls-2.4.3/Network/TLS/Extension.hs-format 1012993016 1985300102 unordered-containers-0.2.21/Data/HashMap/Internal.hs-format 509562512 951647762 unpacked-containers-0/src/Map/Internal.hs-format 551911832 1043593184 vector-0.13.2.0/src/Data/Vector/Generic.hs-format 139491808 261133367 xmonad-0.18.1/src/XMonad/Operations.hs-recall 5154088 11631440 aeson-2.3.1.0-recall 2222200 5118396 conduit-1.3.6.1-recall 1709976 3278498 free-5.2-recall 11088696 34739875 lens-5.3.6-recall 1793600 3797411 megaparsec-9.8.1-recall 2129136 4316346 mtl-2.3.2-recall 1289808 2773580 optics-0.4.2.1-recall 1532472 3627916 pandoc-types-1.23.1.2-recall 2301760 4717796 semigroupoids-6.0.2-recall 3293336 6712858 servant-0.20.3.0-recall 5188496 9120737 text-2.1.4-recall 4061800 8698087 vector-0.13.2.0-resolve 1810656152 3180580388 aeson-2.3.1.0-resolve 435325168 738034252 conduit-1.3.6.1-resolve 471290256 797760851 free-5.2-resolve 2974846416 5176070738 lens-5.3.6-resolve 210109008 366801169 megaparsec-9.8.1-resolve 225682064 388972813 mtl-2.3.2-resolve 64290384 128790701 optics-0.4.2.1-resolve 77403992 142729351 pandoc-types-1.23.1.2-resolve 133167224 239714598 semigroupoids-6.0.2-resolve 213362072 390356497 servant-0.20.3.0-resolve 1159785776 1994326073 text-2.1.4-resolve 2408709680 4066724655 vector-0.13.2.0+check 262394328 561065716 Agda-2.8.0/src/full/Agda/Syntax/Translation/ConcreteToAbstract.hs+check 397446832 830954176 ShellCheck-0.11.0/src/ShellCheck/Analytics.hs+check 241124648 438396589 aeson-2.3.1.0/src/Data/Aeson/KeyMap.hs+check 189604912 503938397 blaze-html-0.9.2.0/src/Text/Blaze/Html5/Attributes.hs+check 61103592 123879818 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Transformations/Alt.hs+check 80904096 166165901 distributed-process-0.7.8/src/Control/Distributed/Process/Node.hs+check 122830480 257529681 esqueleto-3.6.0.3/test/PostgreSQL/Test.hs+check 494049016 907759201 haxl-2.5.1.1/Haxl/Core/Monad.hs+check 208630016 437290005 idris-1.3.4/src/Idris/Elab/Term.hs+check 316305056 548835398 lifted-base-0.2.3.12/Control/Exception/Lifted.hs+check 58863640 126561712 megaparsec-9.8.1/Text/Megaparsec/Internal.hs+check 78865104 169281790 optparse-applicative-0.19.0.0/tests/test.hs+check 49898856 88299550 pandoc-3.10.2/src/Text/Pandoc/Class/PandocPure.hs+check 155279368 326828232 pandoc-3.10.2/src/Text/Pandoc/Readers/Markdown.hs+check 208221512 425543616 pandoc-3.10.2/src/Text/Pandoc/Writers/Powerpoint/Output.hs+check 171071104 351249427 purescript-0.15.15/src/Language/PureScript/Errors.hs+check 83717864 177741049 purescript-0.15.15/src/Language/PureScript/TypeChecker/Types.hs+check 75851168 173224739 recursion-schemes-5.2.3/src/Data/Functor/Foldable.hs+check 87866232 190347605 servant-server-0.20.3.0/src/Servant/Server/Internal.hs+check 96614904 199151954 swagger2-2.9.1/src/Data/Swagger/Internal.hs+check 100392680 186829756 text-2.1.4/tests/Tests/Properties/Folds.hs+check 59225512 125146433 time-1.16.0.1/test/main/Test/Format/ParseTime.hs+check 65691176 132601013 tls-2.4.3/Network/TLS/Extension.hs+check 316686328 745977417 unpacked-containers-0/src/Map/Internal.hs+check 429238096 834183400 vector-0.13.2.0/src/Data/Vector/Generic.hs+check 68487400 145286254 xmonad-0.18.1/src/XMonad/Operations.hs+decode 25986560 42986706 base-4.22.0.0+decode 6162184 7405780 bytestring-0.12.2.0+decode 9617160 11823599 containers-0.8+decode 60341752 75721656 ghc-internal-9.1401.0+decode 5048664 9492955 template-haskell-2.24.0.0+decode 6427528 6910066 text-2.1.3+format 556566968 1040003316 Agda-2.8.0/src/full/Agda/Syntax/Translation/ConcreteToAbstract.hs+format 3948115280 7939983547 QuickCheck-2.18.0.0/src/Test/QuickCheck/Arbitrary.hs+format 863564728 1573781326 ShellCheck-0.11.0/src/ShellCheck/Analytics.hs+format 303951416 534115111 aeson-2.3.1.0/src/Data/Aeson/KeyMap.hs+format 164705632 309258425 blaze-html-0.9.2.0/src/Text/Blaze/Html5/Attributes.hs+format 114058024 212152157 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Transformations/Alt.hs+format 170229520 316697956 distributed-process-0.7.8/src/Control/Distributed/Process/Node.hs+format 297570528 541006283 esqueleto-3.6.0.3/test/PostgreSQL/Test.hs+format 759077968 1520521231 haxl-2.5.1.1/Haxl/Core/Monad.hs+format 483356504 878928957 idris-1.3.4/src/Idris/Elab/Term.hs+format 1320092680 2678994116 lens-5.3.6/src/Control/Lens/Wrapped.hs+format 372668480 696092011 lifted-base-0.2.3.12/Control/Exception/Lifted.hs+format 110834768 209454593 megaparsec-9.8.1/Text/Megaparsec/Internal.hs+format 143959296 262866520 optparse-applicative-0.19.0.0/tests/test.hs+format 52995496 94413105 pandoc-3.10.2/src/Text/Pandoc/Class/PandocPure.hs+format 361638296 659596851 pandoc-3.10.2/src/Text/Pandoc/Readers/Markdown.hs+format 443949808 814179116 pandoc-3.10.2/src/Text/Pandoc/Writers/Powerpoint/Output.hs+format 357025320 656146525 purescript-0.15.15/src/Language/PureScript/Errors.hs+format 150005992 275935657 purescript-0.15.15/src/Language/PureScript/TypeChecker/Types.hs+format 122875928 216641468 recursion-schemes-5.2.3/src/Data/Functor/Foldable.hs+format 164938072 301655910 servant-server-0.20.3.0/src/Servant/Server/Internal.hs+format 167528512 311088065 swagger2-2.9.1/src/Data/Swagger/Internal.hs+format 648895256 1363324909 synthetic/comments+format 105358360 225585007 synthetic/conditionals+format 76791456 114759701 synthetic/imports+format 97076928 207279778 synthetic/nesting+format 793484632 1508895855 text-2.1.4/src/Data/Text/Internal/Fusion/CaseMapping.hs+format 216925176 379840605 text-2.1.4/tests/Tests/Properties/Folds.hs+format 125519664 227671534 time-1.16.0.1/test/main/Test/Format/ParseTime.hs+format 128974056 241642635 tls-2.4.3/Network/TLS/Extension.hs+format 985060280 1912068703 unordered-containers-0.2.21/Data/HashMap/Internal.hs+format 486293072 872473350 unpacked-containers-0/src/Map/Internal.hs+format 533965872 1009710097 vector-0.13.2.0/src/Data/Vector/Generic.hs+format 132689048 241971753 xmonad-0.18.1/src/XMonad/Operations.hs+recall 5011864 9048673 aeson-2.3.1.0+recall 2205328 4115511 conduit-1.3.6.1+recall 1748312 2942852 free-5.2+recall 10404000 22672143 lens-5.3.6+recall 1825680 3287510 megaparsec-9.8.1+recall 2160552 3802105 mtl-2.3.2+recall 1351496 2492548 optics-0.4.2.1+recall 1559568 3026727 pandoc-types-1.23.1.2+recall 2337880 4125023 semigroupoids-6.0.2+recall 3239392 5595514 servant-0.20.3.0+recall 5102944 8218446 text-2.1.4+recall 3946288 7016254 vector-0.13.2.0+resolve 1711586824 2939731779 aeson-2.3.1.0+resolve 414482984 677964413 conduit-1.3.6.1+resolve 453525440 735173130 free-5.2+resolve 2764810360 4739854108 lens-5.3.6+resolve 199884528 326934357 megaparsec-9.8.1+resolve 209198760 338204339 mtl-2.3.2+resolve 63227560 99404301 optics-0.4.2.1+resolve 71461520 116712703 pandoc-types-1.23.1.2+resolve 124101824 202496534 semigroupoids-6.0.2+resolve 198377800 321014101 servant-0.20.3.0+resolve 1090795072 1819001479 text-2.1.4+resolve 2271139808 3767375431 vector-0.13.2.0
corpora/hackage/hackage.manifest view
@@ -482,7 +482,7 @@ formatted da3fb75adccf QuickCheck-2.18.0.0/tests/DiscardRatio.hs formatted 5408e5e13bd3 QuickCheck-2.18.0.0/tests/GCoArbitraryExample.hs formatted 8dd012261272 QuickCheck-2.18.0.0/tests/GShrinkExample.hs-formatted 3f82e6fd307d QuickCheck-2.18.0.0/tests/Generators.hs+formatted 5f4c2707cfc6 QuickCheck-2.18.0.0/tests/Generators.hs formatted 057887c409bc QuickCheck-2.18.0.0/tests/Misc.hs formatted 414efdc48918 QuickCheck-2.18.0.0/tests/MonadFix.hs formatted 85ad2d1decbe QuickCheck-2.18.0.0/tests/Monoids.hs@@ -516,7 +516,7 @@ formatted a381122da904 ShellCheck-0.11.0/src/ShellCheck/Formatter/Quiet.hs formatted ede5712d5ab7 ShellCheck-0.11.0/src/ShellCheck/Formatter/TTY.hs formatted a601aea10041 ShellCheck-0.11.0/src/ShellCheck/Interface.hs-formatted fd032d879733 ShellCheck-0.11.0/src/ShellCheck/Parser.hs+formatted 2cfa8339411e ShellCheck-0.11.0/src/ShellCheck/Parser.hs formatted 41e45f4ec82e ShellCheck-0.11.0/src/ShellCheck/Prelude.hs formatted 0b6a2ca650ea ShellCheck-0.11.0/src/ShellCheck/Regex.hs formatted d7f66e7f3a00 ShellCheck-0.11.0/test/shellcheck.hs@@ -561,30 +561,30 @@ formatted 8e47a331bfb8 aeson-2.3.1.0/src/Data/Aeson/TH.hs formatted 12a0132d4a2c aeson-2.3.1.0/src/Data/Aeson/Text.hs formatted d929af4e0687 aeson-2.3.1.0/src/Data/Aeson/Types.hs-formatted e11d9a90b593 aeson-2.3.1.0/src/Data/Aeson/Types/Class.hs+formatted 769fcc14bce2 aeson-2.3.1.0/src/Data/Aeson/Types/Class.hs formatted fc043ebfc291 aeson-2.3.1.0/src/Data/Aeson/Types/FromJSON.hs formatted af543197c845 aeson-2.3.1.0/src/Data/Aeson/Types/Generic.hs formatted a7b6903cad74 aeson-2.3.1.0/src/Data/Aeson/Types/Internal.hs formatted e372019c3433 aeson-2.3.1.0/src/Data/Aeson/Types/ToJSON.hs formatted 1916009e9352 aeson-2.3.1.0/tests/CastFloat.hs-formatted bad3a5065737 aeson-2.3.1.0/tests/DataFamilies/Encoders.hs-formatted 0d864147e795 aeson-2.3.1.0/tests/DataFamilies/Instances.hs+formatted 83937f2ef82e aeson-2.3.1.0/tests/DataFamilies/Encoders.hs+formatted 204116ceb7db aeson-2.3.1.0/tests/DataFamilies/Instances.hs formatted 91fe7aa4d3dc aeson-2.3.1.0/tests/DataFamilies/Properties.hs-formatted 4d002c77704c aeson-2.3.1.0/tests/DataFamilies/Types.hs+formatted 7f146994b15a aeson-2.3.1.0/tests/DataFamilies/Types.hs formatted 07fde92bf8d1 aeson-2.3.1.0/tests/DoubleToScientific.hs-formatted 659a9357b588 aeson-2.3.1.0/tests/Encoders.hs-formatted 30150da5a855 aeson-2.3.1.0/tests/ErrorMessages.hs+formatted 5377988ba287 aeson-2.3.1.0/tests/Encoders.hs+formatted 5ad9153a88b6 aeson-2.3.1.0/tests/ErrorMessages.hs formatted 72fb423455c1 aeson-2.3.1.0/tests/Functions.hs-formatted 45b065b98a94 aeson-2.3.1.0/tests/Instances.hs+formatted a9d868843992 aeson-2.3.1.0/tests/Instances.hs formatted 38b7096d53c4 aeson-2.3.1.0/tests/JSONTestSuite.hs formatted 82f27586e194 aeson-2.3.1.0/tests/Options.hs-formatted 269fd896fd74 aeson-2.3.1.0/tests/PropUtils.hs+formatted 44fab88974f6 aeson-2.3.1.0/tests/PropUtils.hs formatted 5c7e1bcf3dd6 aeson-2.3.1.0/tests/Properties.hs formatted 7d368ad15e82 aeson-2.3.1.0/tests/PropertyGeneric.hs-formatted 109d7fb52b5b aeson-2.3.1.0/tests/PropertyKeys.hs-formatted 0d9df9d04cba aeson-2.3.1.0/tests/PropertyQC.hs-formatted a2e378d49eb8 aeson-2.3.1.0/tests/PropertyRTFunctors.hs-formatted d54279308532 aeson-2.3.1.0/tests/PropertyRoundTrip.hs+formatted 6e8696681818 aeson-2.3.1.0/tests/PropertyKeys.hs+formatted fbd08dc6e050 aeson-2.3.1.0/tests/PropertyQC.hs+formatted 8225a2de1b48 aeson-2.3.1.0/tests/PropertyRTFunctors.hs+formatted ec45061f33d0 aeson-2.3.1.0/tests/PropertyRoundTrip.hs formatted 5c1372b7a444 aeson-2.3.1.0/tests/PropertyTH.hs formatted 5b4efaf27bc6 aeson-2.3.1.0/tests/RFC8785.hs formatted 378b97660503 aeson-2.3.1.0/tests/Regression/Issue1138.hs@@ -592,16 +592,16 @@ formatted f000e9c51f1a aeson-2.3.1.0/tests/Regression/Issue571.hs formatted 650ac09bee0a aeson-2.3.1.0/tests/Regression/Issue687.hs formatted e5c541d1465a aeson-2.3.1.0/tests/Regression/Issue967.hs-formatted f696f5d31388 aeson-2.3.1.0/tests/SerializationFormatSpec.hs+formatted dc258c7824a4 aeson-2.3.1.0/tests/SerializationFormatSpec.hs formatted 8eaa36c16021 aeson-2.3.1.0/tests/Tests.hs-formatted c88e88732cfd aeson-2.3.1.0/tests/Types.hs-formatted 7731424b43b3 aeson-2.3.1.0/tests/UnitTests.hs+formatted 67cf480b391c aeson-2.3.1.0/tests/Types.hs+formatted 27539a7f2ac7 aeson-2.3.1.0/tests/UnitTests.hs formatted f18689008d86 aeson-2.3.1.0/tests/UnitTests/FromJSONKey.hs formatted 8fa12a214cda aeson-2.3.1.0/tests/UnitTests/Hashable.hs formatted 2e6e13a2b307 aeson-2.3.1.0/tests/UnitTests/KeyMapInsertWith.hs formatted fc462ed61e50 aeson-2.3.1.0/tests/UnitTests/MonadFix.hs formatted 657449faeb93 aeson-2.3.1.0/tests/UnitTests/NoThunks.hs-formatted 31a111ec64d8 aeson-2.3.1.0/tests/UnitTests/NullaryConstructors.hs+formatted 860ac1653916 aeson-2.3.1.0/tests/UnitTests/NullaryConstructors.hs formatted 91f9a9590048 aeson-2.3.1.0/tests/UnitTests/OmitNothingFieldsNote.hs formatted 11d14533f820 aeson-2.3.1.0/tests/UnitTests/OptionalFields.hs formatted 699f022c0cb2 aeson-2.3.1.0/tests/UnitTests/OptionalFields/Common.hs@@ -617,7 +617,7 @@ formatted 1aaa3e2316a9 ansi-terminal-1.1.5/win/System/Console/ANSI/Windows/Foreign.hs formatted e763680dce5b ansi-terminal-1.1.5/win/System/Console/ANSI/Windows/Win32/Types.hs formatted 9e25d9947df5 async-2.2.6/Control/Concurrent/Async.hs-partly-checked 652d49b16c08 async-2.2.6/Control/Concurrent/Async/Internal.hs+partly-checked f088ecc2c2e1 async-2.2.6/Control/Concurrent/Async/Internal.hs formatted 68777666410b async-2.2.6/Control/Concurrent/Async/Warden.hs formatted a0f5a7bd29cb async-2.2.6/Control/Concurrent/Stream.hs formatted e865ae48f11b async-2.2.6/Setup.hs@@ -681,16 +681,16 @@ formatted 316b8d029dee aws-0.25.3/Aws/Core.hs formatted 526f7494fea1 aws-0.25.3/Aws/DynamoDb.hs formatted 11c5a36e6111 aws-0.25.3/Aws/DynamoDb/Commands.hs-formatted fdedda6a01bc aws-0.25.3/Aws/DynamoDb/Commands/BatchGetItem.hs-formatted 284a9d6d25fa aws-0.25.3/Aws/DynamoDb/Commands/BatchWriteItem.hs-formatted 37d0f09919b9 aws-0.25.3/Aws/DynamoDb/Commands/DeleteItem.hs+formatted f3cc0daec291 aws-0.25.3/Aws/DynamoDb/Commands/BatchGetItem.hs+formatted d547ef2d0b43 aws-0.25.3/Aws/DynamoDb/Commands/BatchWriteItem.hs+formatted 677a7cdf1061 aws-0.25.3/Aws/DynamoDb/Commands/DeleteItem.hs formatted 6e48f7806e5d aws-0.25.3/Aws/DynamoDb/Commands/GetItem.hs-formatted 413574967b43 aws-0.25.3/Aws/DynamoDb/Commands/PutItem.hs+formatted 27bb9f414054 aws-0.25.3/Aws/DynamoDb/Commands/PutItem.hs formatted 706f861f1b54 aws-0.25.3/Aws/DynamoDb/Commands/Query.hs formatted 76d3cbe6ea1b aws-0.25.3/Aws/DynamoDb/Commands/Scan.hs formatted c6547f406402 aws-0.25.3/Aws/DynamoDb/Commands/Table.hs-formatted 1159130413aa aws-0.25.3/Aws/DynamoDb/Commands/UpdateItem.hs-formatted 5527efbba0d1 aws-0.25.3/Aws/DynamoDb/Core.hs+formatted 66dd03e705d8 aws-0.25.3/Aws/DynamoDb/Commands/UpdateItem.hs+formatted 0779dd9e102b aws-0.25.3/Aws/DynamoDb/Core.hs formatted 7da30757579d aws-0.25.3/Aws/Ec2/InstanceMetadata.hs formatted 64524ea72e53 aws-0.25.3/Aws/Iam.hs formatted 749eee8b56a6 aws-0.25.3/Aws/Iam/Commands.hs@@ -1462,36 +1462,36 @@ formatted 52caf5793d73 brittany-0.14.0.2/data/Test99.hs formatted 7a5d0ee67b99 brittany-0.14.0.2/source/executable/Main.hs formatted f006bb65ddb6 brittany-0.14.0.2/source/library/Language/Haskell/Brittany.hs-formatted effc7ab14a00 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal.hs-formatted 62f91f308c2e brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Backend.hs+formatted 7ddd24db972c brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal.hs+formatted efd6c420ed12 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Backend.hs formatted b114e1735e96 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/BackendUtils.hs formatted 4db6008c162f brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Config.hs-formatted 47cac5b50849 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Config/Types.hs+formatted 442f38348553 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Config/Types.hs formatted 3479d640d39c brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Config/Types/Instances.hs-formatted 3a7e4e0507b6 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/ExactPrintUtils.hs+formatted 0bd9379faeaf brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/ExactPrintUtils.hs formatted 5d4339023e83 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/LayouterBasics.hs formatted e6236562836a brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/DataDecl.hs-formatted 4259510f3316 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/Decl.hs+formatted 35d642ea03c9 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/Decl.hs formatted 85310fee66ef brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/Expr.hs formatted 63ac93aa854f brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/Expr.hs-boot formatted f730e71a6728 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/IE.hs formatted 4647ea64ed39 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/Import.hs formatted 455e49f342e4 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/Module.hs formatted 94a22988945f brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/Pattern.hs-formatted d46ed5a9675b brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/Stmt.hs+formatted 9219767aabaf brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/Stmt.hs formatted 08c3e5d27dd4 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/Stmt.hs-boot formatted 757402eae174 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Layouters/Type.hs formatted 8d7327e29615 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Obfuscation.hs formatted 0dc2352f4480 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/ParseModule.hs formatted 069a6d78e89a brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Prelude.hs formatted 1a8db66fd521 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/PreludeUtils.hs-formatted a12759ae906d brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Transformations/Alt.hs+formatted a2109c56e3eb brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Transformations/Alt.hs formatted c6217dd1471b brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Transformations/Columns.hs formatted f558625f25b7 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Transformations/Floating.hs formatted 7d95cc82a77f brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Transformations/Indent.hs formatted f6a84d0ce013 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Transformations/Par.hs-formatted 9bb018400ea8 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Types.hs-formatted 203fcf60dae6 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Utils.hs+formatted 5ea5611a32d7 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Types.hs+formatted ad92bdb716e5 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Internal/Utils.hs formatted 20badc0ca0c8 brittany-0.14.0.2/source/library/Language/Haskell/Brittany/Main.hs formatted e425ebfb8e97 brittany-0.14.0.2/source/test-suite/Main.hs formatted e865ae48f11b capability-0.5.0.1/Setup.hs@@ -1567,9 +1567,9 @@ formatted f70f6155e62a conduit-1.3.6.1/benchmarks/unfused.hs formatted 20fad0c5aecd conduit-1.3.6.1/src/Conduit.hs formatted bf88ceabad43 conduit-1.3.6.1/src/Data/Conduit.hs-formatted 1a953cd53139 conduit-1.3.6.1/src/Data/Conduit/Combinators.hs+formatted df07505ba4ab conduit-1.3.6.1/src/Data/Conduit/Combinators.hs formatted f520242138c3 conduit-1.3.6.1/src/Data/Conduit/Combinators/Stream.hs-formatted 7d754c7349cf conduit-1.3.6.1/src/Data/Conduit/Combinators/Unqualified.hs+formatted 438fcb6d4096 conduit-1.3.6.1/src/Data/Conduit/Combinators/Unqualified.hs formatted 8e861a915c2f conduit-1.3.6.1/src/Data/Conduit/Internal.hs formatted c35f5a66accc conduit-1.3.6.1/src/Data/Conduit/Internal/Conduit.hs formatted bbfd28e5b98b conduit-1.3.6.1/src/Data/Conduit/Internal/Fusion.hs@@ -1908,17 +1908,17 @@ formatted 95434fcb6957 exceptions-0.10.12/src/Control/Monad/Catch/Pure.hs formatted da6b7265725b exceptions-0.10.12/tests/Control/Monad/Catch/Tests.hs formatted bba664ba2b8b exceptions-0.10.12/tests/Tests.hs-formatted 421394df9111 fay-0.24.2.0/Setup.hs+formatted 9ecbe8994d9e fay-0.24.2.0/Setup.hs formatted ec9eeba0c6a1 fay-0.24.2.0/examples/CodeWorld.hs formatted 7cf91d63601f fay-0.24.2.0/examples/CodeWorldMain.hs-formatted d34203032896 fay-0.24.2.0/examples/Cont.hs+formatted 2791ee0a9bd2 fay-0.24.2.0/examples/Cont.hs formatted b5088332842e fay-0.24.2.0/examples/D3TreeSample.hs formatted 45fa087c720a fay-0.24.2.0/examples/FayFromJs.hs formatted d4be2511948d fay-0.24.2.0/examples/Separated.hs formatted 73cbc6340843 fay-0.24.2.0/examples/X.hs formatted 89a1e9058dc3 fay-0.24.2.0/examples/Y.hs formatted 55d7b8cf115f fay-0.24.2.0/examples/alert.hs-formatted 3563971c6bdb fay-0.24.2.0/examples/calc.hs+formatted e0b177e5a9f3 fay-0.24.2.0/examples/calc.hs formatted 2d60fb67f2de fay-0.24.2.0/examples/canvaswater.hs formatted 97df12f5dfa2 fay-0.24.2.0/examples/console.hs formatted 7608e18ca01c fay-0.24.2.0/examples/data.hs@@ -1934,38 +1934,38 @@ formatted 7085b02d6471 fay-0.24.2.0/examples/ref.hs formatted 55fe2b9f624f fay-0.24.2.0/examples/showExamples.hs formatted 884b6fbdaadc fay-0.24.2.0/examples/tailrecursive.hs-formatted c2d5e370d7e2 fay-0.24.2.0/src/Fay.hs-formatted 9a042d6a7ba9 fay-0.24.2.0/src/Fay/Compiler.hs-formatted 727b9da5eeda fay-0.24.2.0/src/Fay/Compiler/Decl.hs+formatted d57685ee2836 fay-0.24.2.0/src/Fay.hs+formatted 8e1eda45f5a1 fay-0.24.2.0/src/Fay/Compiler.hs+formatted 5c71e55e06ee fay-0.24.2.0/src/Fay/Compiler/Decl.hs formatted d4575500ee48 fay-0.24.2.0/src/Fay/Compiler/Defaults.hs formatted 891834d5aec2 fay-0.24.2.0/src/Fay/Compiler/Desugar.hs formatted 570ca805808c fay-0.24.2.0/src/Fay/Compiler/Desugar/Name.hs formatted fe53147a7e33 fay-0.24.2.0/src/Fay/Compiler/Desugar/Types.hs-formatted de04ffc1e73c fay-0.24.2.0/src/Fay/Compiler/Exp.hs-formatted cfc0f0f46b43 fay-0.24.2.0/src/Fay/Compiler/FFI.hs+formatted 85a69bad988d fay-0.24.2.0/src/Fay/Compiler/Exp.hs+formatted c6dd7b9e3183 fay-0.24.2.0/src/Fay/Compiler/FFI.hs formatted 69fd6dd80a66 fay-0.24.2.0/src/Fay/Compiler/GADT.hs-formatted bcb9d437929e fay-0.24.2.0/src/Fay/Compiler/Import.hs-formatted 766c34ce53b0 fay-0.24.2.0/src/Fay/Compiler/InitialPass.hs-formatted d9d683411028 fay-0.24.2.0/src/Fay/Compiler/Misc.hs-formatted bce0992d570d fay-0.24.2.0/src/Fay/Compiler/ModuleT.hs-formatted 2fdd19da261b fay-0.24.2.0/src/Fay/Compiler/Optimizer.hs-formatted ac32749d90ec fay-0.24.2.0/src/Fay/Compiler/Packages.hs+formatted 7ffae636dbe4 fay-0.24.2.0/src/Fay/Compiler/Import.hs+formatted 95cf95ae99fe fay-0.24.2.0/src/Fay/Compiler/InitialPass.hs+formatted 9df7da80e8c4 fay-0.24.2.0/src/Fay/Compiler/Misc.hs+formatted e526d58c2637 fay-0.24.2.0/src/Fay/Compiler/ModuleT.hs+formatted 984b0a80a55e fay-0.24.2.0/src/Fay/Compiler/Optimizer.hs+formatted ce1e364e00bb fay-0.24.2.0/src/Fay/Compiler/Packages.hs formatted 1798f425b036 fay-0.24.2.0/src/Fay/Compiler/Parse.hs-formatted 46f343ccba7b fay-0.24.2.0/src/Fay/Compiler/Pattern.hs+formatted dba17f522647 fay-0.24.2.0/src/Fay/Compiler/Pattern.hs formatted 7cca783f3fcd fay-0.24.2.0/src/Fay/Compiler/Prelude.hs-formatted 0fe3943a05fb fay-0.24.2.0/src/Fay/Compiler/PrimOp.hs-formatted d7dcfe1aa9ca fay-0.24.2.0/src/Fay/Compiler/Print.hs+formatted 0705dae72c12 fay-0.24.2.0/src/Fay/Compiler/PrimOp.hs+formatted bec107e6f3d3 fay-0.24.2.0/src/Fay/Compiler/Print.hs formatted da23ccb55970 fay-0.24.2.0/src/Fay/Compiler/QName.hs formatted 9fe2fbd3e360 fay-0.24.2.0/src/Fay/Compiler/State.hs formatted fb1831a445cc fay-0.24.2.0/src/Fay/Compiler/Typecheck.hs formatted 631cbd5cb454 fay-0.24.2.0/src/Fay/Config.hs-formatted 15cd1e849d0e fay-0.24.2.0/src/Fay/Convert.hs+formatted 5e21cc38a1e1 fay-0.24.2.0/src/Fay/Convert.hs formatted 80d46083d774 fay-0.24.2.0/src/Fay/Exts.hs-formatted 7ec6f093e0ca fay-0.24.2.0/src/Fay/Exts/NoAnnotation.hs+formatted 23a0a09bf5e1 fay-0.24.2.0/src/Fay/Exts/NoAnnotation.hs formatted bd1f234d96c0 fay-0.24.2.0/src/Fay/Exts/Scoped.hs-formatted f7856f337bee fay-0.24.2.0/src/Fay/FFI.hs+formatted d29776a761a7 fay-0.24.2.0/src/Fay/FFI.hs formatted b21a6a7c7e2f fay-0.24.2.0/src/Fay/Runtime.hs-formatted 0ac5069cbf76 fay-0.24.2.0/src/Fay/Types.hs+formatted 21ff228b349a fay-0.24.2.0/src/Fay/Types.hs formatted 6654772d79a6 fay-0.24.2.0/src/Fay/Types/CompileError.hs formatted 4f7fdfee2a8f fay-0.24.2.0/src/Fay/Types/CompileResult.hs formatted cea8d04284a4 fay-0.24.2.0/src/Fay/Types/FFI.hs@@ -1973,33 +1973,33 @@ formatted 1165cbb859cc fay-0.24.2.0/src/Fay/Types/ModulePath.hs formatted ed80698a7cd1 fay-0.24.2.0/src/Fay/Types/Printer.hs formatted 6593766b1d2e fay-0.24.2.0/src/haskell-names/Language/Haskell/Names.hs-formatted 379279fca760 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Annotated.hs-formatted effa30f29551 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Exports.hs-formatted 633d0ead4b0e fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/GetBound.hs-formatted 74e293075d13 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/GlobalSymbolTable.hs+formatted c40577bbbe78 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Annotated.hs+formatted 2156e3053cf3 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Exports.hs+formatted 2c712ca7ff92 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/GetBound.hs+formatted 6f5ea387dca8 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/GlobalSymbolTable.hs formatted 13662130820d fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/GlobalSymbolTable.hs-boot-formatted 3346bfa56c31 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Imports.hs+formatted ea2e30c7db82 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Imports.hs formatted bda66c725ce4 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/LocalSymbolTable.hs-formatted c6c4acea37c3 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/ModuleSymbols.hs-formatted 76ce45f69410 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Open/Base.hs+formatted 2dcb549aaa00 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/ModuleSymbols.hs+formatted 81d92815fef2 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Open/Base.hs formatted b12db8ba5ce9 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Open/Derived.hs-formatted 325536c28de7 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Open/Instances.hs-formatted deb2b656a1ad fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/RecordWildcards.hs-formatted 80dec7fda3fb fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Recursive.hs-formatted 006f3c334ab0 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/ScopeUtils.hs-formatted 3dd4a51f2cc5 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/SyntaxUtils.hs-formatted 954e31fc5bf8 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Types.hs+formatted d7517cafa993 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Open/Instances.hs+formatted 864c19ccab50 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/RecordWildcards.hs+formatted 36ca15cae788 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Recursive.hs+formatted 35f308030af1 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/ScopeUtils.hs+formatted 7d8238517a1b fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/SyntaxUtils.hs+formatted cd1075fe28e2 fay-0.24.2.0/src/haskell-names/Language/Haskell/Names/Types.hs formatted a185a50b6e6a fay-0.24.2.0/src/main/Main.hs formatted b9c869a29067 fay-0.24.2.0/src/tests/Test/CommandLine.hs formatted 68c640b352e9 fay-0.24.2.0/src/tests/Test/Compile.hs formatted 6fdbf9951c76 fay-0.24.2.0/src/tests/Test/Convert.hs formatted 89d594b07db0 fay-0.24.2.0/src/tests/Test/Desugar.hs formatted a3937c4d8bef fay-0.24.2.0/src/tests/Test/Util.hs-formatted 63cce78f9492 fay-0.24.2.0/src/tests/Tests.hs+formatted 198e7e859e4a fay-0.24.2.0/src/tests/Tests.hs formatted 8114602da4c3 fay-0.24.2.0/tests/AllBaseModules.hs formatted e78d25c5a73d fay-0.24.2.0/tests/AutomaticList.hs formatted 779c72fbdb74 fay-0.24.2.0/tests/Bool.hs-formatted ad89dd2cf37b fay-0.24.2.0/tests/CPP.hs+formatted d5c6ec08c4a5 fay-0.24.2.0/tests/CPP.hs formatted 61e542a01350 fay-0.24.2.0/tests/Char.hs formatted 7badee2f20a7 fay-0.24.2.0/tests/Compile/CPPMultiLineStrings.hs formatted a5ace073f2a3 fay-0.24.2.0/tests/Compile/CPPTypecheck.hs@@ -2378,12 +2378,12 @@ formatted 4d201ce15d28 haxl-2.5.1.1/Haxl/DataSource/ConcurrentIO.hs formatted cf38b55569d1 haxl-2.5.1.1/Haxl/Prelude.hs formatted 0bf115c25c25 haxl-2.5.1.1/Setup.hs-formatted 5897e1a72e86 haxl-2.5.1.1/tests/AdoTests.hs+formatted b30d01a22e96 haxl-2.5.1.1/tests/AdoTests.hs formatted 7392cb2bf8df haxl-2.5.1.1/tests/AllTests.hs formatted 29963bfcd43e haxl-2.5.1.1/tests/BadDataSource.hs-formatted 9869a85f8734 haxl-2.5.1.1/tests/BatchTests.hs+formatted 3930b8064973 haxl-2.5.1.1/tests/BatchTests.hs formatted a75656cc58b4 haxl-2.5.1.1/tests/Bench.hs-formatted 6b4d75212e14 haxl-2.5.1.1/tests/CoreTests.hs+formatted c77a8f206311 haxl-2.5.1.1/tests/CoreTests.hs formatted 46378c08a2d4 haxl-2.5.1.1/tests/DataCacheTest.hs formatted 8120f9ee1106 haxl-2.5.1.1/tests/DataSourceDispatchTests.hs formatted 04f8f254da6b haxl-2.5.1.1/tests/ExampleDataSource.hs@@ -2396,11 +2396,11 @@ formatted 08f288ec8741 haxl-2.5.1.1/tests/MonadBench.hs formatted be417b8bc52f haxl-2.5.1.1/tests/OutgoneFetchesTests.hs formatted f760bff6e190 haxl-2.5.1.1/tests/ParallelTests.hs-formatted c757b6a749ba haxl-2.5.1.1/tests/ProfileTests.hs+formatted bdcd901d6365 haxl-2.5.1.1/tests/ProfileTests.hs formatted 478999c2d1bd haxl-2.5.1.1/tests/SleepDataSource.hs formatted 23f293b1253d haxl-2.5.1.1/tests/StatsTests.hs formatted 0deec7d41cb2 haxl-2.5.1.1/tests/TestBadDataSource.hs-formatted 6270eac19e14 haxl-2.5.1.1/tests/TestExampleDataSource.hs+formatted 8c99d9ddfade haxl-2.5.1.1/tests/TestExampleDataSource.hs formatted 33f33c2b6767 haxl-2.5.1.1/tests/TestMain.hs formatted c0a01d1bb863 haxl-2.5.1.1/tests/TestTypes.hs formatted 0cdf14515138 haxl-2.5.1.1/tests/TestUtils.hs@@ -2413,21 +2413,21 @@ formatted 7855a4bfe6fc hedgehog-1.7/src/Hedgehog/Internal/Discovery.hs formatted 15b153c4095e hedgehog-1.7/src/Hedgehog/Internal/Distributive.hs formatted db3d79ebed8e hedgehog-1.7/src/Hedgehog/Internal/Exception.hs-formatted 94960f681457 hedgehog-1.7/src/Hedgehog/Internal/Gen.hs+formatted 93bc9212bbae hedgehog-1.7/src/Hedgehog/Internal/Gen.hs formatted 82bd44e46c74 hedgehog-1.7/src/Hedgehog/Internal/HTraversable.hs formatted 0b6e930c617c hedgehog-1.7/src/Hedgehog/Internal/Opaque.hs formatted 0c41d0d4f087 hedgehog-1.7/src/Hedgehog/Internal/Prelude.hs-formatted a48f67a8e4b7 hedgehog-1.7/src/Hedgehog/Internal/Property.hs+formatted 099ade9775e8 hedgehog-1.7/src/Hedgehog/Internal/Property.hs formatted 144a39866885 hedgehog-1.7/src/Hedgehog/Internal/Queue.hs formatted 9d14d12ad0b0 hedgehog-1.7/src/Hedgehog/Internal/Range.hs formatted e83fa4c27743 hedgehog-1.7/src/Hedgehog/Internal/Region.hs-formatted ffa4ddc6361c hedgehog-1.7/src/Hedgehog/Internal/Report.hs-formatted 1c86cc2eaef4 hedgehog-1.7/src/Hedgehog/Internal/Runner.hs+formatted 1f1c1a46ba31 hedgehog-1.7/src/Hedgehog/Internal/Report.hs+formatted 12a45329d79f hedgehog-1.7/src/Hedgehog/Internal/Runner.hs formatted fb3eda304e9f hedgehog-1.7/src/Hedgehog/Internal/Seed.hs formatted 8b91f20c13a8 hedgehog-1.7/src/Hedgehog/Internal/Show.hs formatted c446e4f9c366 hedgehog-1.7/src/Hedgehog/Internal/Shrink.hs formatted c083b8dff0b7 hedgehog-1.7/src/Hedgehog/Internal/Source.hs-formatted ea48c5d7dba1 hedgehog-1.7/src/Hedgehog/Internal/State.hs+formatted e74cdd8b92d6 hedgehog-1.7/src/Hedgehog/Internal/State.hs formatted 8c1393826b50 hedgehog-1.7/src/Hedgehog/Internal/TH.hs formatted 79e0eb255f0f hedgehog-1.7/src/Hedgehog/Internal/Tree.hs formatted 42e8fad71904 hedgehog-1.7/src/Hedgehog/Internal/Tripping.hs@@ -2488,7 +2488,7 @@ formatted 367217908354 hledger-1.52.1/bench/bench.hs formatted 770ce114d693 hledger-1.52.1/test/unittest.hs formatted e865ae48f11b hlint-3.10/Setup.hs-formatted 0b9c394414b3 hlint-3.10/data/HLint_QuickCheck.hs+formatted df385b8f4253 hlint-3.10/data/HLint_QuickCheck.hs declined - hlint-3.10/data/HLint_TypeCheck.hs formatted 7f62e2698706 hlint-3.10/data/Test.hs formatted a3cf0d266650 hlint-3.10/src/Apply.hs@@ -2539,7 +2539,7 @@ formatted 3308ee100b88 hlint-3.10/src/Hint/Type.hs formatted c433ccccd5d9 hlint-3.10/src/Hint/Unsafe.hs formatted 48cd04ad2e02 hlint-3.10/src/HsColour.hs-formatted dd9801a47942 hlint-3.10/src/Idea.hs+formatted 12f5c15a3edb hlint-3.10/src/Idea.hs formatted 576b754ba5d4 hlint-3.10/src/Language/Haskell/HLint.hs formatted 7b8846cb4591 hlint-3.10/src/Main.hs formatted fcdf50a5835c hlint-3.10/src/Parallel.hs@@ -2624,7 +2624,7 @@ formatted 2665482bea3b hspec-core-2.11.17/test/Test/Hspec/Core/Formatters/V1Spec.hs formatted fdce61371e6b hspec-core-2.11.17/test/Test/Hspec/Core/Formatters/V2Spec.hs formatted 177347d407e1 hspec-core-2.11.17/test/Test/Hspec/Core/HooksSpec.hs-formatted ad736b09b724 hspec-core-2.11.17/test/Test/Hspec/Core/QuickCheck/UtilSpec.hs+formatted 4f036cf5daaf hspec-core-2.11.17/test/Test/Hspec/Core/QuickCheck/UtilSpec.hs formatted 775321ce08bf hspec-core-2.11.17/test/Test/Hspec/Core/Runner/EvalSpec.hs formatted 7eadb8336461 hspec-core-2.11.17/test/Test/Hspec/Core/Runner/JobQueueSpec.hs formatted 0d283fd19887 hspec-core-2.11.17/test/Test/Hspec/Core/Runner/PrintSlowSpecItemsSpec.hs@@ -2636,8 +2636,8 @@ formatted ad44f3bb30b2 hspec-core-2.11.17/test/Test/Hspec/Core/TreeSpec.hs formatted 9b07f9624b8d hspec-core-2.11.17/test/Test/Hspec/Core/UtilSpec.hs formatted e13927dd80dc hspec-core-2.11.17/vendor/Data/Algorithm/Diff.hs-partly-checked 03ba27ab9fd5 hspec-core-2.11.17/vendor/async-2.2.5/Control/Concurrent/Async.hs-formatted bea44982097a hspec-core-2.11.17/vendor/stm-2.5.0.1/Control/Concurrent/STM/TMVar.hs+partly-checked c9eef62258f8 hspec-core-2.11.17/vendor/async-2.2.5/Control/Concurrent/Async.hs+formatted 220d41b7fb0c hspec-core-2.11.17/vendor/stm-2.5.0.1/Control/Concurrent/STM/TMVar.hs formatted b3d4964504e7 http-client-0.7.19/Data/KeyedPool.hs formatted 3e436f1b2afb http-client-0.7.19/Network/HTTP/Client.hs formatted 547642efe2cc http-client-0.7.19/Network/HTTP/Client/Body.hs@@ -2811,7 +2811,7 @@ formatted 72999d5c814e intero-0.1.40/src/GhciTypes.hs declined - intero-0.1.40/src/InteractiveUI.hs formatted d3b894941b5e intero-0.1.40/src/Intero/Compat.hs-partly-checked 650cf9fb288c intero-0.1.40/src/Main.hs+partly-checked c4c4f282d93d intero-0.1.40/src/Main.hs formatted 13b22f1a2f96 intero-0.1.40/src/test/Main.hs formatted 4fe73bbfe233 leksah-0.16.2.2/data/leksah-welcome/src/Main.hs formatted 1cefb44a49bc leksah-0.16.2.2/data/leksah-welcome/test/Main.hs@@ -2871,7 +2871,7 @@ formatted 3df2a9c1d21f leksah-0.16.2.2/src/IDE/SymbolNavigation.hs formatted 265b135c829e leksah-0.16.2.2/src/IDE/TextEditor.hs formatted fc7e53f0e945 leksah-0.16.2.2/src/IDE/TextEditor/Class.hs-formatted 1c4c5952013a leksah-0.16.2.2/src/IDE/TextEditor/CodeMirror.hs+formatted 9467271dc7ec leksah-0.16.2.2/src/IDE/TextEditor/CodeMirror.hs formatted ead97fbc797b leksah-0.16.2.2/src/IDE/TextEditor/GtkSourceView.hs formatted 02fedce6ee7f leksah-0.16.2.2/src/IDE/TextEditor/Yi.hs formatted 74ead3c637fd leksah-0.16.2.2/src/IDE/TextEditor/Yi/Config.hs@@ -2889,11 +2889,11 @@ formatted 67948814999e lens-5.3.6/benchmarks/unsafe.hs formatted 6b5d9407288a lens-5.3.6/examples/Aeson.hs formatted fcea15eace15 lens-5.3.6/examples/Plates.hs-formatted 42c84063ca40 lens-5.3.6/examples/Pong.hs+formatted 6016ef723b2e lens-5.3.6/examples/Pong.hs formatted 1e7ca6c2bac4 lens-5.3.6/examples/Turtle.hs formatted e865ae48f11b lens-5.3.6/lens-properties/Setup.hs formatted 03d5b652be06 lens-5.3.6/lens-properties/src/Control/Lens/Properties.hs-formatted 078f1b9b6754 lens-5.3.6/src/Control/Exception/Lens.hs+formatted fb677a8ca744 lens-5.3.6/src/Control/Exception/Lens.hs formatted 4fb8b462c692 lens-5.3.6/src/Control/Lens.hs formatted dab5fde53fc2 lens-5.3.6/src/Control/Lens/At.hs formatted 1a555e9740b2 lens-5.3.6/src/Control/Lens/Combinators.hs@@ -2979,7 +2979,7 @@ formatted 5a4f7cf6359b lens-5.3.6/src/System/FilePath/Lens.hs formatted 18c87dbc74ee lens-5.3.6/src/System/IO/Error/Lens.hs formatted aa93fbb09ce5 lens-5.3.6/tests/BigRecord.hs-formatted 4bb6e1258874 lens-5.3.6/tests/T1024.hs+formatted 22c453e7b965 lens-5.3.6/tests/T1024.hs formatted 783da428b966 lens-5.3.6/tests/T799.hs formatted 735c78d5ee02 lens-5.3.6/tests/T917.hs formatted 1d3a1a7a064d lens-5.3.6/tests/T972.hs@@ -2988,8 +2988,8 @@ declined - lens-5.3.6/tests/properties.hs formatted 4bd3d2baa141 lens-5.3.6/tests/templates.hs formatted 2a47884feb32 lifted-base-0.2.3.12/Control/Concurrent/Chan/Lifted.hs-formatted ed858547b569 lifted-base-0.2.3.12/Control/Concurrent/Lifted.hs-formatted 9e73627aa56a lifted-base-0.2.3.12/Control/Concurrent/MVar/Lifted.hs+formatted 2878fb30d603 lifted-base-0.2.3.12/Control/Concurrent/Lifted.hs+formatted 5ca1cae42a21 lifted-base-0.2.3.12/Control/Concurrent/MVar/Lifted.hs formatted 7896c1c8db7e lifted-base-0.2.3.12/Control/Concurrent/QSem/Lifted.hs formatted 0e92e2e3c452 lifted-base-0.2.3.12/Control/Concurrent/QSemN/Lifted.hs formatted 5cc7e142b7a9 lifted-base-0.2.3.12/Control/Concurrent/SampleVar/Lifted.hs@@ -3745,7 +3745,7 @@ formatted 9390668ca723 purescript-0.15.15/src/Language/PureScript/Ide/Reexports.hs formatted 40ae0185eda5 purescript-0.15.15/src/Language/PureScript/Ide/SourceFile.hs formatted 4badb68aba43 purescript-0.15.15/src/Language/PureScript/Ide/State.hs-formatted 97156b72015a purescript-0.15.15/src/Language/PureScript/Ide/Types.hs+formatted 64c31342d1b6 purescript-0.15.15/src/Language/PureScript/Ide/Types.hs formatted a2547b22a5bd purescript-0.15.15/src/Language/PureScript/Ide/Usage.hs formatted c5c7f0134f7c purescript-0.15.15/src/Language/PureScript/Ide/Util.hs formatted a54bd99f9377 purescript-0.15.15/src/Language/PureScript/Interactive.hs@@ -4342,7 +4342,7 @@ formatted 7d15eaae39ff stack-9.9.9/src/Stack/Dot.hs formatted e3fe02818737 stack-9.9.9/src/Stack/Exec.hs formatted 69a1b5e114c2 stack-9.9.9/src/Stack/Fetch.hs-formatted 967c0ee2eef6 stack-9.9.9/src/Stack/GhcPkg.hs+formatted 7245d6806bca stack-9.9.9/src/Stack/GhcPkg.hs formatted 8e7700c60073 stack-9.9.9/src/Stack/Init.hs formatted 9f228f8da7ce stack-9.9.9/src/Stack/New.hs formatted a4120f7039a7 stack-9.9.9/src/Stack/Package.hs@@ -4358,7 +4358,7 @@ formatted cd2bb0d685de stack-9.9.9/src/Stack/Types/FlagName.hs formatted d8f8b074a086 stack-9.9.9/src/Stack/Types/GhcPkgId.hs formatted bd4be7dcc823 stack-9.9.9/src/Stack/Types/Internal.hs-formatted 6f80f79fae7e stack-9.9.9/src/Stack/Types/PackageIdentifier.hs+formatted 863509c942b8 stack-9.9.9/src/Stack/Types/PackageIdentifier.hs formatted 88a4f8bd73e5 stack-9.9.9/src/Stack/Types/PackageName.hs formatted cf90354db024 stack-9.9.9/src/Stack/Types/StackT.hs formatted c698b30fdbaf stack-9.9.9/src/Stack/Types/Version.hs@@ -4602,7 +4602,7 @@ formatted 7635d57189ea text-2.1.4/src/Data/Text/Internal/PrimCompat.hs formatted 634f3b2cbbde text-2.1.4/src/Data/Text/Internal/Private.hs formatted a41bd3d7edab text-2.1.4/src/Data/Text/Internal/Read.hs-formatted ec56c867ca01 text-2.1.4/src/Data/Text/Internal/Reverse.hs+formatted 7297075f77b5 text-2.1.4/src/Data/Text/Internal/Reverse.hs formatted 9ddb417cbaa3 text-2.1.4/src/Data/Text/Internal/Search.hs formatted 67bf06cca611 text-2.1.4/src/Data/Text/Internal/StrictBuilder.hs formatted ed455e9ee12d text-2.1.4/src/Data/Text/Internal/Transformation.hs@@ -4638,7 +4638,7 @@ formatted 13a8cbedafd1 text-2.1.4/tests/Tests/Properties/Transcoding.hs formatted 139bbe092e98 text-2.1.4/tests/Tests/Properties/Validate.hs formatted 75f4b0d7a5f3 text-2.1.4/tests/Tests/QuickCheckUtils.hs-formatted c6e25a3408e0 text-2.1.4/tests/Tests/RebindableSyntaxTest.hs+formatted 360188e62bc0 text-2.1.4/tests/Tests/RebindableSyntaxTest.hs formatted 939e997260ba text-2.1.4/tests/Tests/Regressions.hs formatted b7037d3f07cc text-2.1.4/tests/Tests/ShareEmpty.hs formatted f4e9f36f6be9 text-2.1.4/tests/Tests/SlowFunctions.hs@@ -4658,7 +4658,7 @@ formatted 0f617301ba5b time-1.16.0.1/lib/Data/Time/Calendar/CalendarDiffDays.hs formatted e210a7d3456b time-1.16.0.1/lib/Data/Time/Calendar/Days.hs formatted 03412f0b8356 time-1.16.0.1/lib/Data/Time/Calendar/Easter.hs-formatted f8d4d6f009e4 time-1.16.0.1/lib/Data/Time/Calendar/Gregorian.hs+formatted c8f966093a51 time-1.16.0.1/lib/Data/Time/Calendar/Gregorian.hs formatted be538512fc07 time-1.16.0.1/lib/Data/Time/Calendar/Julian.hs formatted 625e7b428153 time-1.16.0.1/lib/Data/Time/Calendar/JulianYearDay.hs formatted 9ba18c758e02 time-1.16.0.1/lib/Data/Time/Calendar/Month.hs@@ -4681,23 +4681,23 @@ formatted acaedfdab698 time-1.16.0.1/lib/Data/Time/Clock/Internal/UniversalTime.hs formatted cf441f809fdf time-1.16.0.1/lib/Data/Time/Clock/POSIX.hs formatted 6dd6034d73b4 time-1.16.0.1/lib/Data/Time/Clock/System.hs-formatted 7cdd69049dcf time-1.16.0.1/lib/Data/Time/Clock/TAI.hs+formatted bcae28d3d831 time-1.16.0.1/lib/Data/Time/Clock/TAI.hs formatted b600a5b5c744 time-1.16.0.1/lib/Data/Time/Format.hs formatted 6c005f66f0be time-1.16.0.1/lib/Data/Time/Format/Format/Class.hs-formatted 47f1688c0787 time-1.16.0.1/lib/Data/Time/Format/Format/Instances.hs-formatted 3764319de19b time-1.16.0.1/lib/Data/Time/Format/ISO8601.hs+formatted 200179c37154 time-1.16.0.1/lib/Data/Time/Format/Format/Instances.hs+formatted 14f4a5f4a56e time-1.16.0.1/lib/Data/Time/Format/ISO8601.hs formatted 76aa49c9292b time-1.16.0.1/lib/Data/Time/Format/Internal.hs formatted b7ae7592bcb6 time-1.16.0.1/lib/Data/Time/Format/Locale.hs-formatted 2cd514758801 time-1.16.0.1/lib/Data/Time/Format/Parse.hs+formatted b7301f512161 time-1.16.0.1/lib/Data/Time/Format/Parse.hs formatted 05a7f0bf5554 time-1.16.0.1/lib/Data/Time/Format/Parse/Class.hs-formatted cb2ccd8c40fd time-1.16.0.1/lib/Data/Time/Format/Parse/Instances.hs+formatted b579af300eaa time-1.16.0.1/lib/Data/Time/Format/Parse/Instances.hs formatted 4489a4cdab49 time-1.16.0.1/lib/Data/Time/LocalTime.hs formatted 93986a8450d1 time-1.16.0.1/lib/Data/Time/LocalTime/Internal/CalendarDiffTime.hs formatted 94f381fe1ad1 time-1.16.0.1/lib/Data/Time/LocalTime/Internal/Foreign.hs-formatted ca98c37fb34c time-1.16.0.1/lib/Data/Time/LocalTime/Internal/LocalTime.hs+formatted 4c9ff2a8a8a0 time-1.16.0.1/lib/Data/Time/LocalTime/Internal/LocalTime.hs formatted 9200ebaf067a time-1.16.0.1/lib/Data/Time/LocalTime/Internal/TimeOfDay.hs formatted 74496e557585 time-1.16.0.1/lib/Data/Time/LocalTime/Internal/TimeZone.hs-formatted 343d16749a70 time-1.16.0.1/lib/Data/Time/LocalTime/Internal/ZonedTime.hs+formatted a8370b544557 time-1.16.0.1/lib/Data/Time/LocalTime/Internal/ZonedTime.hs formatted c365d1aa63cd time-1.16.0.1/test/ForeignCalls.hs formatted fc8234c946fb time-1.16.0.1/test/ShowDefaultTZAbbreviations.hs formatted b51274826033 time-1.16.0.1/test/ShowTime.hs@@ -4934,7 +4934,7 @@ formatted 771e81db1b05 unordered-containers-0.2.21/benchmarks/Util/String.hs formatted 2b26017dfa9e unordered-containers-0.2.21/tests/Main.hs formatted 27005a00112e unordered-containers-0.2.21/tests/Properties.hs-formatted b06c5b9e0c6c unordered-containers-0.2.21/tests/Properties/HashMapLazy.hs+formatted 88e44fdb6cbd unordered-containers-0.2.21/tests/Properties/HashMapLazy.hs formatted f2a904c3a89a unordered-containers-0.2.21/tests/Properties/HashMapStrict.hs formatted 2212e9831f16 unordered-containers-0.2.21/tests/Properties/HashSet.hs formatted 3584a01b2cdd unordered-containers-0.2.21/tests/Properties/List.hs@@ -4968,7 +4968,7 @@ formatted d63508f44841 uuid-types-1.0.6.1/src/Data/UUID/Types/Internal/Builder.hs formatted ed86f094a194 uuid-types-1.0.6.1/tests/TestUUID.hs formatted e865ae48f11b vector-0.13.2.0/Setup.hs-formatted cb65eb293bd5 vector-0.13.2.0/benchlib/Bench/Vector/Algo/AwShCC.hs+formatted 968ccc63cf5c vector-0.13.2.0/benchlib/Bench/Vector/Algo/AwShCC.hs formatted 8a7c35b680c0 vector-0.13.2.0/benchlib/Bench/Vector/Algo/FindIndexR.hs formatted 7536358d3c8c vector-0.13.2.0/benchlib/Bench/Vector/Algo/HybCC.hs formatted 31caf6daa18c vector-0.13.2.0/benchlib/Bench/Vector/Algo/Leaffix.hs
+ corpora/vendored/declaration/class/empty-context-out.hs view
@@ -0,0 +1,10 @@+module Report.Pretty where++class () => Pretty a where+ pretty :: a -> String++class (Show a) => Loud a where+ loud :: a -> String++width :: () => Int+width = 80
+ corpora/vendored/declaration/class/empty-context.hs view
@@ -0,0 +1,10 @@+module Report.Pretty where++class () => Pretty a where+ pretty :: a -> String++class (Show a) => Loud a where+ loud :: a -> String++width :: () => Int+width = 80
+ corpora/vendored/declaration/data/empty-context-out.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE DatatypeContexts #-}+{-# LANGUAGE GADTs #-}++module Report.Cells where++data () => Cell a = Cell a++newtype () => Width = Width Int++data Some = forall a. () => Some a++data Shown where+ Shown :: () => Int -> Shown
+ corpora/vendored/declaration/data/empty-context.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE DatatypeContexts #-}+{-# LANGUAGE GADTs #-}++module Report.Cells where++data () => Cell a = Cell a++newtype () => Width = Width Int++data Some = forall a. () => Some a++data Shown where+ Shown :: () => Int -> Shown
+ corpora/vendored/declaration/value/typed-quote-written-across-lines-out.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE TemplateHaskell #-}++-- The body of a typed quote is indented under its bracket, as that of an+-- untyped one is.+untyped :: (Quote m) => m Exp+untyped =+ [|+ let x = 1+ in x + x+ |]++typed :: (Quote m) => Code m Int+typed =+ [||+ let x = 1+ in x + x+ ||]++explicit :: (Quote m) => Code m Int+explicit =+ [e||+ case lookup key table of+ Just x -> x+ Nothing -> 0+ ||]
+ corpora/vendored/declaration/value/typed-quote-written-across-lines.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE TemplateHaskell #-}++-- The body of a typed quote is indented under its bracket, as that of an+-- untyped one is.+untyped :: Quote m => m Exp+untyped =+ [| let x = 1+ in x + x |]++typed :: Quote m => Code m Int+typed =+ [|| let x = 1+ in x + x ||]++explicit :: Quote m => Code m Int+explicit = [e|| case lookup key table of+ Just x -> x+ Nothing -> 0 ||]
+ corpora/vendored/import/sorting-the-prelude-under-rebindable-syntax-out.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE RebindableSyntax #-}++module Weather.Forecast where++import Control.Monad (when)+import Data.List (foldl')+import Prelude (Double, (+))+import Weather.Reading (Reading)++warmer :: Double -> Double+warmer = (+ 1)
+ corpora/vendored/import/sorting-the-prelude-under-rebindable-syntax.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE RebindableSyntax #-}++module Weather.Forecast where++import Weather.Reading (Reading)+import Prelude (Double, (+))+import Data.List (foldl')+import Control.Monad (when)++warmer :: Double -> Double+warmer = (+ 1)
corpora/vendored/other/comment-before-closing-quote-out.hs view
@@ -10,9 +10,9 @@ typed = [||- width * height- -- in pixels- ||]+ width * height+ -- in pixels+ ||] declarations = [d|
+ corpora/vendored/other/comment-inside-a-language-pragma-out.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE+ ScopedTypeVariables+ , LambdaCase -- for classify+ , {- and for nothing else -} BangPatterns+ #-}+{-# LANGUAGE TupleSections #-}++module Kiln.Commented where++-- GHC skips the comments inside the pragma and reads the names around+-- them, and a comment there stays where it is written.+classify :: Int -> String+classify = \case+ 0 -> "zero"+ _ -> "other"
+ corpora/vendored/other/comment-inside-a-language-pragma.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE+ ScopedTypeVariables+ , LambdaCase -- for classify+ , {- and for nothing else -} BangPatterns+ #-}+{-# LANGUAGE DataKinds #-}+module Kiln.Commented where++-- GHC skips the comments inside the pragma and reads the names around+-- them, and a comment there stays where it is written.+classify :: Int -> String+classify = \case+ 0 -> "zero"+ _ -> "other"
+ corpora/vendored/other/cpp/conditional-from-a-where-clause-indented-with-a-tab-out.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE CPP #-}++-- A conditional that begins among bindings indented with a tab and goes on+-- into the declarations after it stays one conditional, as it does where+-- they are indented with spaces.+module T where++f :: Int -> Int+f x = y+ where+ y = x + 1+#if defined(EXTRA)+ z = 2++g :: Int+g = 1+#else++g :: Int+g = 2+#endif
+ corpora/vendored/other/cpp/conditional-from-a-where-clause-indented-with-a-tab.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE CPP #-}++-- A conditional that begins among bindings indented with a tab and goes on+-- into the declarations after it stays one conditional, as it does where+-- they are indented with spaces.+module T where++f :: Int -> Int+f x = y+ where+ y = x + 1+#if defined(EXTRA)+ z = 2++g :: Int+g = 1+#else++g :: Int+g = 2+#endif
+ corpora/vendored/other/cpp/macro-among-statements-out.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE CPP #-}++-- A use standing for a statement stays at the column of the statements+-- around it.+module Solver.Trail where++#ifdef ENABLE_TRACE+#define TRACING(x) x+#else+#define TRACING(x)+#endif++step :: Int -> IO ()+step n = do+ print n+ TRACING(print n)+ print (n + 1)++steps :: [Int] -> IO ()+steps ns = do+ let total = sum ns+ TRACING(print total)+ forM_ ns $ \n -> do+ step n+ TRACING(print n)+ step (n + 1)+ pure ()
+ corpora/vendored/other/cpp/macro-among-statements.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE CPP #-}++-- A use standing for a statement stays at the column of the statements+-- around it.+module Solver.Trail where++#ifdef ENABLE_TRACE+#define TRACING(x) x+#else+#define TRACING(x)+#endif++step :: Int -> IO ()+step n = do+ print n+ TRACING(print n)+ print (n + 1)++steps :: [Int] -> IO ()+steps ns = do+ let total = sum ns+ TRACING(print total)+ forM_ ns $ \n -> do+ step n+ TRACING(print n)+ step (n + 1)+ pure ()
+ corpora/vendored/other/cpp/macro-between-equations-out.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE CPP #-}++-- A use standing for an equation stays at the margin between the equations+-- around it.+module Table.Lookup where++#define ENTRY(k, v) lookupEntry k = v++lookupEntry :: Int -> String+lookupEntry 0 = "zero"+ENTRY(1, "one")+lookupEntry _ = "many"
+ corpora/vendored/other/cpp/macro-between-equations.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE CPP #-}++-- A use standing for an equation stays at the margin between the equations+-- around it.+module Table.Lookup where++#define ENTRY(k, v) lookupEntry k = v++lookupEntry :: Int -> String+lookupEntry 0 = "zero"+ENTRY(1, "one")+lookupEntry _ = "many"
+ corpora/vendored/other/cpp/macro-indented-with-a-tab-out.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE CPP #-}++-- A use indented with a tab is at the column the tab moves it to: one at+-- the column of the code under it is a line of its own, and one further in+-- carries on the line above.+module Steps where++#define ARG(x) (x)+#define STEP(x) print (x)++g :: Int -> Maybe Int+g x = do+ y <-+ Just+ ARG+ (x)+ pure y++h :: Int -> IO ()+h x = do+ STEP(x ++ "";)+ pure ()
+ corpora/vendored/other/cpp/macro-indented-with-a-tab.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE CPP #-}++-- A use indented with a tab is at the column the tab moves it to: one at+-- the column of the code under it is a line of its own, and one further in+-- carries on the line above.+module Steps where++#define ARG(x) (x)+#define STEP(x) print (x)++g :: Int -> Maybe Int+g x = do+ y <- Just+ ARG(x)+ pure y++h :: Int -> IO ()+h x = do+ STEP(x ++ "";)+ pure ()
+ corpora/vendored/other/cpp/preprocessor-turned-on-by-a-flag-out.hs view
@@ -0,0 +1,10 @@+{-# OPTIONS -cpp #-}++module Version where++#if __GLASGOW_HASKELL__ >= 900+f :: Int+#else+f :: Integer+#endif+f = 1
+ corpora/vendored/other/cpp/preprocessor-turned-on-by-a-flag.hs view
@@ -0,0 +1,9 @@+{-# OPTIONS -cpp #-}+module Version where++#if __GLASGOW_HASKELL__ >= 900+f :: Int+#else+f :: Integer+#endif+f = 1
+ corpora/vendored/other/edition-named-by-the-module-out.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE GHC2024 #-}++module Kiln.Edition where++-- GHC compiles a module under the edition it names, so LambdaCase, which+-- GHC2024 turns on, is on.+firing :: Int -> Int+firing = \case+ 0 -> 1+ n -> n
+ corpora/vendored/other/edition-named-by-the-module.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE GHC2024 #-}+module Kiln.Edition where++-- GHC compiles a module under the edition it names, so LambdaCase, which+-- GHC2024 turns on, is on.+firing :: Int -> Int+firing = \case+ 0 -> 1+ n -> n
+ corpora/vendored/other/editions-in-written-order-out.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE Haskell2010 #-}+{-# LANGUAGE GHC2021 #-}++module Kiln.Edition where++-- GHC compiles a module under the edition named last.+fire :: Int+fire = 1
+ corpora/vendored/other/editions-in-written-order.hs view
@@ -0,0 +1,7 @@+{-# LANGUAGE Haskell2010 #-}+{-# LANGUAGE GHC2021 #-}+module Kiln.Edition where++-- GHC compiles a module under the edition named last.+fire :: Int+fire = 1
corpora/vendored/other/extension-taken-back-from-the-edition-out.hs view
@@ -1,5 +1,5 @@-{-# LANGUAGE TypeOperators #-} {-# LANGUAGE NoStarIsType #-}+{-# LANGUAGE TypeOperators #-} module M (type (*)) where
+ corpora/vendored/other/extension-turned-off-above-one-implying-it-out.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE NoExplicitForAll #-}+{-# LANGUAGE RankNTypes #-}++module Kiln.Rank where++-- RankNTypes turns ExplicitForAll back on, so with it named last, a forall+-- can be written.+apply :: (forall a. a -> a) -> (Int, Bool)+apply f = (f 1, f True)
+ corpora/vendored/other/extension-turned-off-above-one-implying-it.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE NoExplicitForAll #-}+{-# LANGUAGE RankNTypes #-}+module Kiln.Rank where++-- RankNTypes turns ExplicitForAll back on, so with it named last, a forall+-- can be written.+apply :: (forall a. a -> a) -> (Int, Bool)+apply f = (f 1, f True)
+ corpora/vendored/other/extension-turned-off-and-on-again-out.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE NoLambdaCase #-}+{-# LANGUAGE LambdaCase #-}++module Kiln.Again where++-- The name written last decides, so LambdaCase is on.+firing :: Int -> Int+firing = \case+ 0 -> 1+ n -> n
+ corpora/vendored/other/extension-turned-off-and-on-again.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE NoLambdaCase #-}+{-# LANGUAGE LambdaCase #-}+module Kiln.Again where++-- The name written last decides, so LambdaCase is on.+firing :: Int -> Int+firing = \case+ 0 -> 1+ n -> n
+ corpora/vendored/other/extension-turned-on-under-an-option-turning-it-off-out.hs view
@@ -0,0 +1,11 @@+{-# OPTIONS_GHC -XNoLambdaCase #-}+{-# LANGUAGE LambdaCase #-}++module Kiln.Case where++-- GHC reads the flags of the options pragmas and the names of the language+-- pragmas in the order they are written, so LambdaCase, named last, is on.+firing :: Int -> Int+firing = \case+ 0 -> 1+ n -> n
+ corpora/vendored/other/extension-turned-on-under-an-option-turning-it-off.hs view
@@ -0,0 +1,10 @@+{-# OPTIONS_GHC -XNoLambdaCase #-}+{-# LANGUAGE LambdaCase #-}+module Kiln.Case where++-- GHC reads the flags of the options pragmas and the names of the language+-- pragmas in the order they are written, so LambdaCase, named last, is on.+firing :: Int -> Int+firing = \case+ 0 -> 1+ n -> n
+ corpora/vendored/other/extensions-turned-on-by-flags-out.hs view
@@ -0,0 +1,15 @@+{-# OPTIONS_GHC -XLambdaCase -fbang-patterns #-}+{-# OPTIONS_GHC -fglasgow-exts #-}++module Flags where++classify :: Int -> String+classify = \case+ 0 -> "zero"+ _ -> "other"++strict :: Int -> Int+strict !n = n++both :: (forall a. a -> a) -> (Int, Bool)+both f = (f 1, f True)
+ corpora/vendored/other/extensions-turned-on-by-flags.hs view
@@ -0,0 +1,14 @@+{-# OPTIONS_GHC -XLambdaCase -fbang-patterns #-}+{-# OPTIONS_GHC -fglasgow-exts #-}+module Flags where++classify :: Int -> String+classify = \case+ 0 -> "zero"+ _ -> "other"++strict :: Int -> Int+strict !n = n++both :: (forall a. a -> a) -> (Int, Bool)+both f = (f 1, f True)
+ corpora/vendored/other/old-options-pragma-in-written-order-out.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS -Wno-orphans #-}+{-# OPTIONS_GHC -Wno-unused-imports #-}+{-# OPTIONS -Wno-name-shadowing #-}++module Kiln.Fire where++-- GHC reads OPTIONS as it reads OPTIONS_GHC, so the two keep the order they+-- are written in among each other.+fire :: Int+fire = 1
+ corpora/vendored/other/old-options-pragma-in-written-order.hs view
@@ -0,0 +1,12 @@+{-# OPTIONS_GHC -Wall #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS -Wno-orphans #-}+{-# OPTIONS_GHC -Wno-unused-imports #-}+{-# LANGUAGE BangPatterns #-}+{-# OPTIONS -Wno-name-shadowing #-}+module Kiln.Fire where++-- GHC reads OPTIONS as it reads OPTIONS_GHC, so the two keep the order they+-- are written in among each other.+fire :: Int+fire = 1
corpora/vendored/other/options-in-written-order-out.hs view
@@ -3,13 +3,15 @@ {-# OPTIONS_GHC -Wall #-} {-# OPTIONS_GHC -Wno-unused-imports #-} {-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -Wall #-} {-# OPTIONS_GHC -fplugin=GHC.TypeLits.Normalise #-} {-# OPTIONS_GHC -fplugin=GHC.TypeLits.KnownNat.Solver #-} module Kiln.Glaze where -- GHC reads the flags of the options pragmas in the order they are written,--- a later one overriding an earlier one, so they keep that order, and only a--- pragma written twice in a row is written once.+-- a later one overriding an earlier one, so they keep that order, and a+-- pragma written twice in a row stays twice, since some flags count each+-- time they are given. glaze :: Int glaze = 1
corpora/vendored/other/options-in-written-order.hs view
@@ -10,7 +10,8 @@ module Kiln.Glaze where -- GHC reads the flags of the options pragmas in the order they are written,--- a later one overriding an earlier one, so they keep that order, and only a--- pragma written twice in a row is written once.+-- a later one overriding an earlier one, so they keep that order, and a+-- pragma written twice in a row stays twice, since some flags count each+-- time they are given. glaze :: Int glaze = 1
+ corpora/vendored/other/pragma-below-the-module-line-out.hs view
@@ -0,0 +1,8 @@+module Kiln.Size where++{-# LANGUAGE OverloadedStrings #-}++-- GHC reads pragmas above the module line alone, so OverloadedStrings is+-- off, and the pragma stays where it is written.+size :: Int+size = length "abc"
+ corpora/vendored/other/pragma-below-the-module-line.hs view
@@ -0,0 +1,7 @@+module Kiln.Size where+{-# LANGUAGE OverloadedStrings #-}++-- GHC reads pragmas above the module line alone, so OverloadedStrings is+-- off, and the pragma stays where it is written.+size :: Int+size = length "abc"
+ corpora/vendored/other/prelude-asked-for-above-rebindable-syntax-out.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE ImplicitPrelude #-}+{-# LANGUAGE RebindableSyntax #-}++module Kiln.Rebound where++-- RebindableSyntax turns ImplicitPrelude off, so with it named last, the+-- Prelude is not imported, and this map is the only one in scope.+map :: ()+map = ()++glaze :: ()+glaze = map
+ corpora/vendored/other/prelude-asked-for-above-rebindable-syntax.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE ImplicitPrelude #-}+{-# LANGUAGE RebindableSyntax #-}+module Kiln.Rebound where++-- RebindableSyntax turns ImplicitPrelude off, so with it named last, the+-- Prelude is not imported, and this map is the only one in scope.+map :: ()+map = ()++glaze :: ()+glaze = map
+ corpora/vendored/other/splices-the-author-grouped-out.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TemplateHaskell #-}++module Store.Lenses where++makeLenses ''Config+-- A note written against the run it stands in, and staying inside it.+makeLenses ''Options++makeLenses ''State+#if defined(PRISMS)+makePrisms ''Event+#endif+makeLenses ''Event
+ corpora/vendored/other/splices-the-author-grouped.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TemplateHaskell #-}++module Store.Lenses where++makeLenses ''Config+-- A note written against the run it stands in, and staying inside it.+makeLenses ''Options++makeLenses ''State+#if defined(PRISMS)+makePrisms ''Event+#endif+makeLenses ''Event
+ corpora/vendored/other/string-literals-as-written-out.hs view
@@ -0,0 +1,41 @@+-- A string comes out as written wherever it is, in a pattern, a pragma or+-- a rule's name: its gaps line up under its opening quote, and nothing is+-- escaped or quoted that was not.+module Text.Written where++isLong :: String -> Bool+isLong+ "a very long string \+ \that goes on" = True+isLong _ = False++{-# DEPRECATED+ old+ "use the new one, \+ \which is better"+ #-}+old :: Int+old = 1++{-# WARNING new ["one", "two"] #-}+new :: Int+new = 2++{-# RULES "f/∘" forall x. f (g x) = h x #-}++f :: Int -> Int+f = id+{-# SCC f "f_label" #-}++g :: Int -> Int+g = id+{-# SCC g #-}++h :: Int -> Int+h = id++x :: Int+x = {-# SCC foo #-} 1++y :: Int+y = {-# SCC "bar" #-} 2
+ corpora/vendored/other/string-literals-as-written.hs view
@@ -0,0 +1,37 @@+-- A string comes out as written wherever it is, in a pattern, a pragma or+-- a rule's name: its gaps line up under its opening quote, and nothing is+-- escaped or quoted that was not.+module Text.Written where++isLong :: String -> Bool+isLong "a very long string \+ \that goes on" = True+isLong _ = False++{-# DEPRECATED old "use the new one, \+ \which is better" #-}+old :: Int+old = 1++{-# WARNING new ["one", "two"] #-}+new :: Int+new = 2++{-# RULES "f/∘" forall x. f (g x) = h x #-}++f :: Int -> Int+f = id+{-# SCC f "f_label" #-}++g :: Int -> Int+g = id+{-# SCC g #-}++h :: Int -> Int+h = id++x :: Int+x = {-# SCC foo #-} 1++y :: Int+y = {-# SCC "bar" #-} 2
src/Tilia/Cabal/Package.hs view
@@ -1,23 +1,23 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-} -- | Information coming from @.cabal@ files. module Tilia.Cabal.Package ( PackageProblem (..), describePackageProblem, readPackageDescription,+ parsePackageDescription, declaredModules,+ packageSourceDirs,+ packageExtensions, PackageReader, newPackageReader, ComponentSection (..), BranchSections,- libraryBranches,- executableBranches,- suiteBranches,- benchmarkBranches,+ Kind (..),+ packageOf,+ packageComponents, addedSources, sourceExtensions, isDeclaredModule,@@ -27,14 +27,17 @@ where import Control.Concurrent.MVar (modifyMVar, newMVar)+import Control.Monad ((<=<)) import Data.ByteString qualified as BS+import Data.Either (rights)+import Data.Foldable (toList) import Data.Generics.Schemes (listify) import Data.IORef-import Data.List (sortOn)+import Data.List (nub, sortOn) import Data.List.NonEmpty qualified as NE import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map-import Data.Maybe (fromMaybe)+import Data.Maybe (fromMaybe, mapMaybe) import Data.Ord (Down (..)) import Data.Set (Set) import Data.Set qualified as Set@@ -53,20 +56,20 @@ Executable (..), GenericPackageDescription (..), Library (..),+ PackageDescription (..), PackageFlag (..), TestSuite (..), TestSuiteInterface (..),+ pkgName,+ unPackageName,+ unUnqualComponentName, ) import Distribution.PackageDescription.Parsec (parseGenericPackageDescription)-import Distribution.Parsec (showPError, simpleParsec)+import Distribution.Parsec (PErrorWithSource (..), showPError, simpleParsec) import Distribution.System (buildArch, buildOS) import Distribution.Types.BuildInfo (usedExtensions)-import Distribution.Types.CondTree (mapTreeConstrs, simplifyCondTree)-#if MIN_VERSION_Cabal_syntax(3, 14, 0)+import Distribution.Types.CondTree (simplifyCondTree) import Distribution.Utils.Path (SymbolicPathX, getSymbolicPath)-#else-import Distribution.Utils.Path (getSymbolicPath)-#endif import Distribution.Version (Version, withinRange) import GHC.Driver.Session qualified as GHC import GHC.LanguageExtensions.Type (Extension)@@ -82,10 +85,10 @@ takeExtension, (</>), )-import Tilia.Cabal.Project (cabalFilesIn)-import Tilia.Pragma (applyExtensionName, lookupEdition)+import Tilia.Cabal.Project (cabalFilesIn, startingDirectory)+import Tilia.Pragma (Defaults, defaultsOf, lookupEdition, nameSwitch) import Tilia.Process (readProgramOutput)-import Tilia.Utils (attempted, quietly)+import Tilia.Utils (attempted, pathUnder, quietly, tshow) -- | Why a file's extensions could not be settled. data PackageProblem@@ -113,15 +116,18 @@ T.pack file <> " names no component whose hs-source-dirs holds it" -- | What we retain from reading a .cabal file.-type PackageReader = FilePath -> IO (Either PackageProblem [Extension])+type PackageReader = FilePath -> IO (Either PackageProblem Defaults) -- | A 'PackageReader' that remembers what it has already worked out. newPackageReader :: -- | The compiler's version, where the build plan names it, which spares -- asking the compiler. Maybe Version ->+ -- | The @.cabal@ files of the project, which a module no @.cabal@ file+ -- above it claims is looked for in.+ [FilePath] -> IO PackageReader-newPackageReader planned = do+newPackageReader planned project = do covering <- newIORef Map.empty described <- newIORef Map.empty known <- newMVar (Just <$> planned)@@ -130,26 +136,25 @@ Nothing -> do version <- compilerVersion pure (Just version, version)+ elsewhere file problem = do+ found <- traverse (componentBranchesOf compiler described) project+ pure $ case claiming file (concat (rights found)) of+ Just c -> Right (componentExtensions c)+ Nothing -> Left problem pure $ \path -> quietly (Left NoPackageFile) $ do file <- canonicalizePath path from <- startingDirectory file findCabalFile covering from >>= \case- Nothing -> pure (Left NoPackageFile)+ Nothing -> elsewhere file NoPackageFile Just cabalFile -> componentBranchesOf compiler described cabalFile >>= \case Left problem -> pure (Left problem) Right components | equalFilePath file (takeDirectory cabalFile </> setupScript) ->- pure (Right (GHC.languageExtensions Nothing))- | otherwise -> pure $ case claiming file components of- Just c -> Right (componentExtensions c)- Nothing -> Left (FileUnclaimed cabalFile)---- | Where to start looking for a @.cabal@ file.-startingDirectory :: FilePath -> IO FilePath-startingDirectory path = do- isDirectory <- quietly False (doesDirectoryExist path)- pure (if isDirectory then path else takeDirectory path)+ pure (Right (defaultsOf (GHC.languageExtensions Nothing) []))+ | Just c <- claiming file components ->+ pure (Right (componentExtensions c))+ | otherwise -> elsewhere file (FileUnclaimed cabalFile) -- | One branch of a component, with everything in force in it already -- worked out.@@ -162,8 +167,8 @@ -- | Its entry points, relative to its source directories in the same -- way but with their extensions. componentEntries :: Set FilePath,- -- | The extensions it puts in force.- componentExtensions :: [Extension]+ -- | What it puts in force.+ componentExtensions :: Defaults } -- | Which branch holds the file, of those whose directories cover it.@@ -175,11 +180,14 @@ where covered = not . null . covering named c = any (declares c) (covering c)- declares c d =- isDeclaredModule (componentModules c) (componentEntries c) (under d)- under d = joinPath (drop (length (splitDirectories d)) (splitDirectories file))- nearness = maximum . fmap length . covering- covering c = [d | d <- componentDirs c, d `covers` file]+ declares c (_, under) =+ isDeclaredModule (componentModules c) (componentEntries c) (joinPath under)+ nearness = maximum . fmap (length . fst) . covering+ covering c =+ [ (d, under)+ | d <- componentDirs c,+ Just under@(_ : _) <- [pathUnder d file]+ ] -- | Is a file, named relative to a source directory, one of the modules or -- entry points declared there?@@ -194,14 +202,6 @@ takeExtension path `elem` sourceExtensions && (Set.member path entries || Set.member (dropExtension path) modules) --- | Whether a file is somewhere under a directory.-covers :: FilePath -> FilePath -> Bool-covers directory file = go (splitDirectories directory) (splitDirectories file)- where- go [] (_ : _) = True- go (d : ds) (f : fs) = equalFilePath d f && go ds fs- go _ _ = False- -- | The nearest @.cabal@ file at or above a directory. findCabalFile :: -- | What is known already, by directory: the file covering it, or@@ -263,13 +263,21 @@ readPackageDescription cabalFile = attempted (BS.readFile cabalFile) >>= \case Left why -> pure (Left (PackageUnreadable cabalFile why))- Right bytes ->- pure $ case snd (runParseResult (parseGenericPackageDescription bytes)) of- Left (_, complaints) ->- Left (PackageMalformed cabalFile (fmap said (NE.toList complaints)))- Right description -> Right description+ Right bytes -> pure (parsePackageDescription cabalFile bytes)++-- | Parse what a @.cabal@ file holds, or say why it cannot be.+parsePackageDescription ::+ -- | Where it comes from, named in what goes wrong.+ FilePath ->+ BS.ByteString ->+ Either PackageProblem GenericPackageDescription+parsePackageDescription cabalFile bytes =+ case snd (runParseResult (parseGenericPackageDescription bytes)) of+ Left (_, complaints) ->+ Left (PackageMalformed cabalFile (fmap said (NE.toList complaints)))+ Right description -> Right description where- said = T.pack . showPError cabalFile+ said = T.pack . showPError cabalFile . perror -- | The value a build of a package on this platform gives each variable its -- conditions ask about.@@ -339,14 +347,32 @@ -- | Every branch of every component of a package, in the order declared. packageBranches :: GenericPackageDescription -> [BranchSections] packageBranches described =+ [b | (_, _, branches) <- packageComponents described, b <- branches]++-- | The kinds of component a @.cabal@ file can declare, and the setup+-- script beside it.+data Kind = Lib | Exe | Test | Bench | Setup+ deriving (Eq, Ord, Show)++-- | Every component of a package, in the order declared: its kind, its+-- name, which for the main library is the package's own, and its branches.+packageComponents :: GenericPackageDescription -> [(Kind, Text, [BranchSections])]+packageComponents described = concat- [ foldMap libraryBranches (condLibrary described),- concatMap (libraryBranches . snd) (condSubLibraries described),- concatMap (executableBranches . snd) (condExecutables described),- concatMap (suiteBranches . snd) (condTestSuites described),- concatMap (benchmarkBranches . snd) (condBenchmarks described)+ [ [(Lib, packageOf described, libraryBranches t) | t <- toList (condLibrary described)],+ [(Lib, nameOf n, libraryBranches t) | (n, t) <- condSubLibraries described],+ [(Exe, nameOf n, executableBranches t) | (n, t) <- condExecutables described],+ [(Test, nameOf n, suiteBranches t) | (n, t) <- condTestSuites described],+ [(Bench, nameOf n, benchmarkBranches t) | (n, t) <- condBenchmarks described] ]+ where+ nameOf = T.pack . unUnqualComponentName +-- | The name of a package.+packageOf :: GenericPackageDescription -> Text+packageOf =+ T.pack . unPackageName . pkgName . package . packageDescription+ -- | Every module any branch of a package declares, with where it would be -- written relative to the package, without an extension. declaredModules :: GenericPackageDescription -> [(Text, FilePath)]@@ -357,6 +383,31 @@ (sub, name) <- sectionModules section ] +-- | Every directory any branch of a package names for its files, and then+-- the package's own.+packageSourceDirs :: GenericPackageDescription -> [FilePath]+packageSourceDirs described = nub (named <> ["."])+ where+ named =+ [ getSymbolicPath d+ | branch <- packageBranches described,+ d <- hsSourceDirs (sectionInfo (ownSection branch))+ ]++-- | What any branch of a package puts in force, all together: every edition+-- it names and every extension it turns on or off, in order.+packageExtensions :: GenericPackageDescription -> Defaults+packageExtensions described =+ defaultsOf+ baseline+ (mapMaybe extensionSwitch (concatMap usedExtensions infos))+ where+ infos =+ [sectionInfo (ownSection branch) | branch <- packageBranches described]+ baseline = case mapMaybe (ghcLanguage <=< defaultLanguage) infos of+ [] -> GHC.languageExtensions (Just GHC.Haskell98)+ editions -> nub (concatMap (GHC.languageExtensions . Just) editions)+ -- | What one branch of a component declares. data ComponentSection = ComponentSection { -- | Its build settings.@@ -388,17 +439,17 @@ } -- | Every branch of a library.-libraryBranches :: CondTree ConfVar c Library -> [BranchSections]+libraryBranches :: CondTree ConfVar Library -> [BranchSections] libraryBranches = branchesOf $ \l -> declaring (libBuildInfo l) (exposedModules l <> signatures l) [] -- | Every branch of an executable.-executableBranches :: CondTree ConfVar c Executable -> [BranchSections]+executableBranches :: CondTree ConfVar Executable -> [BranchSections] executableBranches = branchesOf $ \e -> declaring (buildInfo e) [] [entryPoint (modulePath e)] -- | Every branch of a test suite.-suiteBranches :: CondTree ConfVar c TestSuite -> [BranchSections]+suiteBranches :: CondTree ConfVar TestSuite -> [BranchSections] suiteBranches = branchesOf $ \s -> case testInterface s of TestSuiteExeV10 _ path -> declaring (testBuildInfo s) [] [entryPoint path]@@ -406,7 +457,7 @@ _ -> declaring (testBuildInfo s) [] [] -- | Every branch of a benchmark.-benchmarkBranches :: CondTree ConfVar c Benchmark -> [BranchSections]+benchmarkBranches :: CondTree ConfVar Benchmark -> [BranchSections] benchmarkBranches = branchesOf $ \b -> case benchmarkInterface b of BenchmarkExeV10 _ path -> declaring (benchmarkBuildInfo b) [] [entryPoint path]@@ -416,13 +467,12 @@ branchesOf :: (Semigroup a) => (a -> ComponentSection) ->- CondTree ConfVar c a ->+ CondTree ConfVar a -> [BranchSections] branchesOf f root = go mempty root where built valueOf =- sectionInfo . f . snd $- simplifyCondTree valueOf (mapTreeConstrs (const ()) root)+ sectionInfo (f (simplifyCondTree valueOf root)) go inherited node = let own = f (condTreeData node) in BranchSections inherited own built@@ -480,16 +530,8 @@ | otherwise = a </> b -- | The path of an entry point.------ Cabal 3.14 made a component's entry point a symbolic path; before that--- it was already the plain path we want.-#if MIN_VERSION_Cabal_syntax(3, 14, 0) entryPoint :: SymbolicPathX allowAbsolute from to -> FilePath entryPoint = normalise . getSymbolicPath-#else-entryPoint :: FilePath -> FilePath-entryPoint = normalise-#endif -- | The setup script of a package, beside its @.cabal@ file. --@@ -504,18 +546,22 @@ [] -> ["."] ds -> ds --- | The extensions a component puts in force, before any module's pragmas.-extensionsInForce :: BuildInfo -> [Extension]+-- | What a component puts in force, before any module's pragmas.+extensionsInForce :: BuildInfo -> Defaults extensionsInForce bi =- foldl apply (GHC.languageExtensions edition) (usedExtensions bi)+ defaultsOf+ (GHC.languageExtensions edition)+ (mapMaybe extensionSwitch (usedExtensions bi)) where edition = ghcLanguage (fromMaybe Cabal.Haskell98 (defaultLanguage bi))- apply acc = \case- Cabal.EnableExtension e -> applyExtensionName acc (named e)- Cabal.DisableExtension e -> applyExtensionName acc ("No" <> named e)- _ -> acc- named = T.pack . show +-- | GHC's reading of an extension Cabal turns on or off, where GHC knows it.+extensionSwitch :: Cabal.Extension -> Maybe (GHC.OnOff Extension)+extensionSwitch = \case+ Cabal.EnableExtension e -> nameSwitch (tshow e)+ Cabal.DisableExtension e -> nameSwitch ("No" <> tshow e)+ _ -> Nothing+ -- | GHC's name for a language edition, when it has one. ghcLanguage :: Cabal.Language -> Maybe GHC.Language-ghcLanguage = lookupEdition . T.pack . show+ghcLanguage = lookupEdition . tshow
src/Tilia/Cabal/Plan.hs view
@@ -25,7 +25,6 @@ -- * Readiness Readiness (..), PlanComponent (..),- spellPlanComponent, plannedComponents, planPathFor, checkReadiness,@@ -51,7 +50,8 @@ (.:?), ) import Data.Aeson.Types (parseMaybe)-import Data.Choice (Choice, isTrue)+import Data.Choice (Choice, isFalse)+import Data.Containers.ListUtils (nubOrdOn) import Data.Foldable (toList, traverse_) import Data.List qualified import Data.List.NonEmpty (NonEmpty ((:|)))@@ -66,6 +66,9 @@ import Data.Text.Encoding qualified as T import Data.Text.Read qualified as T import Data.Time (UTCTime, getCurrentTime)+import Distribution.Package (pkgName, pkgVersion, unPackageName)+import Distribution.PackageDescription (package, packageDescription)+import Distribution.Pretty (prettyShow) import Distribution.Version (Version, mkVersion) import GHC.IO.Handle (hDuplicate) import System.Directory@@ -91,12 +94,13 @@ std_out, waitForProcess, )+import Tilia.Cabal.Package (readPackageDescription) import Tilia.Cabal.Project (cabalFilesIn) import Tilia.Cpp.Macros (Macros (..)) import Tilia.Fixity.Cache import Tilia.Fixity.PackageDb import Tilia.Process (readProgramOutput)-import Tilia.Utils (quietly, readFileText, sha256Of)+import Tilia.Utils (quietly, sha256Of) ---------------------------------------------------------------------------- -- The plan@@ -250,7 +254,8 @@ ) (_, Just "local") -> LocalPackage (maybe "" T.unpack (join sourcePath)) (_, Just "source-repo") -> SourceRepo- -- Anything else is treated as already present.+ -- Anything else, a local or remote tarball among them, is+ -- treated as already present. _ -> PreExisting } @@ -320,15 +325,12 @@ | e <- Data.List.sort entries, (ppName p <> "-") `T.isPrefixOf` T.pack e ]- isThePackage p dir = quietly False $ do- contents <- readFileText (dir </> T.unpack (ppName p) <> ".cabal")- pure (maybe False (describes p) contents)- describes p text =- any (names "name:" (ppName p)) (T.lines text)- && any (names "version:" (ppVersion p)) (T.lines text)- names field value line = case T.stripPrefix field (T.toLower (T.strip line)) of- Just rest -> T.strip rest == T.toLower value- Nothing -> False+ isThePackage p dir =+ either (const False) (describes p . package . packageDescription)+ <$> readPackageDescription (dir </> T.unpack (ppName p) <> ".cabal")+ describes p identifier =+ T.pack (unPackageName (pkgName identifier)) == ppName p+ && T.pack (prettyShow (pkgVersion identifier)) == ppVersion p -- | The version macros a plan settles. macrosOf :: BuildPlan -> Macros@@ -453,12 +455,8 @@ PlanStale [FilePath] | -- | The plan says nothing about components the run is about to format. PlanNarrow [Text]- | -- | The plan is there, and some packages have neither been downloaded- -- nor built. The names are listed so that a caller can say what it is- -- waiting for.- --- -- Built counts as having them: their interfaces provide everything the- -- source tarball would.+ | -- | The plan is there, and the packages named have neither been+ -- downloaded nor built. SourcesMissing [Text] deriving (Eq, Show) @@ -467,30 +465,40 @@ planPathFor projectDir = projectDir </> "dist-newstyle" </> "cache" </> "plan.json" -- | Check what is missing.------ One read of the plan and one @stat@ per package, so this is fast enough--- to run before every format. checkReadiness :: Choice "useCache" -> [PlanComponent] -> FilePath -> IO Readiness checkReadiness caching wanted projectDir =- readBuildPlan (planPathFor projectDir) >>= \case- Left _ -> pure PlanMissing- Right plan -> do- solved <- cachedSolveTime =<< openCache caching =<< tokenForBuildPlan plan- newer <- filesNewerThanPlan plan projectDir solved- let covered = plannedComponents plan- missing = [spellPlanComponent c | c <- wanted, c `notElem` covered]- case (newer, missing) of- (_ : _, _) -> pure (PlanStale newer)- ([], _ : _) -> pure (PlanNarrow missing)- ([], []) ->- sourcesShortOf caching plan >>= \case- [] -> pure Ready- ps -> pure (SourcesMissing (fmap ppName ps))+ readinessOf caching wanted projectDir =<< readBuildPlan (planPathFor projectDir) +-- | 'checkReadiness', given the plan as read.+readinessOf ::+ -- | Whether to use the cache.+ Choice "useCache" ->+ -- | The components the run is about to format.+ [PlanComponent] ->+ -- | The project whose plan it is.+ FilePath ->+ -- | Its plan, or why it could not be read.+ Either Text BuildPlan ->+ IO Readiness+readinessOf caching wanted projectDir = \case+ Left _ -> pure PlanMissing+ Right plan -> do+ cache <- openCache caching =<< tokenForBuildPlan plan+ solved <- cachedSolveTime cache+ newer <- filesNewerThanPlan plan projectDir solved+ let covered = plannedComponents plan+ missing = [spellPlanComponent c | c <- wanted, c `notElem` covered]+ case (newer, missing) of+ (_ : _, _) -> pure (PlanStale newer)+ ([], _ : _) -> pure (PlanNarrow missing)+ ([], []) ->+ sourcesShortOf cache plan >>= \case+ [] -> pure Ready+ ps -> pure (SourcesMissing (fmap ppName ps))+ -- | The packages the plan expects to fetch whose sources are not here.-sourcesShortOf :: Choice "useCache" -> BuildPlan -> IO [PlanPackage]-sourcesShortOf caching plan = do- cache <- openCache caching =<< tokenForBuildPlan plan+sourcesShortOf :: Cache -> BuildPlan -> IO [PlanPackage]+sourcesShortOf cache plan = do tarballs <- filter (isFetchable . fst) <$> plannedTarballs cache plan absent <- fmap fst <$> filterM (fmap not . doesFileExist . snd) tarballs if null absent@@ -593,7 +601,7 @@ IO (Either Text ()) prepareWith caching downloading cabal futility wanted projectDir = \case Ready -> pure (Right ())- SourcesMissing _ -> fetch+ SourcesMissing short -> fetch short PlanMissing -> solveThenFetch PlanStale _ -> solveThenFetch PlanNarrow _ ->@@ -606,24 +614,24 @@ cabal (args <> wholeProject) >>= \case Right () -> pure (Right ()) Left _ -> cabal args- fetch- | isTrue downloading = tryWholeProject ["build", ":all", "--only-download"]- | otherwise = pure (Right ())- fetchWhatIsShort =- readBuildPlan (planPathFor projectDir) >>= \case- Left _ -> pure (Right ())- Right plan -> do- short <- fmap ppName <$> sourcesShortOf caching plan+ fetch short+ | isFalse downloading = pure (Right ())+ | otherwise = do refused <- fetchWasFutileFor futility- if null short || all (`elem` refused) short+ if all (`elem` refused) short then pure (Right ()) else- fetch >>= \case+ tryWholeProject ["build", ":all", "--only-download"] >>= \case Left err -> pure (Left err)- Right () -> do- left <- sourcesShortOf caching plan- rememberFutileFetch futility (fmap ppName left)- pure (Right ())+ Right () ->+ withPlan ((Right () <$) . rememberFutileFetch futility)+ fetchWhatIsShort = withPlan fetch+ withPlan use =+ readBuildPlan (planPathFor projectDir) >>= \case+ Left _ -> pure (Right ())+ Right plan -> do+ cache <- openCache caching =<< tokenForBuildPlan plan+ use . fmap ppName =<< sourcesShortOf cache plan solveThenFetch = do started <- getCurrentTime tryWholeProject ["build", ":all", "--dry-run"] >>= \case@@ -631,7 +639,7 @@ Right () -> do rememberSolveTime futility started checkReadiness caching wanted projectDir >>= \case- SourcesMissing _ -> fetch+ SourcesMissing short -> fetch short PlanNarrow _ -> rememberFutileSolve futility >> fetchWhatIsShort _ -> pure (Right ()) @@ -669,8 +677,9 @@ -- | The build plan to use. BuildPlan -> IO ()-fetchUninstalled caching projectDir plan =- sourcesShortOf caching plan >>= \case+fetchUninstalled caching projectDir plan = do+ cache <- openCache caching =<< tokenForBuildPlan plan+ sourcesShortOf cache plan >>= \case [] -> pure () short -> void . runCabal projectDir $@@ -691,31 +700,37 @@ FilePath -> IO (Either Text BuildPlan) loadPlan caching downloading wanted projectDir = do- readiness <- checkReadiness caching wanted projectDir- prepareWith- caching- downloading- (runCabal projectDir)- (futilityFor caching projectDir)- wanted- projectDir- readiness- >>= \case- Left err -> pure (Left err)- _ -> readBuildPlan (planPathFor projectDir)+ written <- readBuildPlan (planPathFor projectDir)+ readiness <- readinessOf caching wanted projectDir written+ case (readiness, written) of+ (Ready, Right plan) -> pure (Right plan)+ _ ->+ prepareWith+ caching+ downloading+ (runCabal projectDir)+ (futilityFor caching projectDir)+ wanted+ projectDir+ readiness+ >>= \case+ Left err -> pure (Left err)+ _ -> readBuildPlan (planPathFor projectDir) -- | Every planned package whose source could be in the package cache, with -- where that would be: under the package cache @cabal@ last named, unless--- a tarball it downloads is not there, when @cabal@ is asked again.+-- a tarball it downloads is not there and a fetch has not already failed+-- to bring it in, when @cabal@ is asked again. plannedTarballs :: Cache -> BuildPlan -> IO [(PlanPackage, FilePath)] plannedTarballs cache plan = cachedPackageCache cache >>= \case Just remembered -> do tarballs <- under remembered+ refused <- cachedFutileFetch cache absent <- filterM (fmap not . doesFileExist . snd)- (filter (isFetchable . fst) tarballs)+ [t | t@(p, _) <- tarballs, isFetchable p, ppName p `notElem` refused] if null absent then pure tarballs else asked Nothing -> asked where@@ -727,7 +742,7 @@ repos <- quietly [] (Data.List.sort <$> listDirectory cacheRoot) traverse (\p -> (,) p <$> tarballFor cacheRoot repos p)- [p | p <- bpPackages plan, not (isLocal p)]+ (nubOrdOn (\p -> (ppName p, ppVersion p)) [p | p <- bpPackages plan, not (isLocal p)]) isLocal p = case ppSource p of LocalPackage _ -> True CheckedOut _ -> True
src/Tilia/Cabal/Project.hs view
@@ -7,6 +7,7 @@ Marker (..), markerFile, findProjectRoot,+ startingDirectory, cabalFilesIn, ) where@@ -49,30 +50,37 @@ -- | Walk upwards from a file or directory looking for a project. findProjectRoot :: FilePath -> IO (Maybe ProjectRoot) findProjectRoot start = quietly Nothing $ do- from <- startingDirectory+ from <- startingDirectory =<< canonicalizePath start found <- traverse markersIn (from : ancestorsOf from) pure (listToMaybe (concatMap fst found <> concatMap snd found)) where- startingDirectory = do- absolute <- canonicalizePath start- isDirectory <- doesDirectoryExist absolute- pure (if isDirectory then absolute else takeDirectory absolute)- ancestorsOf directory = let parent = takeDirectory directory in if parent == directory then [] else parent : ancestorsOf parent markersIn directory = quietly ([], []) $ do entries <- listDirectory directory- packages <- cabalFilesIn directory+ packages <- cabalFilesAmong directory entries pure ( [ProjectRoot directory ProjectFile | "cabal.project" `elem` entries], [ProjectRoot directory (PackageFile (takeFileName p)) | p <- take 1 packages] ) +-- | Where a walk upwards from a path starts: the path itself if it names a+-- directory, else the directory holding it.+startingDirectory :: FilePath -> IO FilePath+startingDirectory path = do+ isDirectory <- quietly False (doesDirectoryExist path)+ pure (if isDirectory then path else takeDirectory path)+ -- | The @.cabal@ files sitting directly in a directory, in order. cabalFilesIn :: FilePath -> IO [FilePath]-cabalFilesIn directory = quietly [] $ do- entries <- listDirectory directory- let named = sort (filter (".cabal" `isSuffixOf`) entries)- filterM doesFileExist (fmap (directory </>) named)+cabalFilesIn directory =+ quietly [] (cabalFilesAmong directory =<< listDirectory directory)++-- | The @.cabal@ files among a directory's entries, in order.+cabalFilesAmong :: FilePath -> [FilePath] -> IO [FilePath]+cabalFilesAmong directory entries =+ filterM+ doesFileExist+ (fmap (directory </>) (sort (filter (".cabal" `isSuffixOf`) entries)))
src/Tilia/Cabal/Target.hs view
@@ -9,11 +9,12 @@ parseTarget, Component (..), Wanted (..),- TargetProblem (..),+ TargetProblem, describeTargetProblem, componentsOfTarget, componentInPlan, filesOfComponents,+ packageFilesOf, ) where @@ -31,14 +32,8 @@ import Data.Text.Encoding qualified as T import Distribution.Fields.Field (Field (..), FieldLine (..), Name (..)) import Distribution.Fields.Parser (readFields)-import Distribution.PackageDescription- ( GenericPackageDescription (..),- PackageDescription (..),- unPackageName,- unUnqualComponentName,- )-import Distribution.Types.PackageId (PackageIdentifier (..))-import System.Directory (doesFileExist, listDirectory)+import Distribution.PackageDescription (GenericPackageDescription (..))+import System.Directory (canonicalizePath, doesFileExist, listDirectory) import System.FilePath ( dropTrailingPathSeparator, normalise,@@ -47,22 +42,21 @@ ) import Tilia.Cabal.Package ( ComponentSection (..),+ Kind (..), PackageProblem, addedSources,- benchmarkBranches, describePackageProblem,- executableBranches, isDeclaredModule, joined,- libraryBranches,+ packageComponents,+ packageOf, readPackageDescription, setupScript,- suiteBranches, ) import Tilia.Cabal.Plan (PlanComponent (..)) import Tilia.Cabal.Project (Marker (..), ProjectRoot (..), cabalFilesIn, markerFile)-import Tilia.Ignore (belowRoot, ignoreFilesAbove, isIgnored)-import Tilia.Utils (fieldWords, quietly)+import Tilia.Ignore (withoutIgnored)+import Tilia.Utils (fieldWords, quietly, tshow) -- | Which components a run was asked for. data Target@@ -74,11 +68,6 @@ Qualified (Maybe Text) Kind Text deriving (Eq, Show) --- | The kinds of component a @.cabal@ file can declare, and the setup--- script beside it.-data Kind = Lib | Exe | Test | Bench | Setup- deriving (Eq, Ord, Show)- -- | Read a target as it was written on the command line. parseTarget :: String -> Either Text Target parseTarget written = case T.splitOn ":" (T.strip (T.pack written)) of@@ -91,7 +80,7 @@ where unrecognised = "unrecognised target "- <> T.pack (show written)+ <> tshow written <> ": expected all, a package or component name, or one of\ \ lib:, exe:, test:, bench: followed by a name" kindNamed = \case@@ -120,7 +109,7 @@ componentPackage :: Text, -- | Which kind it is. componentKind :: Kind,- -- | Its name, which for a library is the package's own.+ -- | Its name, which for the main library is the package's own. componentName :: Text, -- | The directory its @.cabal@ file sits in. componentRoot :: FilePath,@@ -203,8 +192,9 @@ -- | How a build plan names this component, if it has an entry of its own. ----- A plan writes a library as @lib@ and everything else as its kind and--- name, which is not quite how a target is written: see 'spellComponent'.+-- A plan writes the main library as @lib@ and everything else, sub-libraries+-- among them, as its kind and name, which is not quite how a target is+-- written: see 'spellComponent'. -- A setup script has an entry only when the build type is @Custom@, so a -- plan is never expected to say anything about it. componentInPlan :: Component -> Maybe PlanComponent@@ -215,7 +205,7 @@ PlanComponent { pcPackage = componentPackage c, pcName = case kind of- Lib -> "lib"+ Lib | componentName c == componentPackage c -> "lib" _ -> spellKind kind <> ":" <> componentName c } @@ -240,22 +230,17 @@ -- the ones the project's @.tiliaignore@ files exclude. filesOfComponents :: ProjectRoot -> [Component] -> IO [FilePath] filesOfComponents root components = do- candidates <- concat <$> traverse present (Map.toList directories)- let inProject =- [ (path, below)- | path <- candidates,- Just below <- [belowRoot (prPath root) path]+ directories <-+ Map.fromListWith (<>)+ <$> sequence+ [ (,wanted) <$> quietly d (canonicalizePath d)+ | c <- components,+ (written, wanted) <- Map.toList (componentSources c),+ let d = joined (componentRoot c) written ]- ignoreFiles <- ignoreFilesAbove (prPath root) (fmap snd inProject)- let excluded = Set.fromList [path | (path, below) <- inProject, isIgnored ignoreFiles below]- pure (sort (filter (`Set.notMember` excluded) candidates))+ candidates <- concat <$> traverse present (Map.toList directories)+ sort <$> withoutIgnored (prPath root) candidates where- directories =- Map.unionsWith- (<>)- [ Map.mapKeys (joined (asDirectory (componentRoot c))) (componentSources c)- | c <- components- ] present (directory, wanted) = do entries <- quietly [] (listDirectory directory) filterM@@ -302,11 +287,11 @@ packageToCabalFile root entry = do paths <- packageGlobToCabalFiles- root- (fmap T.unpack (T.split (== '/') (T.dropWhile (== '.') stripped)))+ (if "/" `T.isPrefixOf` written then "/" else root)+ (fmap T.unpack (T.split (== '/') written)) concat <$> traverse asPackage paths where- stripped = T.dropWhile (== '/') (T.strip entry)+ written = T.strip entry asPackage path | ".cabal" `isSuffixOf` path = do there <- quietly False (doesFileExist path)@@ -358,11 +343,6 @@ | hasSetup ] --- | The name of a package.-packageOf :: GenericPackageDescription -> Text-packageOf =- T.pack . unPackageName . pkgName . Distribution.PackageDescription.package . packageDescription- -- | The directories a branch's declarations may be found in under one -- source directory. sourcesIn :: FilePath -> ComponentSection -> Map FilePath Wanted@@ -376,21 +356,11 @@ -- | The components of a parsed @.cabal@ file, in the order declared. declaredComponents :: FilePath -> GenericPackageDescription -> [Component] declaredComponents root described =- concat- [ foldMap- (pure . made Lib package . libraryBranches)- (condLibrary described),- [made Lib (nameOf n) (libraryBranches t) | (n, t) <- condSubLibraries described],- [made Exe (nameOf n) (executableBranches t) | (n, t) <- condExecutables described],- [made Test (nameOf n) (suiteBranches t) | (n, t) <- condTestSuites described],- [made Bench (nameOf n) (benchmarkBranches t) | (n, t) <- condBenchmarks described]- ]+ [made kind name branches | (kind, name, branches) <- packageComponents described] where- package = packageOf described- nameOf = T.pack . unUnqualComponentName made kind name branches = Component- { componentPackage = package,+ { componentPackage = packageOf described, componentKind = kind, componentName = name, componentRoot = root,
src/Tilia/Comments.hs view
@@ -39,7 +39,17 @@ import Data.Text qualified as T import GHC.Parser.Lexer qualified as GHC import GHC.Types.SrcLoc qualified as GHC-import Tilia.Source.Lines (Lines, blankAt, directiveOnLine, lineAt, lineTexts)+import Tilia.Pragma (withoutSkippedComments)+import Tilia.Source.Lines+ ( Lines,+ blankAt,+ columnOf,+ contentColumn,+ directiveOnLine,+ lineAt,+ lineTexts,+ offsetOf,+ ) import Tilia.Span (Span (..), covers) import Tilia.Span.Ghc (spanOfReal) @@ -86,7 +96,8 @@ -- | What was on the line above a comment. data Above- = -- | The comment begins on the first line of the file.+ = -- | Nothing: the comment begins on the first line of the file, or the+ -- line above is not in this configuration. TopOfFile | -- | An empty line. BlankLine@@ -141,21 +152,20 @@ Nothing -> TopOfFile Just l | T.all isSpace l -> BlankLine- | otherwise -> ContentAt (contentStart l)+ | otherwise -> ContentAt (contentColumn l) before' = maybe T.empty (T.stripEnd . T.take startColumn) openingLine codeBeforeStopsAt = do l <- openingLine if T.null before' then Nothing else Just (columnOf l (T.length before')) nextLine = listToMaybe- [ (n, contentStart l)+ [ (n, contentColumn l) | (n, Just l) <- takeWhile (isJust . snd) [(n, lineAt n ls) | n <- [spanEndLine spn + 1 ..]], not (T.all isSpace l || isJust (directiveOnLine l)) ]- contentStart l = columnOf l (T.length (T.takeWhile isSpace l)) followed = case lineAt (spanEndLine spn) ls of Just l -> not (T.all isSpace (T.drop (offsetOf l (spanEndColumn spn)) l)) Nothing -> False@@ -374,7 +384,7 @@ spanStartColumn = if n == spanStartLine s then spanStartColumn s- else columnOf l (T.length (T.takeWhile isSpace l)),+ else contentColumn l, spanEndLine = n, spanEndColumn = if n == spanEndLine s@@ -453,47 +463,28 @@ { -- | The name, upper-cased as GHC expects it, e.g. @LANGUAGE@. pragmaName :: Text, -- | Everything between the name and the closing @#-}@, with the- -- surrounding whitespace removed but nothing else touched.- pragmaBody :: Text+ -- surrounding whitespace removed, on one line, and without the comments+ -- GHC skips in a @LANGUAGE@ pragma.+ pragmaBody :: Text,+ -- | Whether it holds such a comment.+ pragmaCommented :: Bool } deriving (Eq, Show) -- | Recognise a pragma. commentPragma :: Comment -> Maybe Pragma commentPragma c = do- inner <- T.stripSuffix "#-}" =<< T.stripPrefix "{-#" oneLine- let (name, body) = T.break isSpace (T.stripStart inner)+ inner <- T.stripSuffix "#-}" =<< T.stripPrefix "{-#" (T.strip written)+ let uncommented = withoutSkippedComments inner+ (name, body) = T.break isSpace (T.stripStart uncommented) if T.null name then Nothing else Just Pragma { pragmaName = T.toUpper name,- pragmaBody = T.strip body+ pragmaBody = T.strip (T.unwords (fmap T.strip (T.lines body))),+ pragmaCommented = uncommented /= inner } where- oneLine = T.unwords (fmap T.strip (NE.toList (commentBody c)))---- | How many characters of a line come before the compiler's column.------ A column is not a character offset. The lexer counts a tab as advancing to--- the next multiple of eight, so a line with a tab in it has more columns--- than it has characters, and cutting the text at a column would cut in the--- wrong place. In a file indented with tabs that is every line.-offsetOf :: Text -> Int -> Int-offsetOf line column = T.length (T.take (walk 0 1) line)- where- walk i c- | c >= column = i- | i >= T.length line = i + (column - c)- | otherwise = walk (i + 1) (afterChar (T.index line i) c)---- | The compiler's column for the character at this offset.-columnOf :: Text -> Int -> Int-columnOf line offset = T.foldl' (flip afterChar) 1 (T.take offset line)---- | Where the column moves to once this character has been read.-afterChar :: Char -> Int -> Int-afterChar ch c- | ch == '\t' = ((c - 1) `div` 8 + 1) * 8 + 1- | otherwise = c + 1+ written = T.intercalate "\n" (NE.toList (commentBody c))
src/Tilia/Comments/Attach.hs view
@@ -9,7 +9,6 @@ ( attachComments, attachScopedComments, Margin (..),- noMargin, ) where
src/Tilia/Cpp.hs view
@@ -67,7 +67,7 @@ import Tilia.Source ( Lines, Written (..),- blankAt,+ blankAcross, comments, dropping, linesOf,@@ -317,7 +317,7 @@ upTo 0 ( go (concatMap (`nestedIn` 0) gss)- : [ if holdsNothing r && all holdsNothing (take 1 (gsBranches gs))+ : [ if blankAcross r written && all (`blankAcross` written) (take 1 (gsBranches gs)) then 0 else go (nestedIn gs i <> concatMap (`nestedIn` 0) others) | (k, gs) <- zip [0 :: Int ..] gss,@@ -332,7 +332,6 @@ ) ] )- holdsNothing (from, to) = all (`blankAt` written) [from .. to] upTo acc = \case _ | acc > limit -> acc [] -> acc
src/Tilia/Cpp/Directives.hs view
@@ -9,7 +9,7 @@ withoutRuledOut, withoutOpaque, CppError (..),- Malformation (..),+ Malformation, describeCppError, -- * Splitting@@ -25,7 +25,6 @@ ruledOutBranch, unconditionalErrors, Assignment,- resolved, -- * The directives themselves Directive (..),@@ -46,7 +45,6 @@ ) where -import Data.Char (isAlphaNum, isSpace) import Data.List (sortOn, tails) import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map@@ -55,13 +53,17 @@ import Data.Text (Text) import Data.Text qualified as T import GHC.LanguageExtensions.Type (Extension (..))-import Tilia.Cpp.Macros (Macros, guardHolds, impliedBy)+import Tilia.Cpp.Macros (Macros, guardHolds, impliedBy, isNameChar) import Tilia.Doc.Internal (Conditional (..)) import Tilia.Parser (ParseError, describeParseError) import Tilia.Source.Lines- ( conditionalKeywords,+ ( Written (..),+ blankAcross,+ conditionalKeywords,+ contentColumn, continuesGroup, directiveOnLine,+ linesOf, opaqueKeywords, opensGroup, )@@ -71,6 +73,7 @@ spanEndLine, spanStartLine, )+import Tilia.Utils (tshow) ---------------------------------------------------------------------------- -- Reading a module@@ -165,23 +168,23 @@ describeCppError :: CppError -> Text describeCppError = \case MalformedConditional n k m ->- "the #" <> k <> " at line " <> T.pack (show n) <> case m of+ "the #" <> k <> " at line " <> tshow n <> case m of NeverClosed -> " is never closed" NothingOpen -> " has no conditional to belong to" AfterElse -> " comes after the #else of its conditional" TooManyConfigurations -> "too many configurations to format" ConfigurationNotParsed c e -> describeParseError e <> inConfiguration c DirectiveInQuotedText n k ->- "the #" <> k <> " at line " <> T.pack (show n) <> " is inside a quasi-quote or a multi-line string"+ "the #" <> k <> " at line " <> tshow n <> " is inside a quasi-quote or a multi-line string" MacroInQuotedText n t ->- t <> " at line " <> T.pack (show n) <> " is inside a quasi-quote or a multi-line string"+ t <> " at line " <> tshow n <> " is inside a quasi-quote or a multi-line string" RuledOutBranch n -> "the branch at line "- <> T.pack (show n)+ <> tshow n <> " is ruled out by a conditional around it that asks the same question" AbortingAlternative n -> "the alternative that the #error at line "- <> T.pack (show n)+ <> tshow n <> " aborts cannot be formatted without parsing it" -- | Which configuration, in words. Empty where there is only one.@@ -261,17 +264,16 @@ branchLeaves :: Text -> Either CppError [Text] branchLeaves source = do forest <- readConditionals source- traverse- resolved- ( filter- (null . unconditionalErrors)- ( distinct- [ configurationOf forest a source- | a <- branchAssignments forest,- possible forest (completed forest a)- ]- )- )+ pure+ [ withoutOpaque leaf+ | leaf <-+ distinct+ [ configurationOf forest a source+ | a <- branchAssignments forest,+ possible forest (completed forest a)+ ],+ null (unconditionalErrors leaf)+ ] where distinct = Map.elems . Map.fromList . fmap (\t -> (t, t)) @@ -366,16 +368,14 @@ forest <- scanConditionals source listToMaybe (go Map.empty forest) where- written = zip [1 ..] (T.lines source)- holdsSomething (from, to) =- any (\(n, l) -> from <= n && n <= to && not (T.all isSpace l)) written+ written = linesOf (Written source) go asked forest = concat [ [ opening | Just j <- [Map.lookup (gsGuards gs) asked], (i, opening, r) <- zip3 [0 :: Int ..] (gsOwnLines gs) (gsBranches gs), i /= j,- holdsSomething r+ not (blankAcross r written) ] <> concat [ go (Map.insert (gsGuards gs) i asked) nested@@ -408,13 +408,13 @@ let answers = completed forest a, possible forest answers ]- traverse (\a -> (,) <$> reading before left a <*> reading after right a) choices+ pure [(reading before left a, reading after right a) | a <- choices] where reading source forest assignment = let selected = configurationOf forest assignment source in if null (unconditionalErrors selected)- then Just <$> resolved selected- else Right Nothing+ then Just (withoutOpaque selected)+ else Nothing -- | An unconditional @#error@ means this configuration has no Haskell -- program to parse. Conditional errors are only considered after choosing a@@ -433,11 +433,6 @@ -- | Which branch every question was answered with to reach a configuration. type Assignment = Map [Guard] Int --- | A module with every conditional answered, and its other directives--- taken out, or why its conditionals do not make sense.-resolved :: Text -> Either CppError Text-resolved source = withoutOpaque source <$ readConditionals source- ---------------------------------------------------------------------------- -- The directives themselves @@ -646,7 +641,7 @@ written = directives source inDirective n = any (\d -> dLine d <= n && n <= dLastLine d) written code (n, l) = not (T.null (T.strip l) || inDirective n || uses l)- indentation = T.length . T.takeWhile isSpace+ indentation l = contentColumn l - 1 closing = maybe False ((`elem` ("})]" :: String)) . fst) . T.uncons . T.stripStart uses l = Set.member name defined && enclosed arguments where@@ -672,4 +667,3 @@ '(' -> k + 1 ')' -> k - 1 _ -> k- isNameChar c = isAlphaNum c || c == '_'
src/Tilia/Cpp/Macros.hs view
@@ -7,6 +7,7 @@ ( Macros (..), guardHolds, impliedBy,+ isNameChar, ) where @@ -329,5 +330,6 @@ isNameStart :: Char -> Bool isNameStart c = isAsciiLower c || isAsciiUpper c || c == '_' +-- | Can a character be part of a preprocessor name? isNameChar :: Char -> Bool isNameChar c = isNameStart c || isDigit c
src/Tilia/Cpp/Merge.hs view
@@ -43,7 +43,7 @@ unwrap, wrap, )-import Tilia.Source (Lines, lineAt)+import Tilia.Source (Lines, lineAt, offsetOf) import Tilia.Span (Span (..), covers, endOf, meets, startOf, zeroWidth) -- | A conditional other than the one asked about that answering a question@@ -81,7 +81,7 @@ | s : others <- spines, all (agreeAll varied layout s) others = d- | Just xs <- traverse only spines = alongside layout xs+ | Just xs <- traverse sole spines = alongside layout xs | otherwise = factored middle layout spines where spines = fmap (spineAt layout) ds@@ -124,9 +124,6 @@ inside <- sole [i | (i, s) <- zip [0 :: Int ..] spans, all (covers s) spans] openedAgainst inside layout =<< listToMaybe (drop inside xs) where- sole [i] = Just i- sole _ = Nothing- openedAgainst inside l d = case unwrap d of Just (w, x) -> wrap w <$> openedAgainst inside (layoutInside l w) x Nothing -> case spineAt l d of@@ -246,7 +243,7 @@ middle _ [] = mempty middle layout ss@(s : rest) | all (alike layout s) rest = mconcat s- | Just xs <- traverse only ss = go layout xs+ | Just xs <- traverse sole ss = go layout xs | Just (c, tails) <- commaLed layout ss = c <> middle layout tails | otherwise = minimumBy@@ -433,8 +430,11 @@ Just (from, to) -> any (\(a, b) -> from < a && b < to) (concatMap printedFrom ds) Nothing -> False - only [d] = Just d- only _ = Nothing+-- | The one element of a list that has one.+sole :: [a] -> Maybe a+sole = \case+ [x] -> Just x+ _ -> Nothing -- | Would these two documents print the same, laid out like this? agree :: Varied -> Layout -> Doc -> Doc -> Bool@@ -548,12 +548,9 @@ [] -> Just base [(_, only)] -> Just only many -> case (spineAt layout base, [(v, spineAt layout d) | (v, d) <- many]) of- ([b], ss) | Just xs <- traverse (\(v, s) -> (,) v <$> single s) ss -> descend b xs+ ([b], ss) | Just xs <- traverse (\(v, s) -> (,) v <$> sole s) ss -> descend b xs (bs, ss) -> spliced bs ss where- single [d] = Just d- single _ = Nothing- descend b xs = do own@(_, i) <- unwrap b others <- traverse (traverse unwrap) xs@@ -579,7 +576,7 @@ cluster _ [c] = Just c cluster bs cs | to - from == 1,- Just xs <- traverse (\c -> (,) (chVaried c) <$> single (chWith c)) cs,+ Just xs <- traverse (\c -> (,) (chVaried c) <$> sole (chWith c)) cs, (b : _) <- drop from bs = ( \d -> Change@@ -769,7 +766,7 @@ Just s -> maybe False- (T.all isSpace . T.take (spanStartColumn s - 1))+ (\l -> T.all isSpace (T.take (offsetOf l (spanStartColumn s)) l)) (lineAt (spanStartLine s) written) Nothing -> False
src/Tilia/Cpp/Place.hs view
@@ -13,6 +13,7 @@ ) where +import Control.Applicative ((<|>)) import Control.Monad (foldM) import Data.List (sortOn, unsnoc) import Data.Map.Strict (Map)@@ -61,7 +62,7 @@ wrap, ) import Tilia.Source (Lines, Written (..), blankAt, lineAt, linesOf)-import Tilia.Span (Span (..), mkSpan)+import Tilia.Span (Span (..), mkSpan, startPoint) -- | What the configurations of a module had besides their code. data CommentSummary = CommentSummary@@ -330,24 +331,23 @@ hulls = Map.filter ((> 1) . length)- (Map.fromListWith (<>) [(startPoint' s, [s]) | d <- ds, s <- outermost d])+ (Map.fromListWith (<>) [(startPoint s, [s]) | d <- ds, s <- outermost d]) rewrite seen = \case DLocated s x- | Set.notMember (startPoint' s) seen,- Just ss <- Map.lookup (startPoint' s) hulls ->- DLocated (foldr (<>) s ss) (rewrite (Set.insert (startPoint' s) seen) x)- | otherwise -> DLocated s (rewrite (Set.insert (startPoint' s) seen) x)+ | Set.notMember (startPoint s) seen,+ Just ss <- Map.lookup (startPoint s) hulls ->+ DLocated (foldr (<>) s ss) (rewrite (Set.insert (startPoint s) seen) x)+ | otherwise -> DLocated s (rewrite (Set.insert (startPoint s) seen) x) d@DCppChoice{} -> d d -> mapChildren (rewrite seen) d outermost = go Set.empty where go seen = \case DLocated s x- | Set.member (startPoint' s) seen -> go seen x- | otherwise -> s : go (Set.insert (startPoint' s) seen) x+ | Set.member (startPoint s) seen -> go seen x+ | otherwise -> s : go (Set.insert (startPoint s) seen) x DCppChoice{} -> [] d -> foldChildren (go seen) d- startPoint' s = (spanStartLine s, spanStartColumn s) -- | Give one alternative of a choice its anchors. anchoredIn ::@@ -462,62 +462,65 @@ Int -> -- | What to put there. Doc ->- -- | How to put it among the lines of something that begins above it, at- -- that thing's own indentation.+ -- | How to put it before code printed further in, which carries on a line+ -- above. (Doc -> Doc) -> Doc -> Doc-placeAt present written n body continuing = among [] body+placeAt present written n body continuing = among [] where- within ctx b d = case d of- DNest 0 x -> DNest 0 (within ctx b x)- DNest k x -> DNest k (within ctx body x)- DAlign x -> DAlign (within ctx body x)- DGroup l x -> DGroup l (within ctx b x)- DVariant x y -> DVariant (within ctx b x) (within ctx b y)+ within ctx d = case d of+ DNest k x -> DNest k (within ctx x)+ DAlign x -> DAlign (within ctx x)+ DGroup l x -> DGroup l (within ctx x)+ DVariant x y -> DVariant (within ctx x) (within ctx y) DLocated s x- | spanStartLine s > n -> among ctx b d- | DCppChoice{} <- x -> DLocated s (within ctx b x)- | otherwise -> DLocated s (within ctx (continuing body) x)- DFence s x -> DFence s (within ctx b x)+ | spanStartLine s > n -> among ctx d+ | otherwise -> DLocated s (within ctx x)+ DFence s x -> DFence s (within ctx x) DCppChoice cs bs e | present ctx -> d | otherwise -> case alternativeAt n cs of Everywhere -> DCppChoice cs- [(g, among (ctx <> [(cs, k)]) b x) | (k, (g, x)) <- zip [0 ..] bs]- (among (ctx <> [(cs, length bs)]) b e)+ [(g, among (ctx <> [(cs, k)]) x) | (k, (g, x)) <- zip [0 ..] bs]+ (among (ctx <> [(cs, length bs)]) e) Only k | k < length bs -> DCppChoice cs- (replaced k (among (ctx <> [(cs, k)]) b (maybe mempty snd (listToMaybe (drop k bs)))) bs)+ (replaced k (among (ctx <> [(cs, k)]) (maybe mempty snd (listToMaybe (drop k bs)))) bs) e- | otherwise -> DCppChoice cs bs (among (ctx <> [(cs, k)]) b e)+ | otherwise -> DCppChoice cs bs (among (ctx <> [(cs, k)]) e) Nowhere -> d- _ -> among ctx b d+ _ -> among ctx d - among ctx b d+ among ctx d | present ctx = d | otherwise = case break startsAfter (spine d) of (before, after) | Just (earlier, holder, spacing) <- lastBounded before, maybe False (>= n) (endOf holder) ->- mconcat (earlier <> [within ctx b holder] <> spacing <> after)+ mconcat (earlier <> [within ctx holder] <> spacing <> after) | (later, holder : rest) <- break holds after ->- mconcat (before <> later <> [within ctx b holder] <> rest)+ mconcat (before <> later <> [within ctx holder] <> rest) | Just (printed, anchor, spacing) <- lastBounded before, Just from <- endOf anchor, Just ls <- written -> if any (`blankAt` ls) [from .. n - 1] || not (all onlySpacing spacing)- then mconcat (before <> [includeWhen (blankAt (n - 1) ls) blankLine, b] <> after)- else mconcat (printed <> [anchor, b] <> spacing <> after)+ then mconcat (before <> [includeWhen (blankAt (n - 1) ls) blankLine, putBefore after] <> after)+ else mconcat (printed <> [anchor, putBefore (spacing <> after)] <> spacing <> after) | next : rest <- after, any (not . onlySpacing) (takeWhile unbounded (reverse before)) ->- mconcat (before <> [within ctx b next] <> rest)- | otherwise -> mconcat (before <> [b] <> after)+ mconcat (before <> [within ctx next] <> rest)+ | otherwise -> mconcat (before <> [putBefore after] <> after) + putBefore following =+ if maybe False (> 0) (firstNesting (mconcat following))+ then continuing body+ else body+ holds = \case DLocated s _ -> spanStartLine s < n && n <= spanEndLine s _ -> False@@ -576,6 +579,17 @@ from < n, n < to ]++-- | How many steps further in than itself a document prints its first+-- text, if it prints any.+firstNesting :: Doc -> Maybe Int+firstNesting = \case+ DText _ -> Just 0+ DCat a b -> firstNesting a <|> firstNesting b+ DNest k x -> (+ k) <$> firstNesting x+ DVariant _ x -> firstNesting x+ DCppChoice _ bs e -> foldr ((<|>) . firstNesting . snd) (firstNesting e) bs+ d -> firstNesting . snd =<< unwrap d -- | The lines a document was printed from, first and last. boundsOf :: Doc -> Maybe (Int, Int)
src/Tilia/Diff.hs view
@@ -19,6 +19,7 @@ import Data.Text (Text) import Data.Text qualified as T import Tilia.Palette (Color (..), Palette, paint)+import Tilia.Utils (tshow) -- | What a line of the comparison does. data Mark = Context | Removed | Added@@ -98,7 +99,7 @@ <> " @@" where span' which n = case slice range of- (l : _) -> T.pack (show (which l)) <> "," <> T.pack (show n)+ (l : _) -> tshow (which l) <> "," <> tshow n [] -> "0,0" line (Line mark _ _ text) = case mark of
src/Tilia/Doc.hs view
@@ -5,12 +5,7 @@ ) where -import Data.Text (Text) import Tilia.Doc.Internal ( Doc,- render,+ printDoc, )---- | Render a document to source text.-printDoc :: Doc -> Text-printDoc = render
src/Tilia/Doc/Body.hs view
@@ -1,4 +1,5 @@--- | Something that can appear as the body of an enclosing construct.+-- | The bodies of enclosing constructs, and how each joins what comes+-- before it. module Tilia.Doc.Body ( Body (..), attachBody,
src/Tilia/Doc/Combinators.hs view
@@ -16,6 +16,7 @@ LineStart (..), TrailingWhitespace (..), verbatimBreak,+ verbatimLines, verbatim, emptyAnchor, insideBrackets,@@ -49,7 +50,6 @@ -- * Wrapping ClosingIndent (..),- bracket, parens, parensWith, brackets,@@ -127,12 +127,13 @@ verbatimBreak :: LineStart -> TrailingWhitespace -> Doc verbatimBreak = DVerbatimBreak +-- | Lines of text reproduced, each break between them a 'verbatimBreak'.+verbatimLines :: LineStart -> TrailingWhitespace -> [Text] -> Doc+verbatimLines start trailing = sepBy (verbatimBreak start trailing) . fmap txt+ -- | Text reproduced exactly, line breaks and all. verbatim :: Text -> Doc-verbatim =- sepBy (verbatimBreak AtMargin KeepWhitespace)- . fmap txt- . T.splitOn "\n"+verbatim = verbatimLines AtMargin KeepWhitespace . T.splitOn "\n" -- | An empty anchor so that comments can attach to it in situations when -- nothing more substantial is present but the position is such that it@@ -269,18 +270,6 @@ ---------------------------------------------------------------------------- -- Wrapping --- | Surround with the given opening and closing documents, adding nothing--- of its own.-enclose ::- -- | Opening bracket.- Doc ->- -- | Closing bracket.- Doc ->- -- | Body.- Doc ->- Doc-enclose open close body = open <> body <> close- -- | Where the closing bracket of a broken bracket pair goes. data ClosingIndent = -- | Back out to the level the opening bracket is on.@@ -399,7 +388,7 @@ -- | Surround with backticks. backticks :: Doc -> Doc-backticks = enclose (txt "`") (txt "`")+backticks d = txt "`" <> d <> txt "`" ---------------------------------------------------------------------------- -- Punctuation
src/Tilia/Doc/Internal.hs view
@@ -25,7 +25,7 @@ TrailingWhitespace (..), -- * Rendering- render,+ printDoc, ) where @@ -403,8 +403,8 @@ } -- | Turn a document into text.-render :: Doc -> Text-render doc = finish (go env doc emptyOut)+printDoc :: Doc -> Text+printDoc doc = finish (go env doc emptyOut) where env = Env
src/Tilia/Equivalence.hs view
@@ -1,12 +1,8 @@ {-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-} -- | Whether formatting changed what a module says. module Tilia.Equivalence@@ -67,8 +63,9 @@ triggerEscaped, ) import Tilia.Imports (normalizeImports)-import Tilia.Parser (ParsedModule (..))-import Tilia.Pragma (languageNames)+import Tilia.Parser (ParsedModule (..), writtenInHeader)+import Tilia.Pragma (languageNames, pragmaFlags)+import Tilia.Pragma.Ghc (InForce (..), inForceAfter) import Tilia.Source (comments, sourceLines) import Tilia.Span (Span (..)) import Tilia.Span.Ghc (spanOf, spansOf)@@ -380,7 +377,7 @@ HsDocStringNext -> "pointer" HsDocStringPrevious -> "pointer" HsDocStringNamed n -> "named " <> T.pack n- HsDocStringGroup n -> "group " <> T.pack (show n)+ HsDocStringGroup n -> "group " <> tshow n -- | What a doc string says, with the whitespace thrown away. docWords :: HsDocString -> [ByteString]@@ -428,7 +425,8 @@ -- Ordinary comments are compared as they will be printed, in order: the -- text is normalised on the way in, so a comment that came out unchanged -- reads back identically, and one that was mangled or moved past its--- neighbor does not.+-- neighbor does not, unless it trails a line and moved past comments on+-- lines of their own written below it. -- -- Except in the header, where the order says nothing. The pragmas are -- sorted and so are the imports, and a comment written against either@@ -440,6 +438,10 @@ -- legitimately rewrite one—a @-- ^ x@ that moves in front of what it -- documents has to become @-- | x@—so the text is not expected to survive, -- but the comment is.+--+-- The pragmas of the header are also read as GHC reads them: the flags of+-- the options have to come out in the same order, and what GHC's own flag+-- reader puts in force after all of them has to stay the same. commentDifference :: -- | The module as written. ParsedModule ->@@ -449,6 +451,23 @@ commentDifference parsedBefore parsedAfter | not (Set.null lost) = Just ("lost the pragma " <> pragmaList lost) | not (Set.null gained) = Just ("invented the pragma " <> pragmaList gained)+ | optionsBefore /= optionsAfter =+ Just+ ( "changed the flags of the options pragmas from "+ <> flagList optionsBefore+ <> " to "+ <> flagList optionsAfter+ )+ | haddockBefore /= haddockAfter =+ Just+ ( "changed the OPTIONS_HADDOCK pragmas from "+ <> flagList haddockBefore+ <> " to "+ <> flagList haddockAfter+ )+ | flagsBefore /= flagsAfter,+ Just why <- inForceDifference (inForceAfter flagsBefore) (inForceAfter flagsAfter) =+ Just why | docsBefore /= docsAfter = Just $ "the module's "@@ -477,6 +496,16 @@ Just lastLine -> (<= lastLine) . spanStartLine . commentSpan lost = pragmasOf before `Set.difference` pragmasOf after+ headerBefore = headerPragmas parsedBefore+ headerAfter = headerPragmas parsedAfter+ flagsBefore = concatMap flagsOf headerBefore+ flagsAfter = concatMap flagsOf headerAfter+ flagsOf p = pragmaFlags (pragmaName p) (pragmaBody p)+ optionsBefore = filter (not . T.isPrefixOf "-X") flagsBefore+ optionsAfter = filter (not . T.isPrefixOf "-X") flagsAfter+ haddockBefore = [pragmaBody p | p <- headerBefore, pragmaName p == "OPTIONS_HADDOCK"]+ haddockAfter = [pragmaBody p | p <- headerAfter, pragmaName p == "OPTIONS_HADDOCK"]+ flagList flags = "`" <> T.unwords flags <> "`" gained = pragmasOf after `Set.difference` pragmasOf before docsBefore = length (documentation before) docsAfter = length (documentation after)@@ -513,6 +542,45 @@ T.intercalate ", " . fmap (\(n, b) -> "{-# " <> n <> " " <> b <> " #-}") . Set.toList++-- | The pragmas of a module's header, which are all the pragmas GHC reads,+-- in the order they are written.+headerPragmas :: ParsedModule -> [Pragma]+headerPragmas m =+ mapMaybe commentPragma (takeWhile written (comments (pmSource m)))+ where+ written = writtenInHeader (pmHeaderEnd m) . commentSpan++-- | How what the flags of a module's header settle changed, if it did.+inForceDifference :: InForce -> InForce -> Maybe Text+inForceDifference before after+ | inForceEdition before /= inForceEdition after =+ Just+ ( "changed the edition in force from "+ <> edition before+ <> " to "+ <> edition after+ )+ | inForceSafety before /= inForceSafety after =+ Just+ ( "changed the Safe Haskell mode from "+ <> tshow (inForceSafety before)+ <> " to "+ <> tshow (inForceSafety after)+ )+ | null switched = Nothing+ | otherwise =+ Just+ ( "changed how the header switches "+ <> T.intercalate ", " (fmap tshow switched)+ )+ where+ switched =+ [ e+ | e <- Map.keys (inForceSwitches before <> inForceSwitches after),+ Map.lookup e (inForceSwitches before) /= Map.lookup e (inForceSwitches after)+ ]+ edition = maybe "the default" tshow . inForceEdition -- | A comment's lines, as they are compared. bodyKey :: Comment -> NonEmpty Text
src/Tilia/Fixity.hs view
@@ -1,7 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-} -- | Working out the fixity of the operators a module uses. module Tilia.Fixity@@ -12,10 +11,11 @@ defaultFixity, spellFixity, readPrecedence,+ opName,+ qualifierOf, -- * Module declarations declaredFixities,- declaredNames, moduleName, -- * Module exports@@ -33,7 +33,6 @@ decides, Namespace (..), Fixities,- inBothNamespaces, Certain (..), ModuleChain (..), spellModuleChain,@@ -51,6 +50,7 @@ Unknown (..), operatorsUsed, unknownOperators,+ disagreement, capturedUses, operatorSpelling, spellUnreadIn,@@ -73,7 +73,6 @@ import Data.Char (isUpper) import Data.Choice (Choice, isTrue) import Data.Foldable (toList)-import Data.Generics.Schemes (listify) import Data.List (nub, sortOn, tails) import Data.List.NonEmpty (NonEmpty ((:|)), nonEmpty) import Data.List.NonEmpty qualified as NE@@ -95,7 +94,7 @@ import Tilia.Palette (Color (Place), Palette, paint) import Tilia.Span (Span, covers) import Tilia.Span.Ghc (spanOf)-import Tilia.Utils (collected, spellList)+import Tilia.Utils (collected, spellList, tshow) ---------------------------------------------------------------------------- -- Fixities@@ -136,7 +135,7 @@ -- | A fixity, written the way it would be declared. spellFixity :: Fixity -> Text spellFixity (Fixity direction precedence) =- which direction <> " " <> T.pack (show precedence)+ which direction <> " " <> tshow precedence where which = \case LeftAssoc -> "infixl"@@ -192,20 +191,13 @@ : [opName (unLoc (fdLName (unLoc f))) | f <- tcdATs] _ -> [] terms = \case- ValD _ b -> boundNames b+ ValD _ b -> fmap opName (collectHsBindBinders CollNoDictBinders b) SigD _ sig -> signedNames sig ForD _ f -> [opName (unLoc (fd_name f))] TyClD _ d@DataDecl{} -> fmap snd (membersOf d)- TyClD _ ClassDecl{tcdSigs} -> concatMap (classMethods . unLoc) tcdSigs+ TyClD _ ClassDecl{tcdSigs} -> concatMap (signedNames . unLoc) tcdSigs _ -> [] --- | The methods a class signature declares.-classMethods :: Sig GhcPs -> [OpName]-classMethods = \case- TypeSig _ ns _ -> fmap (opName . unLoc) ns- ClassOpSig _ _ ns _ -> fmap (opName . unLoc) ns- _ -> []- -- | The members of the type or class a declaration declares, by -- namespace: a data type's constructors and record fields, a class's -- methods and associated families.@@ -217,7 +209,7 @@ DataDecl{tcdDataDefn} -> fmap (InTerms,) (concatMap (fromCon . unLoc) (consOf (dd_cons tcdDataDefn))) ClassDecl{tcdSigs, tcdATs} ->- fmap (InTerms,) (concatMap (classMethods . unLoc) tcdSigs)+ fmap (InTerms,) (concatMap (signedNames . unLoc) tcdSigs) <> [(InTypes, opName (unLoc (fdLName (unLoc f)))) | f <- tcdATs] _ -> [] where@@ -273,19 +265,6 @@ where (types, terms) = declaredNamespaces hsModule --- | The names a binding brings into being.-boundNames :: HsBind GhcPs -> [OpName]-boundNames = \case- FunBind _ n _ -> [opName (unLoc n)]- PatBind _ p _ _ -> [opName n | VarPat _ (L _ n) <- listify isVarPat p]- PatSynBind _ (PSB _ n _ _ _) -> [opName (unLoc n)]- _ -> []- where- isVarPat :: Pat GhcPs -> Bool- isVarPat = \case- VarPat{} -> True- _ -> False- -- | The module's own name, if it declares one. moduleName :: HsModule GhcPs -> Maybe Text moduleName = fmap (T.pack . moduleNameString . unLoc) . hsmodName@@ -366,8 +345,7 @@ data Import = Import { -- | The module being imported. importModule :: Text,- -- | Whether unqualified names are brought into scope. An import that is- -- @qualified@ brings none.+ -- | Whether it is @qualified@, bringing no unqualified names in. importQualified :: Bool, -- | The name qualified uses go through: the alias if there is one, -- otherwise the module's own name.@@ -544,15 +522,6 @@ -- | The fixities a module offers. type Fixities = Map (Namespace, OpName) Fixity --- | Take fixities that say nothing about namespaces to govern both.-inBothNamespaces :: Map OpName Fixity -> Fixities-inBothNamespaces declared =- Map.fromList- [ ((namespace, op), fixity)- | (op, fixity) <- Map.toList declared,- namespace <- [InTypes, InTerms]- ]- -- | What a module certainly brings into scope for a module that imports -- it whole. data Certain = Certain@@ -871,7 +840,7 @@ inTypes = [n | HsOpTy _ _ _ (L _ n) _ <- gatheredTypes found] named namespace n =- (namespace, (qualifierOf n, OpName (T.pack (occNameString (rdrNameOcc n)))))+ (namespace, (qualifierOf n, opName n)) -- | The uses of an operator in an expression that a local binding captures, -- by where the operator is written, with the fixity the binding's group@@ -958,15 +927,28 @@ unknownOperators scope found = Map.toList (Map.fromList (mapMaybe unsettled (concatMap operatorsUsed found))) where- unsettled (namespace, (qualifier, op)) =- case fixityInScope scope namespace qualifier op of- Just (answering, _) ->- ((qualifier, op),) . Ambiguous- <$> Map.lookup (qualifier, op) (reachAmbiguous (reachIn answering scope))- Nothing -> case nonEmpty (unreadThatMightDeclare scope namespace qualifier op) of- Just missing -> Just ((qualifier, op), NotRead missing)- Nothing -> Nothing+ unsettled (namespace, use@(qualifier, op)) =+ (use,) <$> case lookupFixity scope namespace qualifier op of+ Resolved _ _ -> Ambiguous <$> disagreement scope namespace qualifier op+ Unresolved missing -> Just (NotRead missing) +-- | What the imports in scope bring a use in with, where they disagree, in+-- the namespace that answers for it.+disagreement ::+ -- | The scope.+ Scope ->+ -- | The namespace the operator is written in.+ Namespace ->+ -- | The qualifier written at the use site, if any.+ Maybe Text ->+ -- | Operator being resolved.+ OpName ->+ -- | The module each import names and the fixity it brings.+ Maybe (NonEmpty (Text, Fixity))+disagreement scope namespace qualifier op = do+ (answering, _) <- fixityInScope scope namespace qualifier op+ Map.lookup (qualifier, op) (reachAmbiguous (reachIn answering scope))+ -- | An operator as a use site writes it, qualifier and all. operatorSpelling :: Maybe Text -> OpName -> Text operatorSpelling qualifier (OpName op) = maybe "" (<> ".") qualifier <> op@@ -1008,11 +990,8 @@ ---------------------------------------------------------------------------- -- Module summaries --- | Summary of a module: raw facts (per CPP configuration) about a module--- we read from source. This is built for both local modules and dependency--- modules when they come from tarballs. This is an optimization--- mechanism—we create a summary once and then share it during a run. For--- local modules it is also persisted in the cache on disk.+-- | What one CPP configuration of a module read from source says, whether+-- the module is the project's own or comes out of a tarball. data ModuleSummary = ModuleSummary { -- | The name it gives itself, if it gives one. summaryName :: Maybe Text,
src/Tilia/Fixity/Archive.hs view
@@ -25,14 +25,17 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as T+import Distribution.PackageDescription (GenericPackageDescription) import GHC.Generics (Generic)-import Tilia.Fixity.Cabal (cabalFileAtTop, entryPosixPath, sourceDirs)+import Tilia.Cabal.Package (packageSourceDirs)+import Tilia.Fixity.Cabal (entryPosixPath, ownCabalFile) import Tilia.Utils (quietly) -- | What a tarball holds that a module could be read out of. data Archive = Archive- { -- | The package's @.cabal@ file, the first one at the top.- archiveCabal :: Maybe Text,+ { -- | What the package's @.cabal@ file describes, the first one at the top+ -- that parses.+ archiveCabal :: Maybe GenericPackageDescription, -- | Every file that could hold a module, by where it sits, in the order -- the tarball has them. archiveFiles :: [(FilePath, BS.ByteString)]@@ -49,10 +52,9 @@ where sweep cabal found = \case Tar.Next entry rest- | Tar.NormalFile content _ <- Tar.entryContent entry,- cabalFileAtTop (entryPosixPath entry),- Nothing <- cabal ->- sweep (Just (T.decodeUtf8Lenient (BL.toStrict content))) found rest+ | Nothing <- cabal,+ Just own <- ownCabalFile entry ->+ sweep (Just own) found rest | Tar.NormalFile content _ <- Tar.entryContent entry, any (`isSuffixOf` entryPosixPath entry) moduleEndings -> sweep cabal ((entryPosixPath entry, BL.toStrict content) : found) rest@@ -63,7 +65,7 @@ moduleIn :: Text -> Archive -> Maybe InArchive moduleIn modName archive = listToMaybe (mapMaybe pick moduleEndings) where- dirs = maybe [] sourceDirs (archiveCabal archive)+ dirs = maybe [] packageSourceDirs (archiveCabal archive) pick ending = inArchive ending . T.decodeUtf8Lenient . snd <$> listToMaybe (under sfx matching <> matching)@@ -73,7 +75,7 @@ under sfx matching = [f | d <- dirs, f <- matching, inDir sfx d (fst f)] inDir sfx d path | d == "." = takeWhile (/= '/') path <> sfx == path- | otherwise = ("/" <> T.unpack d <> sfx) `isSuffixOf` path+ | otherwise = ("/" <> d <> sfx) `isSuffixOf` path -- | Where a module's file is under the directory that holds it, without an -- extension.
src/Tilia/Fixity/Cabal.hs view
@@ -1,33 +1,28 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} --- | Reading a package's modules out of its @.cabal@ file.+-- | Reading a package's description out of its source tarball. module Tilia.Fixity.Cabal ( packageModules, cabalFileInArchive,+ ownCabalFile, cabalFileAtTop, entryPosixPath,- containedModules,- sourceDirs,- declaredExtensions, ) where import Codec.Archive.Tar qualified as Tar import Codec.Archive.Tar.Entry qualified as Tar import Codec.Compression.GZip qualified as GZip+import Control.Applicative ((<|>))+import Control.DeepSeq (force)+import Control.Exception (evaluate) import Data.ByteString.Lazy qualified as BL-import Data.Char (isSpace) import Data.List (isSuffixOf)-import Data.List qualified-import Data.Maybe (mapMaybe) import Data.Text (Text)-import Data.Text qualified as T-import Data.Text.Encoding qualified as T-import GHC.Driver.Session qualified as GHC-import GHC.LanguageExtensions.Type (Extension)-import Tilia.Pragma (applyExtensionName, lookupEdition)-import Tilia.Utils (fieldWords, quietly)+import Distribution.PackageDescription (GenericPackageDescription)+import Tilia.Cabal.Package (declaredModules, parsePackageDescription)+import Tilia.Utils (quietly) -- | The modules a package holds, read from the @.cabal@ file in its -- source tarball.@@ -36,22 +31,32 @@ packageModules :: FilePath -> IO (Maybe [Text]) packageModules tarball = quietly Nothing $ do bytes <- BL.readFile tarball- pure (containedModules <$> cabalFileInArchive (Tar.read (GZip.decompress bytes)))+ evaluate . force $+ fmap fst . declaredModules+ <$> cabalFileInArchive (Tar.read (GZip.decompress bytes)) -- | The first @.cabal@ file at the top level of an archive. -- -- Stops as soon as it finds one. The archive is decompressed lazily, so a -- @.cabal@ near the front costs a fraction of the whole file.-cabalFileInArchive :: Tar.Entries e -> Maybe Text+cabalFileInArchive :: Tar.Entries e -> Maybe GenericPackageDescription cabalFileInArchive = \case- Tar.Next entry rest- | cabalFileAtTop (entryPosixPath entry),- Tar.NormalFile content _ <- Tar.entryContent entry ->- Just (T.decodeUtf8Lenient (BL.toStrict content))- | otherwise -> cabalFileInArchive rest+ Tar.Next entry rest -> ownCabalFile entry <|> cabalFileInArchive rest Tar.Done -> Nothing Tar.Fail _ -> Nothing +-- | What an archive's entry describes if it is the package's own @.cabal@+-- file and parses.+ownCabalFile :: Tar.Entry -> Maybe GenericPackageDescription+ownCabalFile entry+ | cabalFileAtTop path,+ Tar.NormalFile content _ <- Tar.entryContent entry =+ either (const Nothing) Just $+ parsePackageDescription path (BL.toStrict content)+ | otherwise = Nothing+ where+ path = entryPosixPath entry+ -- | Where an entry sits in its archive, written the way a tar file writes -- it. entryPosixPath :: Tar.Entry -> FilePath@@ -62,58 +67,3 @@ cabalFileAtTop path = ".cabal" `isSuffixOf` path && depth path == 2 where depth = (1 +) . length . filter (== '/')---- | Every module a package holds, whether it exposes it or not.-containedModules :: Text -> [Text]-containedModules t =- modulesUnder "exposed-modules" t <> modulesUnder "other-modules" t- where- modulesUnder field =- concatMap moduleNames . fieldsNamed field . T.lines- where- moduleNames = filter looksLikeModule . fieldWords- looksLikeModule m = case T.uncons m of- Just (c, _) -> c `elem` ['A' .. 'Z']- Nothing -> False---- | Every directory a @.cabal@ file's modules could be under.-sourceDirs :: Text -> [Text]-sourceDirs contents = Data.List.nub (named <> ["."])- where- named = concatMap fieldWords (fieldsNamed "hs-source-dirs" (T.lines contents))---- | The extensions a package puts in force, read from its @.cabal@ file.-declaredExtensions :: Text -> [Extension]-declaredExtensions contents =- foldl applyExtensionName baseline named- where- ls = T.lines contents- baseline =- case mapMaybe lookupEdition (fieldsNamed "default-language" ls) of- [] -> GHC.languageExtensions (Just GHC.Haskell98)- editions ->- Data.List.nub (concatMap (GHC.languageExtensions . Just) editions)- named =- concatMap fieldWords $- fieldsNamed "extensions" ls <> fieldsNamed "default-extensions" ls---- | The values of every field with the given name, wherever it appears and--- however deeply it is nested.-fieldsNamed :: Text -> [Text] -> [Text]-fieldsNamed name = go . filter (not . commented)- where- commented = T.isPrefixOf "--" . T.stripStart- go = \case- [] -> []- (l : ls)- | Just value <- fieldValue l ->- let (continued, rest) = span (deeperThan (indentOf l)) ls- in T.unwords (value : fmap T.strip continued) : go rest- | otherwise -> go ls- fieldValue l =- let (key, rest) = T.break (== ':') l- in if T.toLower (T.strip key) == name && not (T.null rest)- then Just (T.strip (T.drop 1 rest))- else Nothing- deeperThan n l = T.null (T.strip l) || indentOf l > n- indentOf = T.length . T.takeWhile isSpace
src/Tilia/Fixity/Cache.hs view
@@ -39,14 +39,12 @@ import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as T-import Data.Text.IO qualified as T import Data.Time (UTCTime) import Data.Time.Format (defaultTimeLocale, formatTime) import Data.Time.Format.ISO8601 (iso8601ParseM, iso8601Show) import System.Directory ( XdgDirectory (XdgCache), createDirectoryIfMissing,- doesFileExist, getModificationTime, getXdgDirectory, renameFile,@@ -54,7 +52,7 @@ import System.FilePath (takeDirectory, (</>)) import Tilia.Fixity import Tilia.Fixity.PackageDb (Installed (..), InstalledPackage (..))-import Tilia.Utils (quietly)+import Tilia.Utils (quietly, readUtf8File, tshow, writeUtf8File) -- | Where cached answers are kept together with a token unique to this -- build plan and environment, or nowhere, in which case nothing is@@ -75,7 +73,7 @@ -- | Bumped whenever cache format changes. formatVersion :: FilePath-formatVersion = "v2"+formatVersion = "v3" -- | Open, creating the directory if need be. --@@ -471,7 +469,7 @@ [ op, renderNamespace namespace, renderDirection direction,- T.pack (show precedence)+ tshow precedence ] where renderDirection = \case@@ -591,9 +589,7 @@ readIfPresent :: Maybe FilePath -> (Text -> a) -> IO (Maybe a) readIfPresent place parse = case place of Nothing -> pure Nothing- Just path -> quietly Nothing $ do- there <- doesFileExist path- if there then Just . parse <$> T.readFile path else pure Nothing+ Just path -> fmap parse <$> readUtf8File path -- | Write via a temporary file and a rename, making the directory if need -- be.@@ -603,5 +599,5 @@ Just path -> quietly () $ do createDirectoryIfMissing True (takeDirectory path) let temporary = path <> ".tmp"- T.writeFile temporary contents+ writeUtf8File temporary contents renameFile temporary path
src/Tilia/Fixity/Debug.hs view
@@ -4,9 +4,7 @@ -- | An account of how a module's fixities were determined. module Tilia.Fixity.Debug- ( FixityNotes (..),- ImportNote (..),- OperatorNote (..),+ ( FixityNotes, fixityNotes, renderFixityNotes, )@@ -30,12 +28,11 @@ Provenance (..), Resolution (..), Scope (..),+ disagreement, lookupFixity, moduleImports, operatorSpelling, operatorsUsed,- reachAmbiguous,- reachIn, reachUnqualified, spellDisagreement, spellFixity,@@ -44,7 +41,7 @@ ) import Tilia.Palette (Color (Operator, Place), Palette, paint) import Tilia.Parser (ParsedModule (..))-import Tilia.Utils (indent, lineWidth, wrapTo)+import Tilia.Utils (indent, tshow, wrappedUnder) -- | Everything that decided one module's fixities. data FixityNotes = FixityNotes@@ -153,8 +150,7 @@ OperatorNote { noteSpelling = operatorSpelling qualifier op, noteResolution = lookupFixity scope namespace qualifier op,- noteDisagreement =- Map.lookup (qualifier, op) (reachAmbiguous (reachIn namespace scope))+ noteDisagreement = disagreement scope namespace qualifier op } -- | Set out all the 'FixityNotes' per file.@@ -180,9 +176,7 @@ | otherwise = heading what : concatMap (entry . render) items heading what = indent 2 <> "· " <> what - entry line = case wrapTo (lineWidth - 8) line of- [] -> []- (opening : rest) -> (indent 3 <> "· " <> opening) : fmap (indent 4 <>) rest+ entry = wrappedUnder 4 (indent 3 <> "· ") fromImport i = named (noteModule i)@@ -223,7 +217,7 @@ DeclaredHere -> "declared in this module" DeclaredIn m -> "declared in " <> named m BuiltIn -> "built into the language"- ReportDefault -> "the Report's default, nothing in scope declaring it"+ ReportDefault -> "the Report's default" ambiguously o = case noteDisagreement o of Nothing -> ""@@ -235,4 +229,4 @@ operators = \case 1 -> "1 operator"- n -> T.pack (show n) <> " operators"+ n -> tshow n <> " operators"
src/Tilia/Fixity/HiFile.hs view
@@ -35,6 +35,7 @@ import Tilia.Fixity.KnownKeys.Ghc910 qualified as Ghc910 import Tilia.Fixity.KnownKeys.Ghc912 qualified as Ghc912 import Tilia.Fixity.KnownKeys.Ghc914 qualified as Ghc914+import Tilia.Utils (tshow) -- | What an interface file says about the operators its module offers. data HiFile = HiFile@@ -88,7 +89,7 @@ hiFile :: Get HiFile hiFile = do magic <- word32be- unless (magic == 0x1face64) (failure ("not a 64-bit interface file: " <> T.pack (show magic)))+ unless (magic == 0x1face64) (failure ("not a 64-bit interface file: " <> tshow magic)) version <- string series <- case take 3 version of "910" -> pure Series910@@ -200,7 +201,7 @@ 2 -> pure InTypes 3 -> pure InTypes 4 -> InTerms <$ uleb- b -> failure ("unknown namespace " <> T.pack (show b))+ b -> failure ("unknown namespace " <> tshow b) s <- uleb pure (namespace, s)
src/Tilia/Fixity/Interface.hs view
@@ -12,12 +12,11 @@ where import Control.DeepSeq (NFData)-import Control.Monad ((<=<)) import Data.ByteString qualified as BS import Data.Char (isUpper) import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map-import Data.Maybe (isNothing, mapMaybe, maybeToList)+import Data.Maybe (isNothing, mapMaybe) import Data.Set (Set) import Data.Set qualified as Set import Data.Text (Text)@@ -26,15 +25,16 @@ import Tilia.Fixity import Tilia.Fixity.HiFile (HiExport (..), HiFile (..), HiName (..), decodeHiFile) import Tilia.Process (readProgramOutput)-import Tilia.Utils (quietly)+import Tilia.Utils (blocksOf, indented, quietly) -- | What an interface says about the operators a module offers. data Interface = Interface { -- | The fixities the module declares itself for the names it exports, -- by the namespace each governs. interfaceDeclares :: Fixities,- -- | The names it reexports, each with the source module.- interfaceReexports :: [(Text, OpName)],+ -- | The names it reexports, each with the source module and the+ -- namespace it is exported in.+ interfaceReexports :: [(Text, Namespace, OpName)], -- | The members it exports with each name, which is what @T(..)@ in an -- import list stands for. interfaceMembers :: Map OpName (Set OpName),@@ -58,8 +58,8 @@ IO (Maybe Interface) readInterface modName path = quietly Nothing $- (fromHiFile modName <=< decodeHiFile) <$> BS.readFile path >>= \case- Right interface -> pure (Just interface)+ decodeHiFile <$> BS.readFile path >>= \case+ Right hi -> pure (either (const Nothing) Just (fromHiFile modName hi)) Left _ -> readProgramOutput "ghc" ["--show-iface", path] >>= \case Nothing -> pure Nothing@@ -79,8 +79,8 @@ Set.member (namespace, op) exportedHere ], interfaceReexports =- [ (m, op)- | (Just m, _, op) <- concatMap mentioned hiExports,+ [ (m, namespace, op)+ | HiName m namespace op <- concatMap exportedNames hiExports, m /= hiModule ], interfaceMembers = Map.map (Set.map snd) members,@@ -92,7 +92,7 @@ } } where- exported = concatMap exportedNames hiExports+ exported = mapMaybe known (concatMap exportedNames hiExports) exportedHere = Set.fromList [(namespace, op) | (_, namespace, op) <- exported] members =@@ -104,11 +104,8 @@ let kids = filter (/= parent) (mapMaybe known ns) ] exportedNames = \case- Avail n -> maybeToList (known n)- AvailTC _ ns -> mapMaybe known ns- mentioned = \case- Avail n -> maybeToList (known n)- AvailTC p ns -> maybeToList (known p) <> mapMaybe known ns+ Avail n -> [n]+ AvailTC _ ns -> ns known = \case HiName m namespace op -> Just (Just m, namespace, op) BuiltInSyntax namespace op -> Just (Nothing, namespace, op)@@ -128,7 +125,11 @@ fixities (foldMap ownExports (sectionsNamed "exports:")) ),- interfaceReexports = concatMap reexports (sectionsNamed "exports:"),+ interfaceReexports =+ [ (m, namespace, op)+ | (m, op) <- concatMap reexports (sectionsNamed "exports:"),+ namespace <- [InTypes, InTerms]+ ], interfaceMembers = Map.unionsWith Set.union (fmap membersIn (sectionsNamed "exports:")), interfaceExports = Nothing@@ -137,26 +138,27 @@ holdsModule l = case T.words l of ("interface" : m : _) -> m == modName _ -> False- sectionsNamed name =- [ T.replace "|{" "{" body- | (heading, body) <- sections out,- heading == name- ]+ sectionsNamed name = [body | (heading, body) <- sections out, heading == name] fixities = Map.fromList (concatMap declared (sectionsNamed "fixities")) declared = mapMaybe fixityEntry . T.splitOn "," ownExports section = Set.fromList- [ OpName name- | (parent, kids) <- exportEntries section,- name <- parent : kids,- isNothing (moduleOf name)- ]- reexports = concatMap reexportsIn . T.words+ [OpName name | name <- exportedIn section, isNothing (moduleOf name)]+ reexports section =+ [ (m, OpName name)+ | Just (m, name) <- fmap moduleOf (exportedIn section),+ not (T.null name)+ ]+ exportedIn section =+ [ name+ | (parent, parentExported, kids) <- exportEntries section,+ name <- [parent | parentExported] <> kids+ ] membersIn section = Map.fromListWith Set.union [ (nameOnly parent, Set.fromList (fmap nameOnly kids))- | (parent, kids@(_ : _)) <- exportEntries section+ | (parent, _, kids@(_ : _)) <- exportEntries section ] -- | The names an interface declares as types.@@ -170,8 +172,6 @@ keyword `elem` (["data", "type", "newtype", "class"] :: [Text]), name <- take 1 (dropWhile (`elem` (["family", "role", "instance"] :: [Text])) rest) ]- where- indented l = maybe False (== ' ') (fst <$> T.uncons l) -- | Sort declared fixities into the namespaces they govern. namespaced :: Set OpName -> Map OpName Fixity -> Fixities@@ -184,17 +184,11 @@ -- | Split the output into sections. sections :: Text -> [(Text, Text)]-sections = go . T.lines- where- go = \case- [] -> []- (l : ls)- | indented l -> go ls- | otherwise ->- let (body, rest) = span indented ls- (heading, opening) = T.breakOn " " l- in (heading, T.unwords (opening : body)) : go rest- indented l = maybe False (== ' ') (fst <$> T.uncons l)+sections out =+ [ (heading, T.unwords (opening : body))+ | (l, body) <- blocksOf (T.lines out),+ let (heading, opening) = T.breakOn " " l+ ] -- | One entry of a @fixities@ line: @infixl 9 !@ and the like. fixityEntry :: Text -> Maybe (OpName, Fixity)@@ -210,8 +204,10 @@ _ -> Nothing -- | Split an exports section into its entries, keeping the members an entry--- wears in braces with the name they belong to.-exportEntries :: Text -> [(Text, [Text])]+-- wears in braces with the name they belong to, and whether that name is+-- exported itself, which a bar before the braces, as in @T|{...}@, says it+-- is not.+exportEntries :: Text -> [(Text, Bool, [Text])] exportEntries = go where go text = case T.uncons (T.dropWhile (== ' ') text) of@@ -222,22 +218,16 @@ in case T.uncons rest of Just ('{', inside) -> let (kids, after) = T.break (== '}') inside- in (bare name, T.words kids) : go (T.drop 1 after)- _ -> (bare name, []) : go rest+ entry = case T.unsnoc (bare name) of+ Just (parent, '|') -> (parent, False, T.words kids)+ _ -> (bare name, True, T.words kids)+ in entry : go (T.drop 1 after)+ _ -> (bare name, True, []) : go rest bare = T.dropWhileEnd (== ',') -- | An exported name without the module that declared it. nameOnly :: Text -> OpName nameOnly t = OpName (maybe t snd (moduleOf t))---- | The names an export entry reexports, with the module that declared--- each.-reexportsIn :: Text -> [(Text, OpName)]-reexportsIn = mapMaybe qualified . T.split (`elem` ("{}," :: String))- where- qualified name = case moduleOf name of- Just (m, n) | not (T.null n) -> Just (m, OpName n)- _ -> Nothing -- | Split a name into the module that declared it and the name itself. moduleOf :: Text -> Maybe (Text, Text)
src/Tilia/Fixity/PackageDb.hs view
@@ -13,7 +13,6 @@ where import Control.Monad (filterM)-import Data.Char (isSpace) import Data.List (nub) import Data.Map.Strict qualified as Map import Data.Maybe (mapMaybe)@@ -22,7 +21,7 @@ import System.Directory (doesDirectoryExist, findExecutable) import System.FilePath ((</>)) import Tilia.Process (readProgramOutput)-import Tilia.Utils (fieldWords, quietly)+import Tilia.Utils (blocksOf, fieldWords, looksLikeModule, quietly) -- | An installed package. data InstalledPackage = InstalledPackage@@ -137,9 +136,6 @@ (m : rest) | looksLikeModule m -> m : go rest (_ : rest) -> go rest [] -> []- looksLikeModule m = case T.uncons m of- Just (c, _) -> c `elem` ['A' .. 'Z'] && not (T.any (== ':') m)- Nothing -> False -- | The modules an @exposed-modules@ field names that another package -- holds, each with its name there.@@ -154,19 +150,9 @@ -- | Split a record into its fields. parseFields :: Text -> Map.Map Text Text-parseFields = Map.fromList . mapMaybe field . groups . T.lines+parseFields = Map.fromList . mapMaybe field . blocksOf . T.lines where- groups = \case- [] -> []- (l : ls)- | isContinuation l -> groups ls- | otherwise ->- let (continued, rest) = span isContinuation ls- in (l : continued) : groups rest- isContinuation l = not (T.null l) && isSpace (T.head l)-- field [] = Nothing- field (l : rest) = case T.breakOn ":" l of+ field (l, rest) = case T.breakOn ":" l of (key, value) | not (T.null value) -> Just (T.strip key, T.unwords (T.drop 1 value : fmap T.strip rest))
src/Tilia/Fixity/Plan.hs view
@@ -20,15 +20,13 @@ where import Control.Applicative ((<|>))-import Control.Concurrent (getNumCapabilities)+import Control.Concurrent (MVar, getNumCapabilities) import Control.Concurrent.QSem (newQSem, signalQSem, waitQSem) import Control.DeepSeq (force) import Control.Exception (bracket_, evaluate) import Control.Monad (filterM, join)-import Crypto.Hash.SHA256 qualified as SHA256-import Data.ByteString.Base16 qualified as B16-import Data.ByteString.Lazy qualified as BL import Data.Choice (Choice, fromBool, pattern Do)+import Data.Containers.ListUtils (nubOrd) import Data.Foldable (toList) import Data.List.NonEmpty (NonEmpty ((:|))) import Data.List.NonEmpty qualified as NE@@ -39,28 +37,33 @@ import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as T-import Data.Text.Encoding qualified as T import GHC.Hs (HsModule) import GHC.Hs.Extension (GhcPs) import GHC.LanguageExtensions.Type (Extension (ImplicitPrelude)) import System.Directory (doesFileExist) import System.FilePath ((</>))-import Tilia.Cabal.Package (PackageReader, declaredModules, newPackageReader, readPackageDescription)+import Tilia.Cabal.Package+ ( PackageReader,+ declaredModules,+ newPackageReader,+ packageExtensions,+ readPackageDescription,+ ) import Tilia.Cabal.Plan import Tilia.Cabal.Project (cabalFilesIn) import Tilia.Cpp.Directives (branchLeaves, withoutRuledOut) import Tilia.Cpp.Macros (Macros (..)) import Tilia.Fixity import Tilia.Fixity.Archive (Archive (..), InArchive (..), moduleEndings, moduleIn, pathOfModule, readArchive, writtenForHsc)-import Tilia.Fixity.Cabal (declaredExtensions, packageModules)+import Tilia.Fixity.Cabal (packageModules) import Tilia.Fixity.Cache import Tilia.Fixity.HiFile (HiFile, primopFixities) import Tilia.Fixity.Interface import Tilia.Fixity.PackageDb-import Tilia.Memo (memoized, newFlights, newMemo)+import Tilia.Memo (Flights, memoized, newFlights, newMemo) import Tilia.Parser-import Tilia.Pragma (effectiveExtensions)-import Tilia.Utils (byteOrderMark, inParallel, quietly, readFileText, sha256Of)+import Tilia.Pragma (Defaults, effectiveExtensions)+import Tilia.Utils (byteOrderMark, inParallel, quietly, readUtf8File, sha256Of, sha256OfFile, tshow) ---------------------------------------------------------------------------- -- Resolving@@ -93,7 +96,7 @@ BuildPlan -> IO Resolver newResolver plan = do- packageReader <- newPackageReader (plannedCompiler plan)+ packageReader <- newPackageReader (plannedCompiler plan) [] newResolverVia (Do #useCache) [FromInterface, FromSource] packageReader plan -- | 'newResolver', restricted to the routes given.@@ -140,17 +143,13 @@ answersRead <- newMemo extensionsRead <- newMemo archivesRead <- newMemo- interfacesRead <- newMemo reading <- newQSem =<< getNumCapabilities- let interfaceOf modName =- memoized flights interfacesRead Nothing modName $- case Map.lookup modName primopFixities of- Just declared -> pure (Just (asInterface declared))- Nothing -> case Map.lookup modName interfaces of- Nothing -> pure Nothing- Just (_, path) ->- bracket_ (waitQSem reading) (signalQSem reading) $- readInterface modName path+ interfaceOf <- interfaceMemo flights $ \modName ->+ case Map.lookup modName interfaces of+ Nothing -> pure Nothing+ Just (_, path) ->+ bracket_ (waitQSem reading) (signalQSem reading) $+ readInterface modName path let workings = Workings { wkRoutes = routes,@@ -184,7 +183,7 @@ configurationsOf (macrosOf plan) extensions modName text stamp = digestOf . T.intercalate "\n" $- [T.pack (show extensions), macrosRead, text]+ [tshow extensions, macrosRead, text] case digestOf . T.pack <$> Map.lookup modName local of Nothing -> summarized Just key ->@@ -193,19 +192,35 @@ (cachedSummaries cache key stamp) (storeSummaries cache key stamp) (Just <$> summarized)- macrosRead = T.pack (show (macrosOf plan))+ macrosRead = tshow (macrosOf plan) extensionsOf modName | Just path <- Map.lookup modName local = either (const Nothing) Just <$> packageReader path | Just (package, tarball) <- Map.lookup modName index = memoized flights extensionsRead Nothing package $- fmap declaredExtensions . (archiveCabal =<<) <$> archiveOf tarball+ fmap packageExtensions . (archiveCabal =<<) <$> archiveOf tarball | otherwise = pure Nothing archiveOf tarball = memoized flights archivesRead Nothing (T.pack tarball) (readArchive tarball) moduleInArchive tarball modName = (moduleIn modName =<<) <$> archiveOf tarball pure Resolver{askModule = reach Set.empty} +-- | A module's interface, read once, with the primops' fixities standing+-- in for that of @GHC.Prim@, which has no file.+interfaceMemo ::+ -- | Who is working out what, across the resolver's tables.+ MVar Flights ->+ -- | How to read a module's interface.+ (Text -> IO (Maybe Interface)) ->+ IO (Text -> IO (Maybe Interface))+interfaceMemo flights read' = do+ interfacesRead <- newMemo+ pure $ \modName ->+ memoized flights interfacesRead Nothing modName $+ case Map.lookup modName primopFixities of+ Just declared -> pure (Just (asInterface declared))+ Nothing -> read' modName+ -- | A 'Resolver' that reads every module out of the interface given for it, -- for a program that has compiled the project and holds its interfaces. newResolverFrom ::@@ -214,15 +229,9 @@ IO Resolver newResolverFrom given = do flights <- newFlights- interfacesRead <- newMemo answersRead <- newMemo- let interfaceOf modName =- memoized flights interfacesRead Nothing modName $- case Map.lookup modName primopFixities of- Just declared -> pure (Just (asInterface declared))- Nothing ->- (either (const Nothing) Just . fromHiFile modName =<<)- <$> given modName+ interfaceOf <- interfaceMemo flights $ \modName ->+ (either (const Nothing) Just . fromHiFile modName =<<) <$> given modName pure Resolver { askModule = \modName ->@@ -329,7 +338,7 @@ writtenForHsc path = pure mempty | Just path <- Map.lookup modName wkLocal =- readFileText path >>= \case+ readUtf8File path >>= \case Nothing -> pure unreadable Just source -> fromSummaries (wkReach visiting') modName@@ -407,11 +416,8 @@ one (p, tarball) = do let key = cacheKey p let exposed = Map.lookup (ppName p, ppVersion p) byNameVersion- held <- fromCabalFile cache key tarball p- let modules = case (exposed, held) of- (Nothing, Nothing) -> []- (a, b) -> concat (catMaybes [a, b])- pure [(m, (key, tarball)) | m <- modules]+ held <- fromCabalFile cache tarball p+ pure [(m, (key, tarball)) | m <- concat (catMaybes [exposed, held])] -- | Where each installed module's compiled interface is. interfaceIndex :: [InstalledPackage] -> Map Text (Text, FilePath)@@ -458,23 +464,23 @@ declarers <- inParallel asked- (distinct (fmap fst (interfaceReexports iface)))+ (distinct [m | (m, _, _) <- interfaceReexports iface]) let declarer m = join (lookup m declarers) pure Established { establishedFixities =- Map.union (interfaceDeclares iface) . Map.unions $- [ Map.filterWithKey- (\(_, o) _ -> o == op)- (interfaceDeclares declared)- | (m, op) <- interfaceReexports iface,- Just declared <- [declarer m]+ Map.union (interfaceDeclares iface) . Map.fromList $+ [ ((namespace, op), fixity)+ | (m, namespace, op) <- interfaceReexports iface,+ Just declared <- [declarer m],+ Just fixity <-+ [Map.lookup (namespace, op) (interfaceDeclares declared)] ], establishedUnsettled = Map.fromListWith Set.union- [ ([m], Set.fromList [(InTypes, op), (InTerms, op)])- | (m, op) <- interfaceReexports iface,+ [ ([m], Set.singleton (namespace, op))+ | (m, namespace, op) <- interfaceReexports iface, Nothing <- [declarer m] ], establishedUntold = Set.empty,@@ -491,9 +497,6 @@ fromCabalFile :: -- | Where to remember the answer. Cache ->- -- | What to file it under. Carries the hash the tarball was verified- -- against, so a changed tarball misses rather than matching stale data.- Text -> -- | The tarball to read the @.cabal@ file out of. FilePath -> -- | The package it belongs to, consulted for the hash to verify against.@@ -501,11 +504,13 @@ -- | The modules it holds, or 'Nothing' if the tarball is absent, fails -- verification, or holds no @.cabal@ file. IO (Maybe [Text])-fromCabalFile cache key tarball p =+fromCabalFile cache tarball p = recalled (cachedModules cache key) (storeModules cache key) $ verified p tarball >>= \case False -> pure Nothing True -> packageModules tarball+ where+ key = cacheKey p -- | Does the tarball hash to what the plan says it should? verified :: PlanPackage -> FilePath -> IO Bool@@ -516,12 +521,6 @@ actual <- sha256OfFile tarball pure (actual == T.toLower expected) --- | The SHA-256 of a file, as lower-case hex.-sha256OfFile :: FilePath -> IO Text-sha256OfFile path = do- bytes <- BL.readFile path- pure (T.decodeUtf8Lenient (B16.encode (SHA256.hashlazy bytes)))- -- | Read what a module exports out of a tarball, following re-exports. fromSource :: -- | What a tarball holds for a module.@@ -622,7 +621,7 @@ -- | Where each module that lives in a directory rather than an archive is. localModules :: BuildPlan -> IO (Map Text FilePath) localModules plan =- Map.unions <$> traverse forPackage (concatMap directoryOf (bpPackages plan))+ Map.unions <$> traverse forPackage (nubOrd (concatMap directoryOf (bpPackages plan))) where directoryOf p = case ppSource p of LocalPackage dir -> [dir]@@ -919,8 +918,8 @@ configurationsOf :: -- | What the plan settles about the questions its conditionals ask. Macros ->- -- | What extensions the module's package puts in force.- [Extension] ->+ -- | What the module's package puts in force.+ Defaults -> -- | The module's name, for the parser to put in its errors. Text -> -- | Its text.@@ -937,6 +936,6 @@ named = T.unpack modName -- | Does this module see the Prelude without importing it?-hasImplicitPrelude :: [Extension] -> Text -> Choice "implicitPrelude"+hasImplicitPrelude :: Defaults -> Text -> Choice "implicitPrelude" hasImplicitPrelude extensions source = fromBool (ImplicitPrelude `elem` effectiveExtensions extensions source)
src/Tilia/Format.hs view
@@ -34,6 +34,7 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Version (Version, versionBranch)+import GHC.Driver.Session (OnOff (..)) import GHC.LanguageExtensions.Type (Extension (ImplicitPrelude)) import Tilia.Cabal.Package ( PackageProblem (..),@@ -51,6 +52,7 @@ versionMacros, ) import Tilia.Cabal.Project (ProjectRoot (..), findProjectRoot)+import Tilia.Cabal.Target (packageFilesOf) import Tilia.Cpp ( CppError (..), correspondingBranches,@@ -90,7 +92,7 @@ pmGathered, pmModule, )-import Tilia.Pragma (effectiveExtensions, movesPositions)+import Tilia.Pragma (defaultsOf, effectiveExtensions, movesPositions) import Tilia.Render (RenderConfig (..), defaultRenderConfig, renderModule) import Tilia.Utils (collected, spellList, tshow) @@ -117,6 +119,8 @@ NotEquivalent FilePath Text | -- | Formatting is not idempotent. NotIdempotent FilePath Text+ | -- | Formatting the file threw an exception, shown.+ Threw FilePath Text -- | Say what went wrong. describeFormatError :: Palette -> FormatError -> Text@@ -132,7 +136,10 @@ <> describePackageProblem problem NotParsed e -> "cannot parse " <> located (describeParseError e) PositionPragmas path ->- "will not format " <> file path <> ": it uses {-# LINE #-} pragmas, and no reformatting can leave those true"+ "will not format "+ <> file path+ <> ": it uses {-# LINE #-} or {-# COLUMN #-} pragmas, and no"+ <> " reformatting can leave those true" CppUnsupported path why -> "will not format " <> file path <> ": " <> describeCppError why Unreadable path why -> "cannot read " <> file path <> ": " <> why@@ -140,6 +147,8 @@ "formatting " <> file path <> " changed the program: " <> why NotIdempotent path why -> "formatting " <> file path <> " is not idempotent: " <> why+ Threw path why ->+ "formatting " <> file path <> " threw an exception: " <> why UnknownFixity path unknown -> "will not format " <> file path@@ -180,6 +189,7 @@ Unreadable{} -> 16 NotEquivalent{} -> 17 NotIdempotent{} -> 18+ Threw{} -> 19 CppUnsupported _ why -> case why of MalformedConditional{} -> 9 TooManyConfigurations -> 10@@ -200,14 +210,11 @@ Unreadable{} -> False NotEquivalent{} -> False NotIdempotent{} -> False+ Threw{} -> False NoProject{} -> False NoBuildPlan{} -> False -- | What a run works out once and then uses for every file.------ Finding the project, solving its build plan and building a resolver cost--- about as much as formatting a small file, and none of it depends on which--- file is being formatted. data Session = Session { -- | The project. sessionRoot :: ProjectRoot,@@ -222,9 +229,8 @@ sessionCheckAst :: Choice "checkAst", -- | Whether to check idempotence. sessionCheckIdempotence :: Choice "checkIdempotence",- -- | An account of how each file's fixities were determined. 'Nothing'- -- when this information was not requested, which is what keeps an- -- ordinary run from doing any of the work.+ -- | An account of how each file's fixities were determined, or 'Nothing'+ -- where none was asked for. sessionFixityNotes :: Maybe (IORef (Map FilePath FixityNotes)) } @@ -267,7 +273,8 @@ then [FromInterface, FromSource] <$ liftIO (fetchUninstalled caching root plan) else pure [FromInterface] pure (plan, routes)- packageReader <- liftIO (newPackageReader (plannedCompiler plan))+ packages <- liftIO (packageFilesOf project)+ packageReader <- liftIO (newPackageReader (plannedCompiler plan) packages) resolver <- liftIO (newResolverVia caching routes packageReader plan) notes <- if isTrue debugFixity@@ -288,7 +295,9 @@ -- otherwise works out with Cabal, the package database, and the sources of -- dependencies. data Compiled = Compiled- { -- | The extensions the component puts in force.+ { -- | The extensions the component puts in force, which an edition a+ -- module names adds to, as which of them its own edition brought is not+ -- known. compiledExtensions :: [Extension], -- | The version of the compiler. compiledWith :: Version,@@ -322,7 +331,8 @@ versionMacros (Just (numbers compiledWith)) (fmap numbers compiledPackages),- sessionPackage = \_ -> pure (Right compiledExtensions),+ sessionPackage =+ \_ -> pure (Right (defaultsOf [] (On <$> compiledExtensions))), sessionCheckAst = checkAst, sessionCheckIdempotence = checkIdempotence, sessionFixityNotes = Nothing@@ -330,10 +340,8 @@ where numbers = fmap toInteger . versionBranch --- | What the run made of every file's operators, by file.------ Empty unless the session was asked to keep an account of it. Nothing here--- is rendered; 'Tilia.Fixity.Debug.renderFixityNotes' does that.+-- | What the run made of every file's operators, by file, empty unless the+-- session keeps an account of it. fixityNotesOf :: Session -> IO (Map FilePath FixityNotes) fixityNotesOf session = case sessionFixityNotes session of Nothing -> pure Map.empty
src/Tilia/Ignore.hs view
@@ -7,11 +7,11 @@ parseIgnoreFile, isIgnored, isIgnoredInProject,- ignoreFilesAbove,- belowRoot,+ withoutIgnored, ) where +import Data.ByteString qualified as BS import Data.Char ( isAlpha, isAlphaNum,@@ -25,17 +25,17 @@ isSymbol, isUpper, )-import Data.List (inits, isPrefixOf, isSuffixOf, stripPrefix, tails)+import Data.List (inits, isPrefixOf, isSuffixOf, tails) import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map import Data.Maybe (mapMaybe) import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as T-import Data.Text.IO qualified as T+import Data.Text.Encoding qualified as T import System.Directory (canonicalizePath, doesFileExist)-import System.FilePath (joinPath, normalise, splitDirectories, (</>))-import Tilia.Utils (quietly)+import System.FilePath (joinPath, (</>))+import Tilia.Utils (pathUnder, quietly) -- | The patterns of one ignore file, in the order they are written. newtype IgnoreFile = IgnoreFile [Pattern]@@ -257,12 +257,26 @@ IO Bool isIgnoredInProject root path = quietly False $ do file <- canonicalizePath path- case belowRoot root file of- Nothing -> pure False- Just below -> do- ignoreFiles <- ignoreFilesAbove root [below]- pure (isIgnored ignoreFiles below)+ null <$> withoutIgnored root [file] +-- | The files the @.tiliaignore@ files of a project do not ignore, in the+-- order given.+withoutIgnored ::+ -- | The project root, canonical.+ FilePath ->+ -- | The files, canonical.+ [FilePath] ->+ IO [FilePath]+withoutIgnored root files = do+ ignoreFiles <- ignoreFilesAbove root (Map.elems below)+ pure+ [ file+ | file <- files,+ maybe True (not . isIgnored ignoreFiles) (Map.lookup file below)+ ]+ where+ below = Map.fromList [(file, b) | file <- files, Just b <- [pathUnder root file]]+ -- | The @.tiliaignore@ files in the project root and in every directory -- between it and the given files, each by its directory's segments relative -- to the root.@@ -276,15 +290,7 @@ let file = joinPath (root : directory) </> ".tiliaignore" exists <- doesFileExist file if exists- then (\t -> [(directory, parseIgnoreFile t)]) <$> T.readFile file+ then do+ bytes <- BS.readFile file+ pure [(directory, parseIgnoreFile (T.decodeUtf8Lenient bytes))] else pure []---- | A path's segments relative to the project root, if it is under it.-belowRoot ::- -- | The project root.- FilePath ->- -- | The path.- FilePath ->- Maybe [FilePath]-belowRoot root path =- stripPrefix (splitDirectories root) (splitDirectories (normalise path))
src/Tilia/Imports.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ViewPatterns #-} -- | Putting a module's imports in order.
src/Tilia/Parser.hs view
@@ -5,6 +5,7 @@ -- | Turning source text into a syntax tree and a comment stream. module Tilia.Parser ( ParsedModule (..),+ writtenInHeader, parseModule, parseConfiguration, readAlike,@@ -19,16 +20,15 @@ import Data.Foldable (toList) import Data.IntMap.Strict (IntMap) import Data.IntMap.Strict qualified as IntMap-import Data.List (isSuffixOf, nub, sortOn)-import Data.Maybe (listToMaybe, mapMaybe)+import Data.List (isSuffixOf, sortOn)+import Data.Maybe (catMaybes, listToMaybe) import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as T import GHC.Data.EnumSet qualified as EnumSet import GHC.Data.FastString (mkFastString) import GHC.Data.StringBuffer qualified as GHC-import GHC.Driver.Session qualified as GHC-import GHC.Hs (HsModule (..))+import GHC.Hs (AnnsModule (..), EpAnn (anns), HsModule (..), XModulePs (..)) import GHC.Hs.Extension (GhcPs) import GHC.LanguageExtensions.Type (Extension) import GHC.Parser qualified as GHC@@ -39,7 +39,7 @@ import GHC.Utils.Error qualified as GHC import GHC.Utils.Outputable qualified as GHC import Tilia.Gathered (Gathered (..), gathered)-import Tilia.Pragma (effectiveExtensions)+import Tilia.Pragma (Defaults, effectiveExtensions) import Tilia.Source (Lines, Source, SourceType (..), Written (..), lineTexts, linesOf, sourceOf) import Tilia.Span (Span (..), startPoint) import Tilia.Span.Ghc (spanOf)@@ -59,8 +59,8 @@ -- The lexer skips a @#!@ line, which puts it in no annotation and no -- node, so nothing downstream could put it back. pmPrologue :: [Text],- -- | Where the file header stops and the module proper begins, if the- -- module has anything after its header.+ -- | Where the file header stops: at the @module@ line, or where a+ -- module without one begins, if it has anything but a header. -- -- GHC reads pragmas from the header and nowhere else, so this is the -- line that decides whether a @{-# … #-}@ is a pragma at all. One@@ -69,11 +69,18 @@ pmHeaderEnd :: Maybe Span } +-- | Was something that begins here written in the header, before what+-- 'pmHeaderEnd' marks?+writtenInHeader :: Maybe Span -> Span -> Bool+writtenInHeader headerEnd s =+ maybe True ((startPoint s <) . startPoint) headerEnd+ -- | Parse a module. parseModule :: -- | Parser config. ParserConfig ->- -- | Path, used only in positions reported back.+ -- | Path, reported back in positions, whose extension tells a signature+ -- from a module. FilePath -> -- | The source. Text ->@@ -84,7 +91,8 @@ -- | Parse one configuration of a module. parseConfiguration :: ParserConfig ->- -- | Path, used only in positions reported back.+ -- | Path, reported back in positions, whose extension tells a signature+ -- from a module. FilePath -> -- | The lines of the module as written, except for the lines that do not -- belong to this configuration.@@ -138,8 +146,9 @@ . GHC.diagnosticMessage GHC.NoDiagnosticOpts . GHC.errMsgDiagnostic --- | Return each line a token other than a comment begins on, and whether--- the leftmost such token on the line begins an item of the top level.+-- | Return each line a token other than a comment begins on, down to the+-- first item of the top level after the imports, and whether the leftmost+-- such token on the line begins an item of the top level. -- 'Nothing' if the module has no imports, writes its top level in explicit -- braces, or does not lex. importLayout ::@@ -223,41 +232,22 @@ stateFor :: ParserConfig -> FilePath -> Text -> GHC.PState stateFor config path source = GHC.initParserState- (parserOpts config')+ (parserOpts (effectiveExtensions (pcDefaults config) source)) (GHC.stringBufferFromByteString (T.encodeUtf8 source)) (GHC.mkRealSrcLoc (mkFastString path) 1 1)- where- config' =- config- { pcExtensions = withImplied (effectiveExtensions (pcExtensions config) source)- } -- | Would the parser read these two texts with the same extensions? readAlike :: ParserConfig -> Text -> Text -> Bool readAlike config a b = bits a == bits b where- bits t =- GHC.pExtsBitmap- ( parserOpts- config- { pcExtensions = withImplied (effectiveExtensions (pcExtensions config) t)- }- )---- | Close a set of extensions under what they imply.-withImplied :: [Extension] -> [Extension]-withImplied = settle . nub- where- settle es =- let es' = nub (es <> concatMap implied es)- in if length es' == length es then es else settle es'- implied e = [to | (from, GHC.On to) <- GHC.impliedXFlags, from == e]+ bits =+ GHC.pExtsBitmap . parserOpts . effectiveExtensions (pcDefaults config) -- | Options to parse with.-parserOpts :: ParserConfig -> GHC.ParserOpts-parserOpts ParserConfig{pcExtensions} =+parserOpts :: [Extension] -> GHC.ParserOpts+parserOpts extensions = GHC.mkParserOpts- (EnumSet.fromList pcExtensions)+ (EnumSet.fromList extensions) quietDiagnostics False -- safe imports True -- keep Haddock tokens@@ -290,8 +280,12 @@ -- | The start of the first thing that is not part of the header. headerEndOf :: HsModule GhcPs -> Maybe Span headerEndOf hsModule =- listToMaybe . sortOn startPoint $- mapMaybe spanOf (hsmodImports hsModule) <> mapMaybe spanOf (hsmodDecls hsModule)+ listToMaybe . sortOn startPoint . catMaybes $+ [spanOf (am_mod moduleAnns), spanOf (am_sig moduleAnns)]+ <> fmap spanOf (hsmodImports hsModule)+ <> fmap spanOf (hsmodDecls hsModule)+ where+ moduleAnns = anns (hsmodAnn (hsmodExt hsModule)) -- | Why a module did not parse. data ParseError = ParseError@@ -308,8 +302,8 @@ -- | Parser configuration. newtype ParserConfig = ParserConfig- { -- | Extensions to enable before parsing.- pcExtensions :: [Extension]+ { -- | What the component of the module puts in force before its pragmas.+ pcDefaults :: Defaults } -- | What a file's name says it holds.
src/Tilia/Pragma.hs view
@@ -1,22 +1,38 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} -- | Pragma-related helpers. module Tilia.Pragma ( movesPositions,+ Defaults,+ defaultsOf, effectiveExtensions, lookupExtension, lookupEdition,- applyExtensionName,+ nameSwitch,+ flagSwitches,+ optionFlags,+ pragmaFlags, languageNames,+ withoutSkippedComments,+ sameInForce, ) where +import Control.Monad ((<=<))+import Data.Char (isAlphaNum, isSpace) import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map+import Data.Maybe (mapMaybe, maybeToList)+import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as T+import GHC.Data.FastString (fsLit) import GHC.Driver.Session qualified as GHC import GHC.LanguageExtensions.Type (Extension (..))+import GHC.Parser.Header (toArgs)+import GHC.Types.SrcLoc (mkRealSrcLoc, unLoc)+import Tilia.Utils (tshow) -- | Recognize @{-# LINE #-}@ and @{-# COLUMN #-}@ pragmas. movesPositions :: Text -> Bool@@ -25,43 +41,152 @@ positional body = T.toUpper (T.takeWhile (/= ' ') body) `elem` ["LINE", "COLUMN"] +-- | What a component puts in force before the pragmas of a module: its+-- @default-language@ and its @default-extensions@.+data Defaults = Defaults+ { -- | What it turns on and off on top of its edition, in order, and on top+ -- of an edition the module names in its place.+ defaultsSwitches :: [GHC.OnOff Extension],+ -- | What its edition and those switches put in force, worked out once.+ defaultsInForce :: [Extension]+ }+ deriving (Eq, Show)++-- | What a component puts in force, given the extensions of its edition and+-- what it switches on top of them, in order.+defaultsOf :: [Extension] -> [GHC.OnOff Extension] -> Defaults+defaultsOf edition switches = Defaults switches (foldl' switch edition switches)+ -- | The extensions actually in force in a module.+--+-- As with GHC, the edition the header names last takes the place of the+-- component's, wherever it is named, and what the component and then the+-- header switch is switched on top of it. effectiveExtensions ::- -- | What the package the module belongs to puts in force, which is its- -- @default-language@ and @default-extensions@ already resolved into a- -- set.- [Extension] ->- -- | The module's source, read here for its @LANGUAGE@ pragmas alone.+ -- | What the component the module belongs to puts in force.+ Defaults ->+ -- | The module's source, read here for the @LANGUAGE@, @OPTIONS_GHC@ and+ -- @OPTIONS@ pragmas of its header alone. Text -> [Extension]-effectiveExtensions package =- foldl' applyExtensionName package . concatMap pragmaNames . pragmaBodies+effectiveExtensions defaults source =+ foldl' switch inForce (concat switches) where- pragmaNames body =- let (keyword, names) = T.break (== ' ') body- in if T.toUpper keyword == "LANGUAGE"- then languageNames names- else []+ (editions, switches) = unzip (fmap ofPragma (headerPragmaBodies source))+ ofPragma body =+ let (keyword, rest) = T.break (== ' ') body+ in editionsAndSwitches (T.toUpper keyword) rest+ inForce = case concat editions of+ [] -> defaultsInForce defaults+ named ->+ foldl'+ switch+ (GHC.languageExtensions (Just (last named)))+ (defaultsSwitches defaults) --- | Turn on the extension a name spells, or off where the name puts @No@ in--- front of one, leaving the set as it is for a name that spells neither.-applyExtensionName :: [Extension] -> Text -> [Extension]-applyExtensionName acc name =- case T.stripPrefix "No" name >>= lookupExtension of- Just off -> filter (/= off) acc- Nothing -> case lookupExtension name of- Just on | on `notElem` acc -> acc <> [on]- _ -> acc+-- | Do two orders of the same pragmas, each given by its name and body,+-- leave the same extensions on?+--+-- Where none of them turns an extension off, every order turns on the same.+sameInForce :: [(Text, Text)] -> [(Text, Text)] -> Bool+sameInForce a b = not (turnsOff a) || inForce a == inForce b+ where+ turnsOff = foldl' (switchWith (const id) (\_ _ -> True)) False . switches+ inForce = foldl' (switchWith Set.insert Set.delete) Set.empty . switches+ switches = concatMap (uncurry switchesOf) +-- | What a pragma of this name and body turns on and off.+switchesOf :: Text -> Text -> [GHC.OnOff Extension]+switchesOf name = snd . editionsAndSwitches name++-- | The editions a pragma of this name and body names, and what it turns on+-- and off.+editionsAndSwitches :: Text -> Text -> ([GHC.Language], [GHC.OnOff Extension])+editionsAndSwitches name body =+ ( mapMaybe (lookupEdition <=< T.stripPrefix "-X") flags,+ concatMap flagSwitches flags+ )+ where+ flags = pragmaFlags name body++-- | The flags a pragma of this name and body gives GHC: each name of a+-- @LANGUAGE@ pragma after @-X@, and the flags of an options pragma.+pragmaFlags :: Text -> Text -> [Text]+pragmaFlags name body = case name of+ "LANGUAGE" -> ["-X" <> n | n <- languageNames body]+ "OPTIONS_GHC" -> optionFlags body+ "OPTIONS" -> optionFlags body+ _ -> []++-- | The flags of the body of an options pragma, split as GHC splits them.+optionFlags :: Text -> [Text]+optionFlags body =+ either (const (T.words body)) (fmap (T.pack . unLoc)) $+ toArgs (mkRealSrcLoc (fsLit "") 1 1) (T.unpack body)++-- | The extension a name turns on, or off where it puts @No@ in front of one.+nameSwitch :: Text -> Maybe (GHC.OnOff Extension)+nameSwitch name = case T.stripPrefix "No" name >>= lookupExtension of+ Just off -> Just (GHC.Off off)+ Nothing -> GHC.On <$> lookupExtension name++-- | What a flag of an @OPTIONS_GHC@ or @OPTIONS@ pragma turns on and off.+flagSwitches :: Text -> [GHC.OnOff Extension]+flagSwitches flag+ | Just name <- T.stripPrefix "-X" flag = maybeToList (nameSwitch name)+ | flag == "-cpp" = [GHC.On Cpp]+ | flag == "-fglasgow-exts" = GHC.On <$> GHC.glasgowExtsFlags+ | flag == "-fno-glasgow-exts" = GHC.Off <$> GHC.glasgowExtsFlags+ | Just name <- T.stripPrefix "-fno-" flag = GHC.Off <$> oldFlag name+ | Just name <- T.stripPrefix "-f" flag = GHC.On <$> oldFlag name+ | otherwise = []+ where+ oldFlag name = maybeToList (Map.lookup name extensionsByOldFlag)++-- | Turn an extension on or off in a list kept in the order extensions were+-- turned on in.+switch :: [Extension] -> GHC.OnOff Extension -> [Extension]+switch = switchWith turnOn (filter . (/=))+ where+ turnOn e acc = if e `elem` acc then acc else acc <> [e]++-- | Turn an extension on, and then what it implies, or turn it off alone, as+-- GHC does.+switchWith ::+ -- | How to turn one extension on.+ (Extension -> a -> a) ->+ -- | How to turn one extension off.+ (Extension -> a -> a) ->+ -- | What is on so far.+ a ->+ -- | What to turn on or off.+ GHC.OnOff Extension ->+ a+switchWith on off acc = \case+ GHC.On e ->+ foldl' (switchWith on off) (on e acc) (Map.findWithDefault [] e impliedBy)+ GHC.Off e -> off e acc++-- | What GHC turns on or off besides when it turns an extension on.+impliedBy :: Map Extension [GHC.OnOff Extension]+impliedBy =+ Map.fromListWith+ (flip (<>))+ [(from, [implied]) | (from, implied) <- GHC.impliedXFlags]+ -- | The extensions a @LANGUAGE@ pragma names, as written. languageNames :: Text -> [Text] languageNames = filter (not . T.null) . fmap T.strip . T.splitOn "," -- | Parse the name of an edition of the language, such as @GHC2021@. lookupEdition :: Text -> Maybe GHC.Language-lookupEdition name =- lookup name [(T.pack (show e), e) | e <- [minBound .. maxBound]]+lookupEdition name = Map.lookup name editionsByName +-- | Every edition of the language this compiler knows, by its name.+editionsByName :: Map Text GHC.Language+editionsByName =+ Map.fromList [(tshow e, e) | e <- [minBound .. maxBound]]+ -- | Parse an extension name. lookupExtension :: Text -> Maybe Extension lookupExtension name = Map.lookup name extensionsByName@@ -69,46 +194,115 @@ -- | Every extension this compiler knows, by the name one writes in a -- pragma. extensionsByName :: Map Text Extension-extensionsByName =- Map.fromList- [(T.pack (GHC.flagSpecName f), GHC.flagSpecFlag f) | f <- GHC.xFlags]+extensionsByName = byFlagName GHC.xFlags +-- | Every extension an old @-f@ flag such as @-fbang-patterns@ turns on, by+-- the flag without its @-f@.+extensionsByOldFlag :: Map Text Extension+extensionsByOldFlag = byFlagName GHC.fLangFlags++-- | Extensions by the names of the flags that turn them on.+byFlagName :: [GHC.FlagSpec Extension] -> Map Text Extension+byFlagName flags =+ Map.fromList [(T.pack (GHC.flagSpecName f), GHC.flagSpecFlag f) | f <- flags]+ -- | What every @{-# … #-}@ in a module has between its braces, each on one -- line. pragmaBodies :: Text -> [Text]-pragmaBodies = go+pragmaBodies = scanPragmas past where- go = step . T.dropWhile (\c -> c /= '{' && c /= '-' && c /= '"')+ past go t = case T.uncons t of+ Just ('"', after) -> go (skipString after)+ Just ('\'', after) -> go (skipCharacter after)+ _ -> go (skipCode t) - step t+ skipString = inString . T.dropWhile (\c -> c /= '"' && c /= '\\')++ inString t+ | T.null t = t+ | Just after <- T.stripPrefix "\\" t = skipString (T.drop 1 after)+ | Just after <- T.stripPrefix "\"" t = after+ | otherwise = skipString (T.drop 1 t)++ skipCharacter t = case T.unpack (T.take 2 t) of+ ['\\', _] -> T.drop 1 (T.dropWhile (/= '\'') (T.drop 2 t))+ [c, '\''] | c /= '\'' -> T.drop 2 t+ _ -> t++ skipCode t =+ let (plain, rest) = T.break opens (T.drop 1 t)+ before = maybe (T.head t) snd (T.unsnoc plain)+ in if "'" `T.isPrefixOf` rest && inName before then skipCode rest else rest+ opens c = c == '{' || c == '-' || c == '"' || c == '\''+ inName c = isAlphaNum c || c == '_' || c == '\''++-- | What the @{-# … #-}@ of a module's header have between their braces, each+-- on one line: those above the first thing that is neither a comment, a+-- directive nor a pragma, which are all GHC reads.+headerPragmaBodies :: Text -> [Text]+headerPragmaBodies = scanPragmas past+ where+ past go t = case T.uncons t of+ Just (c, _)+ | isSpace c -> go (T.stripStart t)+ | c == '#' -> go (T.dropWhile (/= '\n') t)+ _ -> []++-- | The bodies of the pragmas of a module, each on one line, carrying on past+-- what is neither a comment nor a pragma as the given function does.+scanPragmas :: ((Text -> [Text]) -> Text -> [Text]) -> Text -> [Text]+scanPragmas past = go+ where+ go t | T.null t = [] | Just body <- T.stripPrefix "{-#" t = case T.breakOn "#-}" body of (_, after) | T.null after -> []- (inner, after) -> T.unwords (T.words inner) : go (T.drop 3 after)+ (inner, after) ->+ T.unwords (T.words (withoutSkippedComments inner))+ : go (T.drop 3 after) | Just after <- T.stripPrefix "{-" t = go (skipBlock (1 :: Int) after) | opensLineComment t = go (T.dropWhile (/= '\n') t)- | Just after <- T.stripPrefix "\"" t = go (skipString after)- | otherwise = go (T.drop 1 t)+ | otherwise = past go t - opensLineComment t = case T.stripPrefix "--" t of- Nothing -> False- Just rest -> maybe True (not . symbolic . fst) (T.uncons rest)+-- | What a pragma holds between its @{-#@ and its @#-}@, without the+-- comments GHC skips in it: those of a @LANGUAGE@ pragma, which then comes+-- out on one line.+withoutSkippedComments :: Text -> Text+withoutSkippedComments inner+ | T.any (\c -> c == '-' || c == '{') inner,+ T.toUpper keyword == "LANGUAGE" =+ T.unwords (T.words (withoutComments inner))+ | otherwise = inner+ where+ keyword = T.takeWhile (not . isSpace) (T.stripStart inner) - symbolic c = c `elem` ("!#$%&*+./<=>?@\\^|-~:" :: String)+-- | A text without its comments, each of which leaves a space behind.+withoutComments :: Text -> Text+withoutComments t+ | T.null t = t+ | Just after <- T.stripPrefix "{-" t =+ " " <> withoutComments (skipBlock (1 :: Int) after)+ | opensLineComment t = withoutComments (T.dropWhile (/= '\n') t)+ | otherwise =+ let (plain, rest) = T.break (\c -> c == '{' || c == '-') (T.drop 1 t)+ in T.take 1 t <> plain <> withoutComments rest - skipBlock 0 t = t- skipBlock n t0 = inBlock n (T.dropWhile (\c -> c /= '{' && c /= '-') t0)+-- | Does a line comment begin here?+opensLineComment :: Text -> Bool+opensLineComment t = case T.stripPrefix "--" t of+ Nothing -> False+ Just rest ->+ maybe True (not . symbolic . fst) (T.uncons (T.dropWhile (== '-') rest))+ where+ symbolic c = c `elem` ("!#$%&*+./<=>?@\\^|-~:" :: String) - inBlock n t+-- | What follows the end of a block comment this deep in comments.+skipBlock :: Int -> Text -> Text+skipBlock 0 t = t+skipBlock n t0 = inBlock (T.dropWhile (\c -> c /= '{' && c /= '-') t0)+ where+ inBlock t | T.null t = t | Just after <- T.stripPrefix "{-" t = skipBlock (n + 1) after | Just after <- T.stripPrefix "-}" t = skipBlock (n - 1) after | otherwise = skipBlock n (T.drop 1 t)-- skipString = inString . T.dropWhile (\c -> c /= '"' && c /= '\\')-- inString t- | T.null t = t- | Just after <- T.stripPrefix "\\" t = skipString (T.drop 1 after)- | Just after <- T.stripPrefix "\"" t = after- | otherwise = skipString (T.drop 1 t)
+ src/Tilia/Pragma/Ghc.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE LambdaCase #-}+{-# OPTIONS_GHC -Wno-missing-fields #-}++-- | What GHC's own reader of flags makes of the flags of a header.+module Tilia.Pragma.Ghc+ ( InForce (..),+ inForceAfter,+ readFlags,+ )+where++import Data.List (isPrefixOf)+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as Map+import Data.Maybe (mapMaybe)+import Data.Text (Text)+import Data.Text qualified as T+import GHC.Driver.CmdLine (Flag (..), processArgs)+import GHC.Driver.Session qualified as GHC+import GHC.LanguageExtensions.Type (Extension)+import GHC.Platform (genericPlatform)+import GHC.Settings+import GHC.Settings.Config (cProjectVersion)+import GHC.Types.SafeHaskell (SafeHaskellMode)+import GHC.Types.SrcLoc (noLoc)+import GHC.Unit.Types (stringToUnitId)+import GHC.Utils.Fingerprint (fingerprint0)+import Tilia.Pragma (flagSwitches)++-- | What the flags of a header settle, whatever was in force before them.+data InForce = InForce+ { -- | Whether the flags, with what they imply, last turn each extension+ -- they switch on or off.+ inForceSwitches :: Map Extension Bool,+ -- | The edition named last, if one is.+ inForceEdition :: Maybe GHC.Language,+ -- | The Safe Haskell mode.+ inForceSafety :: SafeHaskellMode+ }++-- | What GHC's flag reader makes of these flags, read in order.+inForceAfter :: [Text] -> InForce+inForceAfter flags =+ InForce+ { inForceSwitches = Map.fromList (fmap switched (reverse (GHC.extensions dflags))),+ inForceEdition = GHC.language dflags,+ inForceSafety = GHC.safeHaskell dflags+ }+ where+ dflags = readFlags flags+ switched = \case+ GHC.On e -> (e, True)+ GHC.Off e -> (e, False)++-- | What GHC's flag reader makes of these flags, read in order from its+-- defaults, of the flags that can switch an extension, the edition or the+-- Safe Haskell mode.+readFlags :: [Text] -> GHC.DynFlags+readFlags flags = snd (GHC.runCmdLineP reading defaults)+ where+ reading = processArgs named (fmap noLoc args) (\_ -> pure [])+ named = mapMaybe ((`Map.lookup` switchingFlags) . drop 1) args+ args = fmap T.unpack flags++-- | The flags of GHC's that can switch an extension, the edition or the Safe+-- Haskell mode, by name: the @-X@ ones and the rest of those 'flagSwitches'+-- reads.+switchingFlags :: Map String (Flag (GHC.CmdLineP GHC.DynFlags))+switchingFlags =+ Map.fromList+ [(flagName f, f) | f <- GHC.flagsDynamic, switching (flagName f)]+ where+ switching name =+ "X" `isPrefixOf` name || not (null (flagSwitches (T.pack ('-' : name))))++-- | What GHC puts in force before it reads any flag.+defaults :: GHC.DynFlags+defaults = GHC.defaultDynFlags settings++-- | Enough of a compiler's settings for its flag reader, which runs no tool.+settings :: Settings+settings =+ Settings+ { sGhcNameVersion = GhcNameVersion "ghc" cProjectVersion,+ sFileSettings = FileSettings{},+ sTargetPlatform = genericPlatform,+ sToolSettings = ToolSettings{toolSettings_opt_P_fingerprint = fingerprint0},+ sPlatformMisc = PlatformMisc{},+ sUnitSettings = UnitSettings (stringToUnitId "base"),+ sRawSettings = []+ }
src/Tilia/Render/Class.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-}@@ -19,15 +18,13 @@ import Data.Choice (fromBool, pattern Do) import Data.Foldable (fold)-import Data.Function (on)-import Data.List (sortBy) import Data.Maybe (isNothing) import GHC.Builtin.Types (cTupleTyConName, isCTupleTyConName) import GHC.Core.Coercion.Axiom (Role (..)) import GHC.Hs import GHC.Types.Fixity (LexicalFixity (..)) import GHC.Types.Name.Reader (RdrName (..))-import GHC.Types.SrcLoc (GenLocated (..), leftmost_smallest, unLoc)+import GHC.Types.SrcLoc (GenLocated (..), unLoc) import Tilia.Doc.Combinators import Tilia.Render.Context import Tilia.Render.Data (dataDecl)@@ -87,7 +84,7 @@ body = includeUnless (null members)- (breakOrSpace <> indent (knotDeclsGrouped (ctxKnot ctx) ctx Associated members))+ (breakOrSpace <> indent (knotDeclsGrouped (ctxKnot ctx) ctx members)) members = inSourceOrder@@ -112,6 +109,7 @@ <> space <> indent (commaSep (fmap (align . at_ ctx (funDep ctx)) fdeps)) +-- | One functional dependency of a class, as in @a b -> c@. funDep :: Ctx -> FunDep GhcPs -> Doc funDep ctx (FunDep _ before after) = hsep (fmap (name ctx) before)@@ -146,7 +144,7 @@ body = includeUnless (null members) . indent $- breakOrSpace <> knotDeclsGrouped (ctxKnot ctx) ctx Associated members+ breakOrSpace <> knotDeclsGrouped (ctxKnot ctx) ctx members members = inSourceOrder@@ -258,6 +256,8 @@ includeUnless (null given) $ hardBreak <> indent (vsep (fmap (at_ ctx (tyFamInstEqn ctx)) given)) +-- | What the head of a family says of its result, a kind or a variable+-- naming it, if anything. familyResultSig :: Ctx -> LFamilyResultSig GhcPs -> Maybe Doc familyResultSig ctx (L _ sig) = case sig of NoSig NoExtField -> Nothing@@ -266,6 +266,7 @@ TyVarSig NoExtField bndr -> Just (txt "=" <> breakOrSpace <> at ctx bndr (tyVarBndr ctx)) +-- | The injectivity annotation of a type family, as in @| r -> a@. injectivityAnn :: Ctx -> InjectivityAnn GhcPs -> Doc injectivityAnn ctx (InjectivityAnn _ from to) = txt "|"@@ -312,8 +313,3 @@ Nominal -> txt "nominal" Representational -> txt "representational" Phantom -> txt "phantom"---- | Merge several lists of declarations back into the order they were--- written in.-inSourceOrder :: [[LHsDecl GhcPs]] -> [LHsDecl GhcPs]-inSourceOrder = sortBy (leftmost_smallest `on` getLocA) . concat
src/Tilia/Render/Context.hs view
@@ -43,17 +43,14 @@ ) where -import Data.List.NonEmpty (NonEmpty) import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map import Data.Set (Set) import Data.Set qualified as Set import Data.Text (Text)-import Data.Text qualified as T import GHC.Hs hiding (Fixity) import GHC.LanguageExtensions.Type (Extension (NegativeLiterals))-import GHC.Types.Name.Occurrence (occNameString)-import GHC.Types.Name.Reader (RdrName (..), rdrNameOcc)+import GHC.Types.Name.Reader (RdrName (..)) import GHC.Types.SrcLoc (GenLocated (..)) import GHC.Types.SrcLoc qualified as GHC import Tilia.Comments (Comment (..), CommentStyle (..), carriesOn, commentTrailing)@@ -61,10 +58,11 @@ import Tilia.Fixity ( Fixity, Namespace (..),- OpName (..), Resolution (..), Scope, lookupFixity,+ opName,+ qualifierOf, ) import Tilia.Render.Layout (Bracing (..)) import Tilia.Source@@ -114,10 +112,10 @@ -- | Signature declarations, needed by @let@ and @where@ bodies. knotSig :: Ctx -> Sig GhcPs -> Doc, -- | A run of declarations, needed by Template Haskell brackets.- knotDecls :: Ctx -> FamilyStyle -> [LHsDecl GhcPs] -> Doc,+ knotDecls :: Ctx -> [LHsDecl GhcPs] -> Doc, -- | A run of declarations that keeps the author's blank lines, needed -- by class and instance bodies.- knotDeclsGrouped :: Ctx -> FamilyStyle -> [LHsDecl GhcPs] -> Doc+ knotDeclsGrouped :: Ctx -> [LHsDecl GhcPs] -> Doc } -- | Whether a family or data declaration stands on its own or inside a@@ -186,14 +184,9 @@ operatorFixity :: Ctx -> Namespace -> RdrName -> Maybe Fixity operatorFixity ctx namespace name = do scope <- ctxScope ctx- case lookupFixity scope namespace qualifier op of+ case lookupFixity scope namespace (qualifierOf name) (opName name) of Resolved fixity _ -> Just fixity Unresolved _ -> Nothing- where- op = OpName (T.pack (occNameString (rdrNameOcc name)))- qualifier = case name of- Qual m _ -> Just (T.pack (moduleNameString m))- _ -> Nothing ---------------------------------------------------------------------------- -- What lies between two spans@@ -392,11 +385,11 @@ ---------------------------------------------------------------------------- -- Haddocks --- | The author's own text for the Haddock at this position, if we kept it.-writtenHaddock :: Ctx -> Maybe Span -> Maybe (NonEmpty Text)+-- | The Haddock the author wrote at this position, if we kept it.+writtenHaddock :: Ctx -> Maybe Span -> Maybe Comment writtenHaddock ctx ms = do s <- ms- c <- Map.lookup (spanStartLine s, spanStartColumn s) (ctxHaddocks ctx)+ c <- Map.lookup (startPoint s) (ctxHaddocks ctx) case commentStyle c of- DocComment -> Just (commentBody c)+ DocComment -> Just c _ -> Nothing
src/Tilia/Render/Data.hs view
@@ -205,10 +205,10 @@ argsAndResult = arguments <> resultType -- GHC keeps a GADT's result type without the brackets it was written- -- with, and there is one shape that does not survive losing them. A- -- kind signature needs them back: @MkT :: Int -> T :: Star@ reads as a- -- second signature on the constructor rather than as a kind on its- -- result, and does not parse at all.+ -- with, and three shapes need them back: a kind signature, as+ -- @MkT :: Int -> T :: Star@ does not parse, and a @forall@ or a context+ -- making up the whole signature, which would be read as the+ -- constructor's own. resultType = case unLoc con_res_ty of HsKindSig{} -> parens (hsType ctx con_res_ty) HsForAllTy{} | standsAlone -> parens (hsType ctx con_res_ty)@@ -361,6 +361,4 @@ ) <> indent (space <> txt "=" <> separator <> hsType ctx rhs) where- separator- | typeIsDocumented (unLoc rhs) = hardBreak- | otherwise = breakOrSpace+ separator = typeBreak (typeIsDocumented (unLoc rhs))
src/Tilia/Render/Declaration.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-}@@ -36,16 +35,17 @@ import Tilia.Render.Signature import Tilia.Render.Type import Tilia.Source (SourceType (..))-import Tilia.Span import Tilia.Span.Ghc --- | A run of declarations, with blank lines wherever we think they belong.-decls :: Ctx -> FamilyStyle -> [LHsDecl GhcPs] -> Doc-decls = declRun Disregard+-- | A run of declarations standing on their own, with blank lines wherever+-- we think they belong.+decls :: Ctx -> [LHsDecl GhcPs] -> Doc+decls ctx = declRun Disregard ctx Free --- | A run of declarations that keeps the author's grouping.-declsKeepingGroups :: Ctx -> FamilyStyle -> [LHsDecl GhcPs] -> Doc-declsKeepingGroups = declRun Respect+-- | A run of declarations in a class or an instance, which keeps the+-- author's grouping.+declsKeepingGroups :: Ctx -> [LHsDecl GhcPs] -> Doc+declsKeepingGroups ctx = declRun Respect ctx Associated -- | Whether the author's own blank lines are consulted. data Grouping@@ -119,7 +119,7 @@ _ -> False belongs previous current = (not isSignatureFile && isSignatureSeries ctx previous current)- || isDerivingSeries ctx previous current+ || isSeries ctx previous current || relatedDecls d current || relatedDecls previous current @@ -131,12 +131,15 @@ not (commentPrintedBetween ctx (spanOf x) (spanOf y)) _ -> False --- | Two standalone @deriving@ declarations the author ran together.-isDerivingSeries :: Ctx -> LHsDecl GhcPs -> LHsDecl GhcPs -> Bool-isDerivingSeries ctx x@(L _ a) y@(L _ b) = case (a, b) of- (DerivD{}, DerivD{}) ->- not (separatedByBlank ctx (spanOf x) (spanOf y))+-- | Two standalone @deriving@ declarations, or two splices, the author ran+-- together.+isSeries :: Ctx -> LHsDecl GhcPs -> LHsDecl GhcPs -> Bool+isSeries ctx x@(L _ a) y@(L _ b) = case (a, b) of+ (DerivD{}, DerivD{}) -> ranTogether+ (SpliceD{}, SpliceD{}) -> ranTogether _ -> False+ where+ ranTogether = not (separatedByBlank ctx (spanOf x) (spanOf y)) -- | The kinds of declaration that grouping distinguishes. data Kind@@ -151,7 +154,6 @@ | FamilyDeclaration | TypeSynonym | PragmaDeclaration- | TopLevelSplice | DocumentsNext | DocumentsPrevious | Unremarkable@@ -169,7 +171,7 @@ SigD _ sig | Just n <- specialisedName sig -> (PragmaDeclaration, [n]) ValD _ (FunBind _ (L _ n) _) -> (FunctionBody, [n])- ValD _ (PatBind _ p _ _) -> (FunctionBody, boundNames p)+ ValD _ (PatBind _ p _ _) -> (FunctionBody, collectPatBinders CollNoDictBinders p) ValD _ (PatSynBind _ (PSB _ (L _ n) _ _ _)) -> (PatternDefinition, [n]) AnnD _ (HsAnnotation _ (ValueAnnProvenance (L _ n)) _) -> (PragmaDeclaration, [n]) AnnD _ (HsAnnotation _ (TypeAnnProvenance (L _ n)) _) -> (PragmaDeclaration, [n])@@ -180,36 +182,16 @@ TyClD _ (SynDecl _ (L _ n) _ _ _) -> (TypeSynonym, [n]) TyClD _ (FamDecl _ (FamilyDecl _ _ _ (L _ n) _ _ _ _)) -> (FamilyDeclaration, [n]) KindSigD _ (StandaloneKindSig _ (L _ n) _) -> (KindSignature, [n])- SpliceD _ (SpliceDecl _ _ _) -> (TopLevelSplice, []) DocD _ (DocCommentNext _) -> (DocumentsNext, []) DocD _ (DocCommentPrev _) -> (DocumentsPrevious, []) _ -> (Unremarkable, []) --- | The names a pattern binding brings into scope.-boundNames :: LPat GhcPs -> [RdrName]-boundNames (L _ p) = case p of- VarPat _ (L _ n) -> [n]- AsPat _ (L _ n) inner -> n : boundNames inner- NPlusKPat _ (L _ n) _ _ _ _ -> [n]- LazyPat _ inner -> boundNames inner- BangPat _ inner -> boundNames inner- ParPat _ inner -> boundNames inner- SigPat _ inner _ -> boundNames inner- ViewPat _ _ inner -> boundNames inner- SumPat _ inner _ _ -> boundNames inner- TuplePat _ ps _ -> concatMap boundNames ps- ListPat _ ps -> concatMap boundNames ps- OrPat _ ps -> concatMap boundNames (NE.toList ps)- ConPat _ _ details -> concatMap boundNames (hsConPatArgs details)- _ -> []- -- | Should these two declarations be printed with no blank line between -- them? relatedDecls :: LHsDecl GhcPs -> LHsDecl GhcPs -> Bool relatedDecls a b = case (kindA, kindB) of (DocumentsNext, _) -> True (_, DocumentsPrevious) -> True- (TopLevelSplice, TopLevelSplice) -> not (blankBetween (spanOf a) (spanOf b)) pair | pair `elem` relatedKinds -> shareAName namesA namesB _ -> False where
src/Tilia/Render/Expression.hs view
@@ -1,11 +1,9 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} -- | Expressions, and the equations and blocks built out of them.@@ -19,9 +17,8 @@ import Control.Applicative ((<|>)) import Data.Choice (Choice, fromBool, isTrue, pattern Is, pattern Isn't)-import Data.Function (on) import Data.Generics.Schemes (listify)-import Data.List (sortBy, unsnoc)+import Data.List (unsnoc) import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty qualified as NE import Data.Map.Strict qualified as Map@@ -34,11 +31,9 @@ import GHC.Types.Fixity (LexicalFixity (..)) import GHC.Types.Name.Occurrence (isVarOcc, occNameString) import GHC.Types.Name.Reader (RdrName (..), mkVarUnqual, rdrNameOcc)-import GHC.Types.SourceText import GHC.Types.SrcLoc ( GenLocated (..), isZeroWidthSpan,- leftmost_smallest, noSrcSpan, unLoc, )@@ -51,7 +46,7 @@ ) import Tilia.Render.Context import Tilia.Render.Layout-import Tilia.Render.Literal (stringLiteral)+import Tilia.Render.Literal (hsLit, writtenString) import Tilia.Render.Name import Tilia.Render.Operator import Tilia.Render.Pattern@@ -74,11 +69,7 @@ HsOverLabel src _ -> txt "#" <> sourceText src HsIPVar _ (HsIPName n) -> txt "?" <> outputable n HsOverLit _ v -> outputable (ol_val v)- HsLit _ lit -> case lit of- HsString (SourceText s) _ -> stringLiteral s- HsStringPrim (SourceText s) _ -> stringLiteral s- HsMultilineString (SourceText s) _ -> stringLiteral s- other -> outputable other+ HsLit _ lit -> hsLit lit HsLam _ variant' mg -> lambda ctx site variant' (ExprBody ctx) mg HsApp _ f x -> application ctx site f x HsAppType at' e a ->@@ -169,12 +160,7 @@ <> joinedBy "::" <> indent (hsSigType ctx hswc_body) ArithSeq _ _ range -> arithSeq ctx here (closingFor site) range- HsTypedBracket (bracketAnn, _) e ->- txt opener- <> breakOrNothing- <> insideBrackets here (hsExpr ctx e)- <> breakOrNothing- <> txt "||]"+ HsTypedBracket (bracketAnn, _) e -> quoted here opener "||]" (hsExpr ctx e) where opener = case bracketAnn of BracketNoE{} -> "[||"@@ -191,7 +177,7 @@ HsPragE _ prag x -> case prag of HsPragSCC _ n -> txt "{-# SCC "- <> outputable n+ <> writtenString n <> txt " #-}" <> breakOrSpace <> nest (if siteInBlock site then 1 else 0) (hsExpr ctx x)@@ -507,17 +493,7 @@ <> breakOrSpace <> indent (hsExpr ctx trS_using) RecStmt{..} ->- txt "rec"- <> space- <> align- ( at ctx recS_stmts $ \xs ->- items (siteBracing site) $- keepBlanks- (separatedByBlank ctx)- [ (spanOf s, at_ ctx (stmtBody ctx site mkBody) s)- | s <- xs- ]- )+ txt "rec" <> space <> align (stmtBlock ctx site mkBody recS_stmts) -- | The statements of a block, with the break that introduces them. statements ::@@ -532,7 +508,21 @@ XRec GhcPs [LStmt GhcPs (XRec GhcPs body)] -> Doc statements ctx site mkBody es =- breakOrSpace <> underSite site (at ctx es block)+ breakOrSpace <> underSite site (stmtBlock ctx site mkBody es)++-- | The statements of a block, each laid out as an item of it.+stmtBlock ::+ ( Body b,+ Anno [LStmt GhcPs (XRec GhcPs body)] ~ SrcSpanAnnLW,+ Anno (Stmt GhcPs (XRec GhcPs body)) ~ SrcSpanAnnA,+ Anno body ~ SrcSpanAnnA+ ) =>+ Ctx ->+ Site ->+ BodyOf body b ->+ XRec GhcPs [LStmt GhcPs (XRec GhcPs body)] ->+ Doc+stmtBlock ctx site mkBody es = at ctx es block where block xs = items (siteBracing site) $@@ -699,10 +689,7 @@ | (position, item) <- positioned sorted ] where- sorted =- sortBy- (leftmost_smallest `on` getLocA)- (fmap (fmap Left) binds <> fmap (fmap Right) sigs)+ sorted = inSourceOrder [fmap (fmap Left) binds, fmap (fmap Right) sigs] rendered position item = blockItem position NoBrace $ \b -> at_ ctx (either (valDecl ctx b) (knotSig (ctxKnot ctx) ctx)) item@@ -889,11 +876,7 @@ | isCaseStyle style, null (unLoc mg_alts) = txt "{}" | otherwise = items blockBracing (fmap rendered (positioned (unLoc mg_alts))) where- blockBracing = case style of- CaseStyle -> ifEmpty- LambdaCaseStyle -> ifEmpty- _ -> NoBrace- ifEmpty = if null (unLoc mg_alts) then MayBrace else bracing+ blockBracing = if isCaseStyle style then bracing else NoBrace rendered (position, m) = blockItem position bracing (\b -> at_ ctx (renderMatch b) m) renderMatch b m@Match{..} =@@ -1008,13 +991,10 @@ _ -> True separator- | length grhssGRHSs > 1 = mempty+ | hasGuards = mempty | otherwise = case style of- FunctionStyle _ | hasGuards -> mempty FunctionStyle _ -> space <> indent (txt "=")- PatternBindStyle | hasGuards -> mempty PatternBindStyle -> space <> indent (txt "=")- s | isCaseStyle s && hasGuards -> mempty _ -> space <> txt "->" body = sepBy breakOrSpace (fmap alternative (NE.toList grhssGRHSs))@@ -1196,24 +1176,19 @@ -- | A Template Haskell quotation. quotation :: Ctx -> Maybe Span -> HsQuote GhcPs -> Doc quotation ctx here = \case- ExpBr (bracketAnn, _) e -> quoted (flavour bracketAnn) (hsExpr ctx e)+ ExpBr (bracketAnn, _) e -> quotedAs (flavour bracketAnn) (hsExpr ctx e) where flavour = \case BracketNoE{} -> "" BracketHasE{} -> "e"- PatBr _ p -> quoted "p" (hsPat ctx p)+ PatBr _ p -> quotedAs "p" (hsPat ctx p) DecBrL _ decls ->- quoted "d" (starGuard decls (knotDecls (ctxKnot ctx) ctx Free decls))+ quotedAs "d" (starGuard decls (knotDecls (ctxKnot ctx) ctx decls)) DecBrG _ _ -> error "Tilia: DecBrG is produced by the renamer"- TypBr _ ty -> quoted "t" (starGuard ty (hsType ctx ty))+ TypBr _ ty -> quotedAs "t" (starGuard ty (hsType ctx ty)) VarBr _ isSingle n -> txt (if isSingle then "'" else "''") <> name ctx n where- quoted flavour body =- txt "["- <> txt flavour- <> txt "|"- <> breakOrNothing- <> indent (insideBrackets here body <> breakOrNothing <> txt "|]")+ quotedAs flavour = quoted here ("[" <> flavour <> "|") "|]" starGuard x body | risky = space <> body <> space | otherwise = body@@ -1228,6 +1203,22 @@ ClosedTypeFamily Nothing -> True _ -> False +-- | The body of a quotation between its brackets, indented where it breaks.+quoted ::+ -- | Where the quotation is written.+ Maybe Span ->+ -- | The opening bracket.+ Text ->+ -- | The closing bracket.+ Text ->+ -- | The body.+ Doc ->+ Doc+quoted here opener closer body =+ txt opener+ <> breakOrNothing+ <> indent (insideBrackets here body <> breakOrNothing <> txt closer)+ -- | An item of a block: the last one keeps the block's bracing, and any -- other may brace itself when the block is flat. blockItem :: Position -> Bracing -> (Bracing -> Doc) -> Doc@@ -1306,6 +1297,8 @@ HsCmdLam{} -> Hanging _ -> Normal +-- | Does this command at the top of an arrow form absorb the line break+-- that introduces it? cmdTopHangs :: HsCmdTop GhcPs -> Placement cmdTopHangs (HsCmdTop _ c) = cmdHangs (unLoc c)
src/Tilia/Render/Haddock.hs view
@@ -18,14 +18,13 @@ import Data.Data (Data) import Data.Generics.Schemes (listify) import Data.List (dropWhileEnd)-import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty qualified as NE import Data.Maybe (fromMaybe, mapMaybe) import Data.Text (Text) import Data.Text qualified as T import GHC.Hs import GHC.Types.SrcLoc (GenLocated (..), getLoc, unLoc)-import Tilia.Comments (afterOpener, triggerOn)+import Tilia.Comments (Comment (..), afterOpener, bracketed, singleLine, triggerOn) import Tilia.Doc.Combinators import Tilia.Gathered (Gathered (..)) import Tilia.Render.Context@@ -75,13 +74,11 @@ docBody :: Ctx -> DocStyle -> LHsDoc GhcPs -> (Doc, Bool) docBody ctx style doc@(L l str) = case reusableText ctx style doc of- Just written ->+ Just c -> ( maybe id located (spanOfSrcSpan l) $ align- $ sepBy- (verbatimBreak AtIndent TrimWhitespace)- (fmap txt (NE.toList written)),- selfClosing written+ $ verbatimLines AtIndent TrimWhitespace (NE.toList (commentBody c)),+ selfClosing c ) Nothing | null written' -> (writtenAt emptyBlock, True)@@ -95,7 +92,7 @@ <> mconcat (replicate trailingBlanks (hardBreak <> txt "--")) trailingBlanks = case writtenHaddock ctx (spanOfSrcSpan l) of Nothing -> 0- Just ls -> length (takeWhile isBlankLine (reverse (NE.toList ls)))+ Just c -> length (takeWhile isBlankLine (reverse (NE.toList (commentBody c)))) isBlankLine t = T.null (T.strip (fromMaybe t (T.stripPrefix "--" (T.strip t)))) line' isFirst t = (if isFirst then txt (opener style) else txt "--")@@ -105,7 +102,7 @@ align $ txt (blockOpener style) <> space- <> sepBy (verbatimBreak AtIndent TrimWhitespace) (fmap txt written')+ <> verbatimLines AtIndent TrimWhitespace written' <> space <> txt "-}" asBlock = writtenAsBlock ctx doc@@ -131,7 +128,7 @@ -- | Did the author write this Haddock as a block comment? writtenAsBlock :: Ctx -> LHsDoc GhcPs -> Bool writtenAsBlock ctx doc =- maybe False isBlockForm (writtenHaddock ctx (spanOfSrcSpan (getLoc doc)))+ maybe False bracketed (writtenHaddock ctx (spanOfSrcSpan (getLoc doc))) -- | The anchor of a named documentation chunk. docSectionName :: String -> Text@@ -143,11 +140,11 @@ -- the one it was written in, since the text carries the style in its first -- characters, nor when it holds nothing but the spaces the end of a line -- loses.-reusableText :: Ctx -> DocStyle -> LHsDoc GhcPs -> Maybe (NonEmpty Text)+reusableText :: Ctx -> DocStyle -> LHsDoc GhcPs -> Maybe Comment reusableText ctx style doc = do- written <- writtenHaddock ctx (spanOfSrcSpan (getLoc doc))- if openedInStyle style (NE.head written) && not onlySpaces- then Just written+ c <- writtenHaddock ctx (spanOfSrcSpan (getLoc doc))+ if openedInStyle style (NE.head (commentBody c)) && not onlySpaces+ then Just c else Nothing where onlySpaces = not (null text) && all isSpace text && '\n' `notElem` text@@ -169,13 +166,9 @@ Section n -> T.replicate n "*" Chunk n -> "$" <> T.pack n --- | Was the reused text a block comment?-isBlockForm :: NonEmpty Text -> Bool-isBlockForm written = "{-" `T.isPrefixOf` T.stripStart (NE.head written)---- | May code follow the reused text on the line it ends?-selfClosing :: NonEmpty Text -> Bool-selfClosing written = isBlockForm written && null (NE.tail written)+-- | May code follow a Haddock on the line it ends?+selfClosing :: Comment -> Bool+selfClosing c = bracketed c && singleLine c -- | Lay the document out on several lines if printing this fragment will -- emit a Haddock that takes whole lines.@@ -194,7 +187,7 @@ docs -> any takesWholeLines docs where takesWholeLines doc = case reusableText ctx Pipe doc of- Just written -> not (selfClosing written)+ Just c -> not (selfClosing c) Nothing -> not (null (docLines (writtenAsBlock ctx doc) (unLoc doc))) -- | The spans of every Haddock in a module.
src/Tilia/Render/Header.hs view
@@ -14,8 +14,9 @@ import Control.Applicative ((<|>)) import Data.Function (on) import Data.List (sortOn)+import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty qualified as NE-import Data.Maybe (isJust)+import Data.Maybe (isJust, isNothing) import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as T@@ -25,7 +26,8 @@ import GHC.Types.SrcLoc (GenLocated (..), unLoc) import Tilia.Comments (Comment (..), Pragma (..), commentPragma) import Tilia.Doc.Combinators-import Tilia.Pragma (languageNames, lookupEdition)+import Tilia.Parser (writtenInHeader)+import Tilia.Pragma (languageNames, lookupEdition, sameInForce) import Tilia.Render.Context import Tilia.Render.Declaration (decls) import Tilia.Render.Haddock@@ -42,42 +44,29 @@ hpSpan :: Span, -- | How many preprocessor directives the header has above it. hpRun :: Int,- -- | @LANGUAGE@, @OPTIONS_GHC@ or @OPTIONS_HADDOCK@.+ -- | @LANGUAGE@, @OPTIONS_GHC@, @OPTIONS@ or @OPTIONS_HADDOCK@. hpName :: Text, -- | One extension, or the whole of an options string.- hpBody :: Text+ hpBody :: Text,+ -- | The lines it was written on, where it is a language pragma holding a+ -- comment, which it keeps as written.+ hpWritten :: Maybe (NonEmpty Text) } deriving (Eq, Show) -- | Where a pragma sorts among the others. ----- The derived ordering is the whole of the policy: language pragmas first,--- then @OPTIONS_GHC@, then @OPTIONS_HADDOCK@; and within the language--- pragmas, by the class of extension.+-- The derived ordering is the policy wherever it leaves the same extensions+-- in force: the editions first, in the order they are written, then the+-- other language pragmas, by name, then @OPTIONS_GHC@ and @OPTIONS@, then+-- @OPTIONS_HADDOCK@. data PragmaOrder- = LanguageOrder ExtensionClass+ = EditionOrder+ | LanguageOrder | OptionsGhcOrder | OptionsHaddockOrder deriving (Eq, Ord, Show) --- | Which group an extension sorts into.------ Sorting the extensions alphabetically outright would change what a module--- means, because an extension can turn others on and a later one can turn--- them off again. Sorting only within these groups keeps the relationships--- that matter: a pack before what it enables, an enabling before a--- disabling, and the stragglers that have to come last at the end.-data ExtensionClass- = -- | @GHC2021@, @Haskell2010@ and the like.- Pack- | -- | Anything else.- Enabling- | -- | An extension written with a @No@ prefix.- Disabling- | -- | Extensions that only work when nothing follows them.- Last'- deriving (Eq, Ord, Show)- -- | Pick the header pragmas out of a comment stream. takeHeaderPragmas :: -- | The module as written.@@ -108,28 +97,27 @@ { hpSpan = commentSpan c, hpRun = directivesAbove (spanStartLine (commentSpan c)), hpName = pragmaName p,- hpBody = pragmaBody p+ hpBody = pragmaBody p,+ hpWritten =+ if pragmaCommented p then Just (commentBody c) else Nothing } headerPragma c = do p <- commentPragma c _ <- pragmaOrder (pragmaName p) (pragmaBody p)- if inHeader headerEnd (commentSpan c) then Just p else Nothing+ if writtenInHeader headerEnd (commentSpan c) then Just p else Nothing -- | Where a pragma sorts, given its name and its body, or 'Nothing' for one -- the header does not hoist. pragmaOrder :: Text -> Text -> Maybe PragmaOrder pragmaOrder named body = case named of- "LANGUAGE" -> Just (LanguageOrder (classifyExtension body))+ "LANGUAGE"+ | namesAnEdition body -> Just EditionOrder+ | otherwise -> Just LanguageOrder "OPTIONS_GHC" -> Just OptionsGhcOrder+ "OPTIONS" -> Just OptionsGhcOrder "OPTIONS_HADDOCK" -> Just OptionsHaddockOrder _ -> Nothing --- | Was this written above everything the compiler reads as code?-inHeader :: Maybe Span -> Span -> Bool-inHeader headerEnd s = case headerEnd of- Nothing -> True- Just end -> spanStartLine s < spanStartLine end- -- | Take the Stack script header off the front of a comment stream. takeStackHeader :: -- | Where the header ends.@@ -142,45 +130,51 @@ cs -> (mempty, cs) where isStackHeader c =- inHeader headerEnd (commentSpan c)+ writtenInHeader headerEnd (commentSpan c) && T.isPrefixOf "stack" (T.stripStart (T.drop 2 (NE.head (commentBody c)))) reproduce c =- sepBy (verbatimBreak AtMargin TrimWhitespace) (fmap txt (NE.toList (commentBody c)))+ verbatimLines AtMargin TrimWhitespace (NE.toList (commentBody c)) --- | The pragmas of a header, one per line, sorted, with the options in the--- order they were written, which is the order GHC reads their flags in.+-- | The pragmas of a header, one per line, with the options in the order+-- they were written, which is the order GHC reads their flags in, and every+-- one of them kept, since a flag such as @-fplugin-opt@ counts each time it+-- is given.+--+-- The pragmas between two directives are sorted unless that would leave other+-- extensions in force, in which case they keep the order they were written in. pragmaBlock :: [HeaderPragma] -> Doc-pragmaBlock = foldMap render . dedupe . sortOn order . concatMap split+pragmaBlock =+ foldMap render+ . dedupe+ . concatMap (sorted . NE.toList)+ . NE.groupBy ((==) `on` hpRun)+ . concatMap split where- order p = (hpRun p, orderOf p, extension p)- extension p = if hpName p == "LANGUAGE" then hpBody p else ""+ sorted run =+ let candidate = sortOn order run+ in if sameInForce (named candidate) (named run) then candidate else run+ named = fmap (\p -> (hpName p, hpBody p))+ order p = (orderOf p, extension p)+ extension p+ | orderOf p == Just LanguageOrder = hpBody p+ | otherwise = "" key p = (hpRun p, orderOf p, hpBody p) orderOf p = pragmaOrder (hpName p) (hpBody p)- dedupe = fmap NE.head . NE.groupBy ((==) `on` key)+ dedupe = fmap NE.head . NE.groupBy repeated+ repeated a b = hpName a == "LANGUAGE" && key a == key b split p- | hpName p == "LANGUAGE" =+ | hpName p == "LANGUAGE",+ isNothing (hpWritten p) = [ p{hpBody = body} | body <- languageNames (hpBody p) ] | otherwise = [p] - render p =- located- (hpSpan p)- (txt "{-# " <> txt (hpName p) <> space <> txt (hpBody p) <> txt " #-}")- <> hardBreak---- | Which group an extension belongs to.-classifyExtension :: Text -> ExtensionClass-classifyExtension t- | namesAnEdition t = Pack- -- @ImplicitPrelude@ and @CUSKs@ are turned off by other extensions, so- -- asking for either of them only takes effect at the end.- | t == "ImplicitPrelude" = Last'- | t == "CUSKs" = Last'- | otherwise = case T.uncons (T.drop 2 t) of- Just (c, _) | "No" `T.isPrefixOf` t, c `elem` ['A' .. 'Z'] -> Disabling- _ -> Enabling+ render p = located (hpSpan p) (written p) <> hardBreak+ written p = case hpWritten p of+ Nothing ->+ txt "{-# " <> txt (hpName p) <> space <> txt (hpBody p) <> txt " #-}"+ Just ls -> verbatim (T.intercalate "\n" (NE.toList ls)) -- | Does this name a whole edition of the language rather than one -- extension of it?@@ -210,7 +204,7 @@ (null hsmodDecls) ( hardBreak <> declarationsStart- <> declarationLayout (decls ctx Free hsmodDecls)+ <> declarationLayout (decls ctx hsmodDecls) ) where declarationLayout = case (hsmodDecls, spansOf hsmodDecls) of@@ -330,7 +324,7 @@ IEGroup NoExtField n str -> haddock ctx (Section n) Open str IEDoc NoExtField str -> haddock ctx Pipe Open str IEDocNamed NoExtField n -> case writtenHaddock ctx here of- Just written -> sepBy (verbatimBreak AtIndent TrimWhitespace) (fmap txt (NE.toList written))+ Just c -> verbatimLines AtIndent TrimWhitespace (NE.toList (commentBody c)) Nothing -> txt (docSectionName n) where plain warning n doc =
src/Tilia/Render/Literal.hs view
@@ -5,7 +5,9 @@ -- | String literals. module Tilia.Render.Literal- ( stringLiteral,+ ( hsLit,+ writtenString,+ stringLiteral, ) where @@ -16,10 +18,28 @@ import Data.Text (Text) import Data.Text qualified as T import GHC.Data.FastString (FastString, unpackFS)+import GHC.Hs (GhcPs, HsLit (..)) import GHC.Parser.CharClass (is_space)+import GHC.Types.SourceText (SourceText (..), StringLiteral (..)) import Tilia.Doc.Combinators import Tilia.Render.Layout (Position (..), positioned)+import Tilia.Render.Name (outputable) +-- | A literal, a string one as its author wrote it.+hsLit :: HsLit GhcPs -> Doc+hsLit = \case+ HsString (SourceText s) _ -> stringLiteral s+ HsStringPrim (SourceText s) _ -> stringLiteral s+ HsMultilineString (SourceText s) _ -> stringLiteral s+ other -> outputable other++-- | A string literal as its author wrote it, or the bare text it holds where+-- none was written, as for an unquoted @SCC@ label.+writtenString :: StringLiteral -> Doc+writtenString l = case sl_st l of+ SourceText s -> stringLiteral s+ NoSourceText -> txt (T.pack (unpackFS (sl_fs l)))+ -- | A string literal, from the text the author wrote. stringLiteral :: FastString -> Doc stringLiteral src = case takeApart (T.pack (unpackFS src)) of@@ -33,10 +53,7 @@ where body = case litKind literal of Regular -> variant onOneLine acrossLines- Multiline ->- sepBy- (verbatimBreak AtIndent KeepWhitespace)- (fmap txt (litParts literal))+ Multiline -> verbatimLines AtIndent KeepWhitespace (litParts literal) onOneLine = txt (joinParts (litParts literal)) acrossLines = sepBy breakOrSpace (fmap continued (positioned (litParts literal)))
src/Tilia/Render/Pattern.hs view
@@ -22,10 +22,10 @@ import GHC.Hs import GHC.Types.Basic (Arity, Boxity (..), ConTag) import GHC.Types.Name.Reader (RdrName)-import GHC.Types.SrcLoc (GenLocated (..)) import Tilia.Doc.Combinators import Tilia.Render.Context import Tilia.Render.Layout+import Tilia.Render.Literal (hsLit) import Tilia.Render.Name import Tilia.Render.Type import Tilia.Span@@ -37,14 +37,12 @@ -- | A pattern whose outermost bracket closes where it is told to. hsPatClosing :: Ctx -> ClosingIndent -> LPat GhcPs -> Doc-hsPatClosing ctx = hsPatIn ctx NoBrace (Isn't #inAsPat)+hsPatClosing ctx = hsPatIn ctx (Isn't #inAsPat) -- | A pattern that knows where it stands. hsPatIn :: -- | The context. Ctx ->- -- | Bracing an or-pattern inherits.- Bracing -> -- | Is this inside an as-pattern? Choice "inAsPat" -> -- | Where its outermost bracket closes.@@ -52,15 +50,13 @@ -- | The pattern. LPat GhcPs -> Doc-hsPatIn ctx bracing inAsPat closing l =- at ctx l (patBody ctx bracing inAsPat closing (spanOf l))+hsPatIn ctx inAsPat closing l =+ at ctx l (patBody ctx inAsPat closing (spanOf l)) -- | Every shape a pattern can take. patBody :: -- | The context. Ctx ->- -- | Bracing an or-pattern inherits.- Bracing -> -- | Is this inside an as-pattern? Choice "inAsPat" -> -- | Where its outermost bracket closes.@@ -70,12 +66,12 @@ -- | The pattern. Pat GhcPs -> Doc-patBody ctx bracing inAsPat closing here = \case+patBody ctx inAsPat closing here = \case WildPat _ -> txt "_" VarPat _ n -> name ctx n LazyPat _ p -> txt "~" <> recur p AsPat _ n p ->- name ctx n <> txt "@" <> hsPatIn ctx bracing (Is #inAsPat) Indented p+ name ctx n <> txt "@" <> hsPatIn ctx (Is #inAsPat) Indented p ParPat _ p -> parensWith closing (insideBrackets here (recur p)) BangPat _ p -> txt "!" <> recur p ListPat _ ps ->@@ -86,16 +82,16 @@ closing (insideBrackets here (commaSep (fmap (align . recur) ps))) OrPat _ ps ->- itemsSepBy (fromBool (isTrue inAsPat)) bracing (fmap recur (NE.toList ps))+ itemsSepBy (fromBool (isTrue inAsPat)) NoBrace (fmap recur (NE.toList ps)) SumPat _ p tag arity -> unboxedSum closing tag arity (recur p)- ConPat _ con details -> conPattern ctx bracing inAsPat here con details+ ConPat _ con details -> conPattern ctx inAsPat here con details ViewPat _ e p -> align $ knotExpr (ctxKnot ctx) ctx plainSite e <> joinedBy "->" <> indent (recur p) SplicePat _ splice -> knotSplice (ctxKnot ctx) ctx DollarSplice splice- LitPat _ lit -> outputable lit+ LitPat _ lit -> hsLit lit NPat _ v (isJust -> negated) _ -> includeWhen negated (txt "-" <> negativeLiteralGap ctx) <> at ctx v (outputable . ol_val)@@ -109,14 +105,12 @@ EmbTyPat _ (HsTP _ ty) -> txt "type" <> space <> hsType ctx ty InvisPat _ (HsTP _ ty) -> txt "@" <> hsType ctx ty where- recur = hsPatIn ctx bracing inAsPat Indented+ recur = hsPatIn ctx inAsPat Indented -- | A constructor pattern, in whichever of its three forms. conPattern :: -- | The context. Ctx ->- -- | Bracing an or-pattern inherits.- Bracing -> -- | Is this inside an as-pattern? Choice "inAsPat" -> -- | Where the pattern was written.@@ -126,7 +120,7 @@ -- | What it was applied to. HsConPatDetails GhcPs -> Doc-conPattern ctx bracing inAsPat here con = \case+conPattern ctx inAsPat here con = \case PrefixCon args -> align $ name ctx con@@ -141,13 +135,12 @@ recur l <> attachOperator Normal (spanOf l) (spanOf con) (spanOf r) (name ctx con) (recur r) where- recur = hsPatIn ctx bracing inAsPat Indented+ recur = hsPatIn ctx inAsPat Indented field = either wildcard (at_ ctx (patFieldBind ctx)) wildcard l = at ctx l (const (txt "..")) visibleFields dotdot fields = case dotdot of Nothing -> Right <$> fields- Just l@(L _ (RecFieldsDotDot n)) ->- (Right <$> take n fields) <> [Left l]+ Just l -> (Right <$> fields) <> [Left l] -- | One field of a record pattern. patFieldBind :: Ctx -> HsRecField GhcPs (LPat GhcPs) -> Doc
src/Tilia/Render/Pragma.hs view
@@ -26,6 +26,7 @@ import GHC.Unit.Module.Warnings import Tilia.Doc.Combinators import Tilia.Render.Context+import Tilia.Render.Literal (writtenString) import Tilia.Render.Name -- | Wrap a body in pragma braces.@@ -121,5 +122,5 @@ -- | One message is written bare; several go in a list. literalList :: [LocatedE StringLiteral] -> Doc literalList = \case- [l] -> outputable l- ls -> brackets (commaSep (fmap outputable ls))+ [l] -> writtenString (unLoc l)+ ls -> brackets (commaSep (fmap (writtenString . unLoc) ls))
src/Tilia/Render/Signature.hs view
@@ -23,7 +23,6 @@ InlinePragma (..), InlineSpec (..), RuleMatchInfo (..),- RuleName, ) import GHC.Types.Fixity (Fixity (..), FixityDirection (..)) import GHC.Types.Name.Reader (RdrName)@@ -32,6 +31,7 @@ import Tilia.Doc.Combinators import Tilia.Render.Context import Tilia.Render.Expression (hsExpr)+import Tilia.Render.Literal (writtenString) import Tilia.Render.Name import Tilia.Render.Pragma import Tilia.Render.Type@@ -68,7 +68,7 @@ -- | The type they are given. LHsSigType GhcPs -> Doc-typeSig _ _ [] _ = mempty+typeSig _ _ [] _ = error "Tilia: a signature names something" typeSig ctx indentTail (n : ns) sigType | null ns = name ctx n <> typeAscription ctx sigType | otherwise =@@ -158,8 +158,8 @@ (joinedBy "::" <> commaSep (fmap (hsSigType ctx) types)) ) where- -- A pragma that says neither when to inline nor whether to is saying- -- nothing, so the phase is left off rather than printed as @[~]@.+ -- A @NOINLINE@ written without a phase reads as never active, so that+ -- phase is left off rather than printed as @[~]@. phase = case (inl_inline, inl_act) of (NoInline _, NeverActive) -> mempty _ -> activation inl_act@@ -223,7 +223,7 @@ sccSig :: Ctx -> LocatedN RdrName -> Maybe (XRec GhcPs StringLiteral) -> Doc sccSig ctx n literal = pragma "SCC" . indent $- name ctx n <> foldMap (\l -> breakOrSpace <> outputable l) literal+ name ctx n <> foldMap (\l -> breakOrSpace <> writtenString (unLoc l)) literal -- | @type T :: k@. standaloneKindSig :: Ctx -> StandaloneKindSig GhcPs -> Doc@@ -250,8 +250,8 @@ -- | One rule of a @RULES@ block. ruleDecl :: Ctx -> RuleDecl GhcPs -> Doc-ruleDecl ctx (HsRule _ ruleName phase binders lhs rhs) =- at ctx ruleName ruleNameLiteral+ruleDecl ctx (HsRule (_, written) ruleName phase binders lhs rhs) =+ at ctx ruleName (\n -> writtenString (StringLiteral written n Nothing)) <> space <> activation phase <> space@@ -263,11 +263,6 @@ <> txt "=" <> indent (breakOrSpace <> hsExpr ctx rhs) )---- | A rule's name is a string literal, and printing it as one is what puts--- the quotes back.-ruleNameLiteral :: RuleName -> Doc-ruleNameLiteral n = outputable (HsString NoSourceText n :: HsLit GhcPs) -- | The @forall@s a rule or a @SPECIALIZE@ pragma binds. ruleBinders :: Ctx -> RuleBndrs GhcPs -> Doc
src/Tilia/Render/Type.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}@@ -27,6 +26,7 @@ typeArgument, typeArgSpan, typeIsDocumented,+ typeBreak, asSigType, ) where@@ -143,7 +143,7 @@ HsRecTy _ fields -> recordFields ctx Nothing fields where recur = typeBody ctx documented Nothing- betweenArgs = if isTrue documented then hardBreak else breakOrSpace+ betweenArgs = typeBreak (isTrue documented) -- | A chain of type operators, regrouped by precedence. typeChain :: Ctx -> LHsType GhcPs -> LocatedN RdrName -> LHsType GhcPs -> Doc@@ -193,8 +193,8 @@ IsPromoted -> txt "'" NotPromoted -> mempty --- | The tick on a promoted name, held off it when the name itself begins--- with one, since @''@ is the spelling of a type-level quote.+-- | The tick on a promoted name, held off it where the name's second+-- character is a tick too, as @'B'@ would read as a character literal. promotion :: PromotionFlag -> LocatedN RdrName -> Doc promotion NotPromoted _ = mempty promotion IsPromoted n =@@ -234,6 +234,11 @@ SrcStrict -> txt "!" NoSrcStrict -> mempty +-- | The break between the parts of a type, a hard one where the type+-- carries documentation, which ends its line.+typeBreak :: Bool -> Doc+typeBreak documented = if documented then hardBreak else breakOrSpace+ -- | Does any argument of this type carry documentation? typeIsDocumented :: HsType GhcPs -> Bool typeIsDocumented = any documented . spine@@ -269,11 +274,9 @@ _ -> t -- | A context standing to the left of a @=>@, with the arrow and the break--- after it, or nothing where the context is empty.+-- after it. leftContext :: Ctx -> LHsContext GhcPs -> Doc-leftContext ctx ctxt- | null (unLoc ctxt) = mempty- | otherwise = context ctx ctxt <> joinedBy "=>"+leftContext ctx ctxt = context ctx ctxt <> joinedBy "=>" -- | A context over anything that can stand as a constraint. contextOf ::@@ -390,18 +393,14 @@ hsSigTypeBody ctx HsSig{..} = outerBndrs ctx afterBinders sig_bndrs <> hsType ctx sig_body where- afterBinders- | typeIsDocumented (unLoc sig_body) = hardBreak- | otherwise = breakOrSpace+ afterBinders = typeBreak (typeIsDocumented (unLoc sig_body)) -- | The @:: t@ that follows a name. typeAscription :: Ctx -> LHsSigType GhcPs -> Doc typeAscription ctx sigType = indent (space <> txt "::" <> separator <> hsSigType ctx sigType) where- separator- | typeIsDocumented (unLoc (sig_body (unLoc sigType))) = hardBreak- | otherwise = breakOrSpace+ separator = typeBreak (typeIsDocumented (unLoc (sig_body (unLoc sigType)))) -- | Give a plain type the shape of a signature type. asSigType :: LHsType GhcPs -> LHsSigType GhcPs
src/Tilia/Run.hs view
@@ -25,16 +25,16 @@ ) where +import Control.Exception (evaluate) import Data.ByteString qualified as BS import Data.List (sortOn) import Data.Map.Strict qualified as Map import Data.Text (Text) import Data.Text qualified as T-import Data.Text.Encoding qualified as T import System.FilePath (takeExtension) import Tilia.Diff (diffInFull) import Tilia.Format- ( FormatError (Unreadable),+ ( FormatError (Threw, Unreadable), Session, describeFormatError, formatErrorExitCode,@@ -49,8 +49,9 @@ byteOrderMark, inParallel, indent,- lineWidth,- wrapTo,+ tshow,+ wrappedUnder,+ writeUtf8File, ) ----------------------------------------------------------------------------@@ -107,7 +108,7 @@ runOver session = inParallel one where one path = do- !outcome <-+ outcome <- readAsUtf8 path >>= \case Left why -> pure (Failed (Unreadable path why)) Right before -> outcomeOf session path before@@ -123,10 +124,13 @@ Text -> IO Outcome outcomeOf session path before =- formatSource session path (setNewlineStyle Lf source) >>= \case- Left e -> pure (if refused e then Declined e else Failed e)- Right formatted -> pure (formattingOutcome before (mark <> formatted))+ either (Failed . Threw path) id+ <$> attempted (evaluate . outcome =<< formatted) where+ formatted = formatSource session path (setNewlineStyle Lf source)+ outcome = \case+ Left e -> if refused e then Declined e else Failed e+ Right it -> formattingOutcome before (mark <> it) (mark, source) = byteOrderMark before -- | Read a source file as UTF-8.@@ -152,7 +156,7 @@ -- | Put a formatted file back, and only if it changed. writeBack :: (FilePath, Outcome) -> IO () writeBack (path, outcome) = case outcome of- Changed _ after -> BS.writeFile path (T.encodeUtf8 after)+ Changed _ after -> writeUtf8File path after _ -> pure () ----------------------------------------------------------------------------@@ -274,17 +278,14 @@ -- | Render the number of files. count :: Palette -> Int -> Text -> Text count palette n extension =- T.pack (show n)+ tshow n <> " " <> paint palette Place extension <> (if n == 1 then " file" else " files") -- | One case among several, opened by a bullet and wrapped underneath it. bulleted :: Palette -> FormatError -> [Text]-bulleted palette e = case wrapTo (lineWidth - 6) (describeFormatError palette e) of- [] -> []- (opening : rest) ->- (indent 2 <> "· " <> opening) : fmap (indent 3 <>) rest+bulleted palette e = wrappedUnder 3 (indent 2 <> "· ") (describeFormatError palette e) -- | Something to say under a mark of its own, wrapped to fit beneath it. noted ::@@ -293,8 +294,5 @@ (Text, Color) -> Text -> [Text]-noted palette (mark, color) text = case wrapTo (lineWidth - 6) text of- [] -> []- (opening : rest) ->- (indent 1 <> marker palette color mark <> " " <> opening)- : fmap (indent 3 <>) rest+noted palette (mark, color) =+ wrappedUnder 3 (indent 1 <> marker palette color mark <> " ")
src/Tilia/Source.hs view
@@ -14,6 +14,8 @@ sourceLines, lineAt, blankAt,+ blankAcross,+ offsetOf, directivePresentOnLine, directiveOnLine,
src/Tilia/Source/Lines.hs view
@@ -11,6 +11,10 @@ lineTexts, lineAt, blankAt,+ blankAcross,+ offsetOf,+ columnOf,+ contentColumn, directivePresentOnLine, directiveOnLine, conditionalKeywords,@@ -77,6 +81,39 @@ -- | Was this line empty? blankAt :: Int -> Lines -> Bool blankAt n = maybe False (T.all isSpace) . lineAt n++-- | Were all the lines from one to another, both included, empty?+blankAcross :: (Int, Int) -> Lines -> Bool+blankAcross (from, to) ls = all (`blankAt` ls) [from .. to]++-- | How many characters of a line come before the compiler's column.+--+-- A column is not a character offset. The lexer counts a tab as advancing to+-- the next multiple of eight, so a line with a tab in it has more columns+-- than it has characters, and cutting the text at a column would cut in the+-- wrong place. In a file indented with tabs that is every line.+offsetOf :: Text -> Int -> Int+offsetOf line column = T.length (T.take (walk 0 1) line)+ where+ walk i c+ | c >= column = i+ | i >= T.length line = i + (column - c)+ | otherwise = walk (i + 1) (afterChar (T.index line i) c)++-- | The compiler's column for the character at this offset.+columnOf :: Text -> Int -> Int+columnOf line offset = T.foldl' (flip afterChar) 1 (T.take offset line)++-- | The compiler's column for the first character of a line that is not+-- white space.+contentColumn :: Text -> Int+contentColumn l = columnOf l (T.length (T.takeWhile isSpace l))++-- | Where the column moves to once this character has been read.+afterChar :: Char -> Int -> Int+afterChar ch c+ | ch == '\t' = ((c - 1) `div` 8 + 1) * 8 + 1+ | otherwise = c + 1 -- | Does this line hold a preprocessor directive? directivePresentOnLine :: Int -> Lines -> Bool
src/Tilia/Span.hs view
@@ -4,7 +4,6 @@ mkSpan, isSingleLine, sameLine,- blankBetween, meets, covers, zeroWidth,@@ -52,11 +51,6 @@ sameLine :: Maybe Span -> Maybe Span -> Bool sameLine (Just a) (Just b) = spanEndLine a == spanStartLine b sameLine _ _ = False---- | Was there an empty line between the two?-blankBetween :: Maybe Span -> Maybe Span -> Bool-blankBetween (Just a) (Just b) = spanStartLine b > spanEndLine a + 1-blankBetween _ _ = False -- | Do the two cover any of the same input? --
src/Tilia/Span/Ghc.hs view
@@ -7,12 +7,15 @@ spanOf, spansOf, bracketsSpan,+ inSourceOrder, ) where +import Data.Function (on)+import Data.List (sortBy) import Data.Maybe (mapMaybe) import GHC.Parser.Annotation (AnnListBrackets (..), HasLoc, getHasLoc)-import GHC.Types.SrcLoc (GenLocated)+import GHC.Types.SrcLoc (GenLocated, getLoc, leftmost_smallest) import GHC.Types.SrcLoc qualified as GHC import Tilia.Span (Span, mkSpan) @@ -40,6 +43,11 @@ ListBraces open close -> spanOf open <> spanOf close ListSquare open close -> spanOf open <> spanOf close _ -> Nothing++-- | Merge several lists of located things back into the order they were+-- written in.+inSourceOrder :: (HasLoc l) => [[GenLocated l a]] -> [GenLocated l a]+inSourceOrder = sortBy (leftmost_smallest `on` (getHasLoc . getLoc)) . concat -- | The span covering every located thing in the list. spansOf :: (HasLoc l) => [GenLocated l a] -> Maybe Span
src/Tilia/Utils.hs view
@@ -1,19 +1,25 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-} -- | Miscellaneous utilities. module Tilia.Utils ( quietly, attempted,- readFileText,+ readUtf8File,+ writeUtf8File, fieldWords,+ blocksOf,+ indented,+ looksLikeModule,+ pathUnder, asUtf8, byteOrderMark, sha256Of,+ sha256OfFile, lineWidth, indent, wrapTo,+ wrappedUnder, visibleLength, spellList, collected,@@ -23,17 +29,26 @@ where import Control.Concurrent- ( forkIO,+ ( forkFinally, getNumCapabilities, newEmptyMVar, putMVar, takeMVar, )-import Control.Exception (SomeException, displayException, try)-import Control.Monad (replicateM)+import Control.Exception+ ( SomeAsyncException,+ SomeException,+ catch,+ displayException,+ fromException,+ throwIO,+ )+import Control.Monad (replicateM, (>=>)) import Crypto.Hash.SHA256 qualified as SHA256 import Data.ByteString qualified as BS import Data.ByteString.Base16 qualified as B16+import Data.ByteString.Lazy qualified as BL+import Data.Char (isSpace) import Data.Foldable (for_, traverse_) import Data.IORef (atomicModifyIORef', newIORef, readIORef) import Data.Map.Strict qualified as Map@@ -41,38 +56,81 @@ import Data.Text qualified as T import Data.Text.Encoding qualified as T import System.Directory (doesFileExist)+import System.FilePath (equalFilePath, normalise, splitDirectories) -- | 'show' a value and take the result as 'Text'. tshow :: (Show a) => a -> Text tshow = T.pack . show --- | Run an action, falling back on the given value if it throws.+-- | Run an action, falling back on the given value if it throws, but for+-- an asynchronous exception, which asks it to stop. quietly :: a -> IO a -> IO a quietly fallback action =- try action >>= \case- Left (_ :: SomeException) -> pure fallback- Right a -> pure a+ action `catch` \e -> if asynchronous e then throwIO e else pure fallback --- | Read a file, if it is there and is text.-readFileText :: FilePath -> IO (Maybe Text)-readFileText path = quietly Nothing $ do+-- | Read a file as UTF-8, leniently, if it is there and can be read.+readUtf8File :: FilePath -> IO (Maybe Text)+readUtf8File path = quietly Nothing $ do there <- doesFileExist path if there then Just . T.decodeUtf8Lenient <$> BS.readFile path else pure Nothing +-- | Write a file as UTF-8.+writeUtf8File :: FilePath -> Text -> IO ()+writeUtf8File path = BS.writeFile path . T.encodeUtf8+ -- | The words of a field's value, which whitespace and commas separate. fieldWords :: Text -> [Text] fieldWords = filter (not . T.null) . concatMap (T.split (== ',')) . T.words +-- | Group lines into blocks, each a line at the margin with the indented+-- lines under it, dropping indented lines before the first.+blocksOf :: [Text] -> [(Text, [Text])]+blocksOf = \case+ [] -> []+ l : ls+ | indented l -> blocksOf ls+ | otherwise ->+ let (under, rest) = span indented ls+ in (l, under) : blocksOf rest++-- | Does a line begin with white space?+indented :: Text -> Bool+indented = maybe False (isSpace . fst) . T.uncons++-- | A path's segments under a directory, if it is at or under it.+pathUnder :: FilePath -> FilePath -> Maybe [FilePath]+pathUnder directory path =+ go (splitDirectories directory) (splitDirectories (normalise path))+ where+ go [] fs = Just fs+ go (d : ds) (f : fs) | equalFilePath d f = go ds fs+ go _ _ = Nothing++-- | Could a word name a module: does it begin with a capital letter and+-- hold no colon?+looksLikeModule :: Text -> Bool+looksLikeModule m = case T.uncons m of+ Just (c, _) -> c `elem` ['A' .. 'Z'] && not (T.any (== ':') m)+ Nothing -> False+ -- | Run an action and return its result or the exception it threw rendered--- as 'Text'.+-- as 'Text', but for an asynchronous exception, which asks it to stop. attempted :: IO a -> IO (Either Text a) attempted action =- try action >>= \case- Left (e :: SomeException) -> pure (Left (T.pack (displayException e)))- Right a -> pure (Right a)+ (Right <$> action) `catch` \e ->+ if asynchronous e+ then throwIO e+ else pure (Left (T.pack (displayException e))) +-- | Does an exception ask what it interrupts to stop, as an interrupt or a+-- cancellation does?+asynchronous :: SomeException -> Bool+asynchronous e = case fromException e of+ Just (_ :: SomeAsyncException) -> True+ Nothing -> False+ -- | Decode source as UTF-8. asUtf8 :: BS.ByteString -> Either Text Text asUtf8 bytes = case T.decodeUtf8' bytes of@@ -86,8 +144,16 @@ -- | The SHA-256 of a text's UTF-8 encoding, in hexadecimal. sha256Of :: Text -> Text-sha256Of = T.decodeUtf8Lenient . B16.encode . SHA256.hash . T.encodeUtf8+sha256Of = hex . SHA256.hash . T.encodeUtf8 +-- | The SHA-256 of a file, in hexadecimal.+sha256OfFile :: FilePath -> IO Text+sha256OfFile = fmap (hex . SHA256.hashlazy) . BL.readFile++-- | Bytes as lower-case hexadecimal.+hex :: BS.ByteString -> Text+hex = T.decodeUtf8Lenient . B16.encode+ -- | The line width for the terminal output of this program. lineWidth :: Int lineWidth = 76@@ -96,6 +162,14 @@ indent :: Int -> Text indent level = T.replicate (2 * level) " " +-- | Text wrapped under an opening as wide as the given level of indentation:+-- the opening begins the first line and the rest are indented to that+-- level.+wrappedUnder :: Int -> Text -> Text -> [Text]+wrappedUnder level opening text = case wrapTo (lineWidth - 2 * level) text of+ [] -> []+ first : rest -> (opening <> first) : fmap (indent level <>) rest+ -- | Break text into lines that fit the room given, at spaces. wrapTo :: Int -> Text -> [Text] wrapTo room = concatMap (go . T.words) . T.lines@@ -135,7 +209,8 @@ (before, (_, vs) : after) -> before <> [(k, vs <> [v])] <> after _ -> seen <> [(k, [v])] --- | Run an action over every element at once, as far as the machine allows.+-- | Run an action over every element at once, as far as the machine allows,+-- passing on an exception it throws. inParallel :: (a -> IO b) -> [a] -> IO [b] inParallel act xs = do capabilities <- getNumCapabilities@@ -152,6 +227,6 @@ atomicModifyIORef' answers (\m -> (Map.insert i y m, ())) worker done <- replicateM (max 1 (min capabilities (length xs))) newEmptyMVar- for_ done $ \signal -> forkIO (worker >> putMVar signal ())- traverse_ takeMVar done+ for_ done $ \signal -> forkFinally worker (putMVar signal)+ traverse_ (takeMVar >=> either throwIO pure) done Map.elems <$> readIORef answers
tests/Tilia/Cabal/PackageSpec.hs view
@@ -4,20 +4,34 @@ -- | Information obtained from @.cabal@ files. module Tilia.Cabal.PackageSpec (spec) where +import Data.Either (isRight)+import Data.Foldable (for_) import Data.List (isSuffixOf, sort) import Data.Text (Text) import Data.Text qualified as T-import Data.Text.IO qualified as T import GHC.LanguageExtensions.Type (Extension (..)) import System.Directory (createDirectoryIfMissing, removeFile) import System.FilePath ((</>)) import System.IO.Temp (withSystemTempDirectory) import Test.Hspec-import Tilia.Cabal.Package (PackageProblem (..), newPackageReader)+import Tilia.Cabal.Package+ ( PackageProblem (..),+ newPackageReader,+ parsePackageDescription,+ ) import Tilia.Cabal.Plan (BuildPlan (..), plannedCompiler)+import Tilia.Pragma (Defaults, effectiveExtensions)+import Tilia.TestUtils (withFiles)+import Tilia.Utils (writeUtf8File) spec :: Spec spec = do+ describe "a .cabal file" $+ it "parses whatever version of the format it declares, up to the newest" $+ for_ ["2.4", "3.0", "3.4", "3.8", "3.12", "3.14", "3.16", "3.18"] $ \v ->+ let held = "cabal-version: " <> v <> "\nname: t\nversion: 0\nlibrary\n"+ in parsePackageDescription "t.cabal" held `shouldSatisfy` isRight+ describe "the component a file belongs to" $ do it "is the one whose source directory holds it" $ inPackage twoComponents ["src", "test"] $ \root -> do@@ -83,7 +97,8 @@ it "are the language edition's" $ inPackage twoComponents ["src"] $ \root -> do library <- asked (root </> "src" </> "M.hs")- (length <$> library) `shouldSatisfy` either (const False) (> 40)+ (length . (`effectiveExtensions` "") <$> library)+ `shouldSatisfy` either (const False) (> 40) it "are Haskell98's where it names no edition, as cabal builds it" $ inPackage namesNoEdition ["src"] $ \root -> do@@ -115,7 +130,7 @@ it "take the compiler's version from the plan where it names one" $ inPackage conditionalExtensions ["src"] $ \root -> do- ask <- newPackageReader (plannedCompiler (BuildPlan "ghc-8.4.4" []))+ ask <- newPackageReader (plannedCompiler (BuildPlan "ghc-8.4.4" [])) [] library <- ask (root </> "src" </> "M.hs") (has StarIsType library, has BangPatterns library) `shouldBe` (True, True)@@ -139,24 +154,58 @@ complaints `shouldSatisfy` not . null other -> expectationFailure ("expected a parse failure, got " <> show other) + describe "a module no package above it claims" $ do+ it "is claimed by a package of the project that names it"+ $ withFiles+ [ ("one/one.cabal", reachingOut "../common"),+ ("common/Common.hs", "module Common where\n")+ ]+ $ \root -> do+ ask <- newPackageReader Nothing [root </> "one" </> "one.cabal"]+ (has LambdaCase <$> ask (root </> "common" </> "Common.hs"))+ `shouldReturn` True++ it "is claimed so where the package above it does not name it"+ $ withFiles+ [ ("one/one.cabal", reachingOut "../two/shared"),+ ("two/two.cabal", twoComponents),+ ("two/shared/Common.hs", "module Common where\n")+ ]+ $ \root -> do+ ask <-+ newPackageReader+ Nothing+ [root </> "one" </> "one.cabal", root </> "two" </> "two.cabal"]+ (has LambdaCase <$> ask (root </> "two" </> "shared" </> "Common.hs"))+ `shouldReturn` True++ it "is still unclaimed where no package of the project names it"+ $ withFiles+ [ ("one/one.cabal", reachingOut "../common"),+ ("other/Other.hs", "module Other where\n")+ ]+ $ \root -> do+ ask <- newPackageReader Nothing [root </> "one" </> "one.cabal"]+ ask (root </> "other" </> "Other.hs") `shouldReturn` Left NoPackageFile+ describe "a reader kept between files" $ do it "answers as a fresh one would" $ inPackage twoComponents ["src", "test"] $ \root -> do- ask <- newPackageReader Nothing+ ask <- newPackageReader Nothing [] kept <- traverse ask (modules root) fresh <- traverse asked (modules root) kept `shouldBe` fresh it "reads a package once, not once per file" $ inPackage twoComponents ["src"] $ \root -> do- ask <- newPackageReader Nothing+ ask <- newPackageReader Nothing [] first <- ask (root </> "src" </> "A.hs") removeFile (root </> "demo.cabal") ask (root </> "src" </> "B.hs") `shouldReturn` first it "remembers every directory the walk went through" $ inPackage twoComponents ["src" </> "deep"] $ \root -> do- ask <- newPackageReader Nothing+ ask <- newPackageReader Nothing [] deep <- ask (root </> "src" </> "deep" </> "A.hs") removeFile (root </> "demo.cabal") ask (root </> "src" </> "B.hs") `shouldReturn` deep@@ -343,6 +392,21 @@ " default-extensions: BangPatterns" ] +-- | A library with @LambdaCase@ on that declares @Common@, which is in the+-- source directory given, outside the package.+reachingOut :: Text -> Text+reachingOut shared =+ T.unlines+ [ "cabal-version: 2.4",+ "name: reaching",+ "version: 0",+ "library",+ " exposed-modules: Common",+ " hs-source-dirs: src " <> shared,+ " default-language: Haskell2010",+ " default-extensions: LambdaCase"+ ]+ conditionalLibrary :: Text conditionalLibrary = T.unlines@@ -362,28 +426,28 @@ inPackage :: Text -> [FilePath] -> (FilePath -> IO a) -> IO a inPackage contents dirs use = withSystemTempDirectory "tilia-package" $ \root -> do- T.writeFile (root </> "demo.cabal") contents+ writeUtf8File (root </> "demo.cabal") contents mapM_ (createDirectoryIfMissing True . (root </>)) (sort dirs) use root -- | Ask about one file with a reader of its own, which is what a test that -- is not about caching wants.-asked :: FilePath -> IO (Either PackageProblem [Extension])+asked :: FilePath -> IO (Either PackageProblem Defaults) asked path = do- ask <- newPackageReader Nothing+ ask <- newPackageReader Nothing [] ask path -- | One module in each of the two components. modules :: FilePath -> [FilePath] modules root = [root </> "src" </> "M.hs", root </> "test" </> "S.hs"] -unreadableOrMissing :: Either PackageProblem [Extension] -> Bool+unreadableOrMissing :: Either PackageProblem Defaults -> Bool unreadableOrMissing = either (const True) (const False) -has :: Extension -> Either PackageProblem [Extension] -> Bool-has e = either (const False) (e `elem`)+has :: Extension -> Either PackageProblem Defaults -> Bool+has e = either (const False) ((e `elem`) . (`effectiveExtensions` "")) -unclaimed :: Either PackageProblem [Extension] -> Bool+unclaimed :: Either PackageProblem Defaults -> Bool unclaimed = either isFileUnclaimed (const False) where isFileUnclaimed = \case
tests/Tilia/Cabal/ProjectSpec.hs view
@@ -3,11 +3,10 @@ -- | Finding the project a file belongs to. module Tilia.Cabal.ProjectSpec (spec) where -import System.Directory (createDirectoryIfMissing) import System.FilePath ((</>))-import System.IO.Temp (withSystemTempDirectory) import Test.Hspec import Tilia.Cabal.Project+import Tilia.TestUtils (withFiles) spec :: Spec spec = do@@ -33,28 +32,28 @@ describe "marker precedence" $ do it "prefers cabal.project to a bare .cabal file" $- withTree [("cabal.project", ""), ("thing.cabal", "")] $ \dir -> do+ withFiles [("cabal.project", ""), ("thing.cabal", "")] $ \dir -> do root <- findProjectRoot dir (prMarker <$> root) `shouldBe` Just ProjectFile it "accepts a bare .cabal file when there is no project" $- withTree [("thing.cabal", "")] $ \dir -> do+ withFiles [("thing.cabal", "")] $ \dir -> do root <- findProjectRoot dir (prMarker <$> root) `shouldBe` Just (PackageFile "thing.cabal") it "passes over a stack.yaml, which cabal does not read" $- withTree [("stack.yaml", ""), ("thing.cabal", "")] $ \dir -> do+ withFiles [("stack.yaml", ""), ("thing.cabal", "")] $ \dir -> do root <- findProjectRoot dir (prMarker <$> root) `shouldBe` Just (PackageFile "thing.cabal") it "climbs out of a stack project to the package it is asked about" $- withTree [("stack.yaml", ""), ("packages/inner/inner.cabal", "")] $+ withFiles [("stack.yaml", ""), ("packages/inner/inner.cabal", "")] $ \dir -> do root <- findProjectRoot (dir </> "packages" </> "inner") prPath <$> root `shouldBe` Just (dir </> "packages" </> "inner") it "climbs past a package to the project that contains it"- $ withTree+ $ withFiles [ ("cabal.project", ""), ("packages/inner/placeholder", "") ]@@ -63,7 +62,7 @@ (prMarker <$> root) `shouldBe` Just ProjectFile it "climbs past a package that has its own .cabal"- $ withTree+ $ withFiles [ ("cabal.project", ""), ("packages/inner/inner.cabal", "") ]@@ -73,7 +72,7 @@ (prMarker <$> root) `shouldBe` Just ProjectFile it "takes the nearest project of the ones above"- $ withTree+ $ withFiles [ ("cabal.project", ""), ("packages/inner/cabal.project", ""), ("packages/inner/inner.cabal", "")@@ -83,7 +82,7 @@ prPath <$> root `shouldBe` Just (dir </> "packages" </> "inner") it "takes the nearest package when no project is above either"- $ withTree+ $ withFiles [ ("outer.cabal", ""), ("packages/inner/inner.cabal", "") ]@@ -92,7 +91,7 @@ (prMarker <$> root) `shouldBe` Just (PackageFile "inner.cabal") it "climbs to a project past a package that is not the one asked about"- $ withTree+ $ withFiles [ ("cabal.project", ""), ("outer.cabal", ""), ("packages/inner/inner.cabal", "")@@ -103,22 +102,9 @@ describe "no project" $ it "gives up rather than guessing" $- withTree [("lonely/Thing.hs", "module Thing where")] $ \dir -> do+ withFiles [("lonely/Thing.hs", "module Thing where")] $ \dir -> do root <- findProjectRoot (dir </> "lonely") case root of Nothing -> pure () Just found -> prPath found `shouldNotBe` (dir </> "lonely")---- | Build a throwaway tree of files and run an action on its root.-withTree :: [(FilePath, String)] -> (FilePath -> IO a) -> IO a-withTree files action =- withSystemTempDirectory "tilia-project" $ \dir -> do- mapM_ (create dir) files- action dir- where- create dir (path, contents) = do- let full = dir </> path- createDirectoryIfMissing True (parentOf full)- writeFile full contents- parentOf = reverse . drop 1 . dropWhile (/= '/') . reverse
tests/Tilia/Cabal/TargetSpec.hs view
@@ -7,13 +7,13 @@ import Data.List (isSuffixOf, sort) import Data.Text (Text) import Data.Text qualified as T-import Data.Text.IO qualified as T-import System.Directory (createDirectoryIfMissing) import System.FilePath (takeFileName, (</>))-import System.IO.Temp (withSystemTempDirectory) import Test.Hspec+import Tilia.Cabal.Plan (PlanComponent (..)) import Tilia.Cabal.Project (Marker (..), ProjectRoot (..), findProjectRoot) import Tilia.Cabal.Target+import Tilia.TestUtils (withFiles)+import Tilia.Utils (writeUtf8File) spec :: Spec spec = do@@ -129,6 +129,44 @@ Left problem -> expectationFailure (T.unpack (describeTargetProblem problem)) Right cs -> sort (fmap componentPackage cs) `shouldBe` ["one", "two"] + it "reads a package an entry goes up to"+ $ withProject+ [ ("proj/cabal.project", "packages: ../lib\n"),+ ("lib/lib.cabal", packageWith "lib" ["src"] ["L"]),+ ("lib/src/L.hs", "module L where\n")+ ]+ $ \root -> do+ let project = ProjectRoot (prPath root </> "proj") ProjectFile+ componentsOfTarget project Everything >>= \case+ Left problem -> expectationFailure (T.unpack (describeTargetProblem problem))+ Right cs -> do+ files <- filesOfComponents project cs+ fmap takeFileName files `shouldBe` ["L.hs"]++ it "reads a package an absolute entry names"+ $ withProject+ [ ("proj/cabal.project", ""),+ ("lib/lib.cabal", package "lib" "src")+ ]+ $ \root -> do+ writeUtf8File+ (prPath root </> "proj" </> "cabal.project")+ ("packages: " <> T.pack (prPath root </> "lib") <> "\n")+ componentsOfTarget (ProjectRoot (prPath root </> "proj") ProjectFile) Everything+ >>= \case+ Left problem -> expectationFailure (T.unpack (describeTargetProblem problem))+ Right cs -> fmap componentPackage cs `shouldBe` ["lib"]++ it "reads a package in a directory whose name begins with a dot"+ $ withProject+ [ ("cabal.project", "packages: .hidden\n"),+ (".hidden/hidden.cabal", package "hidden" "src")+ ]+ $ \root ->+ componentsOfTarget root Everything >>= \case+ Left problem -> expectationFailure (T.unpack (describeTargetProblem problem))+ Right cs -> fmap componentPackage cs `shouldBe` ["hidden"]+ it "expands a glob in the packages field" $ withProject [ ("cabal.project", "packages: pkgs/*/*.cabal\n"),@@ -236,6 +274,25 @@ length cs `shouldBe` 2 fmap takeFileName files `shouldBe` ["One.hs", "Shared.hs", "Two.hs"] + it "names a file once when two packages reach its directory differently"+ $ withProject+ [ ("cabal.project", "packages: one two\n"),+ ("one/one.cabal", packageWith "one" ["src", "shared"] ["A", "Common"]),+ ("one/src/A.hs", "module A where\n"),+ ("one/shared/Common.hs", "module Common where\n"),+ ("two/two.cabal", packageWith "two" ["src", "../one/shared"] ["B", "Common"]),+ ("two/src/B.hs", "module B where\n")+ ]+ $ \root ->+ componentsOfTarget root Everything >>= \case+ Left problem -> expectationFailure (T.unpack (describeTargetProblem problem))+ Right cs -> do+ files <- filesOfComponents root cs+ files+ `shouldBe` fmap+ (prPath root </>)+ ["one/shared/Common.hs", "one/src/A.hs", "two/src/B.hs"]+ it "leaves out files no component declares" $ withProject [ ("only.cabal", packageWith "only" ["."] ["A"]),@@ -388,6 +445,14 @@ filesFor (Called "only") `shouldReturn` ["Setup.hs", "A.hs"] filesFor (Qualified Nothing Lib "only") `shouldReturn` ["A.hs"] + it "names each library the way a plan does" $+ withProject [("both.cabal", withSubLibrary)] $ \root ->+ componentsOfTarget root Everything >>= \case+ Left problem -> expectationFailure (T.unpack (describeTargetProblem problem))+ Right cs ->+ fmap (fmap pcName . componentInPlan) cs+ `shouldBe` [Just "lib", Just "lib:inner"]+ it "does not offer the setup script as a target" $ withProject [ ("only.cabal", packageWith "only" ["src"] ["A"]),@@ -465,14 +530,8 @@ -- | Lay out a project in a temporary directory and hand over its root. withProject :: [(FilePath, Text)] -> (ProjectRoot -> IO a) -> IO a withProject files act =- withSystemTempDirectory "tilia-target" $ \directory -> do- mapM_ (place directory) files- act (ProjectRoot directory (marker files))+ withFiles files $ \directory -> act (ProjectRoot directory (marker files)) where- place directory (path, contents) = do- createDirectoryIfMissing True (directory </> parent path)- T.writeFile (directory </> path) contents- parent = reverse . drop 1 . dropWhile (/= '/') . reverse marker fs | any ((== "cabal.project") . fst) fs = ProjectFile | (named : _) <- [p | (p, _) <- fs, ".cabal" `isSuffixOf` p] = PackageFile named@@ -496,6 +555,23 @@ " main-is: Two.hs", " hs-source-dirs: app", " other-modules: Shared",+ " default-language: Haskell2010"+ ]++-- | A package with a main library and a sub-library.+withSubLibrary :: Text+withSubLibrary =+ T.unlines+ [ "cabal-version: 3.0",+ "name: both",+ "version: 0.1.0.0",+ "",+ "library",+ " hs-source-dirs: src",+ " default-language: Haskell2010",+ "",+ "library inner",+ " hs-source-dirs: inner", " default-language: Haskell2010" ]
tests/Tilia/CommentsSpec.hs view
@@ -118,18 +118,26 @@ describe "pragmas" $ do it "recognises one" $ (commentPragma <$> commentsIn "{-# LANGUAGE CPP #-}\nmodule M where\nx = 1\n")- `shouldBe` [Just (Pragma "LANGUAGE" "CPP")]+ `shouldBe` [Just (Pragma "LANGUAGE" "CPP" False)]+ it "reads a language pragma past the comments in it, as GHC does" $+ (commentPragma <$> commentsIn "{-# LANGUAGE\n CPP -- for the version\n , {- c -} LambdaCase\n #-}\nmodule M where\n")+ `shouldBe` [Just (Pragma "LANGUAGE" "CPP , LambdaCase" True)]+ it "leaves a comment's dashes in an options pragma, which GHC reads" $+ (fmap pragmaBody . commentPragma <$> commentsIn "{-# OPTIONS_GHC -Wall -- no #-}\nmodule M where\n")+ `shouldBe` [Just "-Wall -- no"] it "upper-cases the name" $ (fmap pragmaName . commentPragma <$> commentsIn "{-# language CPP #-}\nmodule M where\nx = 1\n") `shouldBe` [Just "LANGUAGE"] it "is not fooled by an ordinary block comment" $ (commentPragma <$> commentsIn "module M where\n{- not a pragma -}\nx = 1\n") `shouldBe` [Nothing]- it "reports the header boundary" $+ it "reports the header boundary at the module line" $ headerLine "{-# LANGUAGE CPP #-}\nmodule M where\nimport Data.List\nx = 1\n"- `shouldBe` Just 3- it "reports no boundary for a module with only a header" $- headerLine "{-# LANGUAGE CPP #-}\nmodule M where\n" `shouldBe` Nothing+ `shouldBe` Just 2+ it "reports it where a module without a module line begins" $+ headerLine "{-# LANGUAGE CPP #-}\nimport Data.List\nx = 1\n" `shouldBe` Just 2+ it "reports no boundary for a module of pragmas alone" $+ headerLine "{-# LANGUAGE CPP #-}\n" `shouldBe` Nothing describe "what is deliberately not normalized" $ do it "keeps blank lines inside a comment" $
tests/Tilia/Corpus.hs view
@@ -1,7 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-} -- | Corpora of Haskell to run the formatter over. module Tilia.Corpus@@ -33,7 +32,6 @@ import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as T-import GHC.LanguageExtensions.Type (Extension) import Network.HTTP.Client qualified as HTTP import Network.HTTP.Req import System.Directory@@ -42,7 +40,6 @@ doesDirectoryExist, doesFileExist, getXdgDirectory,- listDirectory, removePathForcibly, renameDirectory, renameFile,@@ -50,6 +47,8 @@ import System.Environment (lookupEnv) import System.FilePath (splitDirectories, takeDirectory, (</>)) import Tilia.Cabal.Package (newPackageReader, sourceExtensions)+import Tilia.Pragma (Defaults)+import Tilia.TestUtils (haskellFilesIn) import Tilia.Utils (quietly) ----------------------------------------------------------------------------@@ -89,6 +88,9 @@ -- A name with no extension stands for a directory and takes everything -- under it. expectSkip :: [FilePath],+ -- | Examples GHC's own parser cannot read, run all the same to check+ -- that it still cannot.+ expectUnreadable :: [FilePath], -- | Examples the formatter is supposed to refuse. expectDeclined :: [FilePath] }@@ -121,6 +123,7 @@ Listed Lists { expectSkip = [],+ expectUnreadable = [], expectDeclined = [ "other" </> "position-pragmas.hs", "other" </> "cpp" </> "unbalanced.hs",@@ -148,7 +151,12 @@ ("data" </> "examples"), corpusReference = ReferenceMarked "-out", corpusExpectations =- Listed Lists{expectSkip = ormoluSkip, expectDeclined = []},+ Listed+ Lists+ { expectSkip = ormoluSkip,+ expectUnreadable = ormoluUnreadable,+ expectDeclined = []+ }, corpusInPackages = False } @@ -176,8 +184,8 @@ { expectSkip = [ "numeric" </> "should_run" </> "quotRem2Large.hs", "perf" </> "compiler" </> "parsing001.hs"- ]- <> ghcUnreadable,+ ],+ expectUnreadable = ghcUnreadable, expectDeclined = ghcDeclined }, corpusInPackages = False@@ -186,13 +194,15 @@ -- | GHC test suite files the formatter is right to refuse. ghcDeclined :: [FilePath] ghcDeclined =- [ "ghci.debugger" </> "HappyTest.hs",+ [ "driver" </> "T17786.hs",+ "ghci.debugger" </> "HappyTest.hs", "parser" </> "should_compile" </> "ColumnPragma.hs", "parser" </> "should_compile" </> "T7118.hs", "perf" </> "compiler" </> "T20261.hs", "perf" </> "compiler" </> "T5631.hs", "programs" </> "joao-circular" </> "Funcs_Parser_Lazy.hs",- "quasiquotation" </> "T4150.hs"+ "quasiquotation" </> "T4150.hs",+ "th" </> "T23378A.hs" ] -- | Packages from Hackage.@@ -342,6 +352,7 @@ "declaration" </> "value" </> "function" </> "infix" </> "esqueleto-0.hs", "declaration" </> "value" </> "function" </> "infix" </> "esqueleto-1.hs", "declaration" </> "class" </> "default-signatures.hs",+ "declaration" </> "class" </> "super-classes.hs", "declaration" </> "type-families" </> "closed-type-family" </> "with-comments.hs", "declaration" </> "deriving" </> "singleline.hs", "declaration" </> "deriving" </> "multiline.hs",@@ -363,10 +374,29 @@ "declaration" </> "splice" </> "quasiquote.hs", "declaration" </> "value" </> "function" </> "quasi-quotes.hs", "declaration" </> "value" </> "function" </> "operator-comments-0.hs",- "declaration" </> "signature" </> "fixity" </> "infix.hs"+ "declaration" </> "signature" </> "fixity" </> "infix.hs",+ "declaration" </> "class" </> "associated-data2.hs",+ "declaration" </> "class" </> "associated-types2.hs",+ "declaration" </> "class" </> "dependency-super-classes.hs",+ "declaration" </> "class" </> "functional-dependencies.hs",+ "declaration" </> "class" </> "multi-parameters2.hs",+ "declaration" </> "data" </> "gadt" </> "multiline.hs",+ "declaration" </> "data" </> "gadt" </> "simple.hs",+ "import" </> "no-implicit-prelude-package.hs",+ "other" </> "pragma-sorting.hs",+ "declaration" </> "splice" </> "bracket.hs",+ "declaration" </> "class" </> "type-operators3.hs",+ "other" </> "cpp" </> "lonely-hash.hs",+ "other" </> "cpp" </> "separation-0a.hs",+ "other" </> "cpp" </> "separation-0b.hs",+ "other" </> "cpp" </> "separation-1a.hs",+ "other" </> "cpp" </> "separation-1b.hs",+ "other" </> "cpp" </> "separation-2a.hs",+ "other" </> "cpp" </> "separation-2b.hs",+ "other" </> "cpp" </> "shifted.hs",+ "other" </> "cpp" </> "simple-import.hs" ] <> ormoluRecordBraces- <> ormoluUnreadable -- | Ormolu examples with a space between a record and its braces. ormoluRecordBraces :: [FilePath]@@ -383,14 +413,14 @@ "declaration" </> "value" </> "function" </> "record" </> "updaters.hs", "declaration" </> "value" </> "function" </> "record" </> "wildcards.hs", "declaration" </> "value" </> "pattern-synonyms" </> "bidirectional.hs",- "declaration" </> "value" </> "pattern-synonyms" </> "unidirectional.hs"+ "declaration" </> "value" </> "pattern-synonyms" </> "unidirectional.hs",+ "declaration" </> "value" </> "function" </> "pragmas.hs" ] -- | Ormolu examples GHC's own parser cannot read. ormoluUnreadable :: [FilePath] ormoluUnreadable =- [ "declaration" </> "class" </> "type-operators3.hs",- "declaration" </> "data" </> "datatype-contexts.hs",+ [ "declaration" </> "data" </> "datatype-contexts.hs", "declaration" </> "foreign" </> "foreign-import-multiline.hs", "declaration" </> "value" </> "function" </> "application-1.hs", "declaration" </> "value" </> "function" </> "application-2.hs",@@ -408,17 +438,6 @@ "declaration" </> "value" </> "function" </> "pattern" </> "or-patterns.hs", "declaration" </> "value" </> "function" </> "type-applications.hs", "other" </> "comment-before-hanging.hs",- "other" </> "cpp" </> "continuation.hs",- "other" </> "cpp" </> "cpp-and-imports.hs",- "other" </> "cpp" </> "lonely-hash.hs",- "other" </> "cpp" </> "separation-0a.hs",- "other" </> "cpp" </> "separation-0b.hs",- "other" </> "cpp" </> "separation-1a.hs",- "other" </> "cpp" </> "separation-1b.hs",- "other" </> "cpp" </> "separation-2a.hs",- "other" </> "cpp" </> "separation-2b.hs",- "other" </> "cpp" </> "shifted.hs",- "other" </> "cpp" </> "simple-import.hs", "other" </> "necessary-brackets.hs" ] @@ -432,58 +451,16 @@ "annotations" </> "should_fail" </> "annfail13.hs", "arrows" </> "should_fail" </> "T2111.hs", "arrows" </> "should_fail" </> "arrowfail003.hs",- "cabal" </> "fileStatus.hs",- "codeGen" </> "should_run" </> "CheckBoundsOK.hs",- "codeGen" </> "should_run" </> "T10245.hs", "codeGen" </> "should_run" </> "T12855.hs",- "codeGen" </> "should_run" </> "T2080.hs",- "codeGen" </> "should_run" </> "T7600.hs",- "codeGen" </> "should_run" </> "cas_int.hs",- "codeGen" </> "should_run" </> "cgrun044.hs",- "codeGen" </> "should_run" </> "cgrun071.hs",- "codeGen" </> "should_run" </> "cgrun072.hs",- "codeGen" </> "should_run" </> "cgrun075.hs",- "codeGen" </> "should_run" </> "cgrun076.hs",- "codeGen" </> "should_run" </> "cgrun077.hs",- "codeGen" </> "should_run" </> "cgrun079.hs",- "codeGen" </> "should_run" </> "cgrun080.hs",- "concurrent" </> "should_run" </> "T5611.hs",- "concurrent" </> "should_run" </> "T5611a.hs",- "concurrent" </> "should_run" </> "conc036.hs",- "concurrent" </> "should_run" </> "conc037.hs",- "concurrent" </> "should_run" </> "conc038.hs",- "concurrent" </> "should_run" </> "foreignInterruptible.hs", "corelint" </> "T21115.hs",- "deSugar" </> "should_run" </> "T5742.hs", "dependent" </> "should_fail" </> "RenamingStar.hs",- "dmdanal" </> "should_compile" </> "T9208.hs",- "driver" </> "FullGHCVersion.hs",- "driver" </> "T10869.hs",- "driver" </> "T10869A.hs",- "driver" </> "T10970.hs",- "driver" </> "T11763.hs",- "driver" </> "T12135.hs",- "driver" </> "T12674" </> "-T12674.hs",- "driver" </> "T12752pass.hs",+ "dependent" </> "should_fail" </> "T16326_Fail7.hs", "driver" </> "T16167.hs", "driver" </> "T16476a.hs", "driver" </> "T16476b.hs",- "driver" </> "T16521" </> "A.hs",- "driver" </> "T17786.hs",- "driver" </> "T2464.hs",- "driver" </> "T3389.hs",- "driver" </> "T8526" </> "A.hs", "driver" </> "bug1677" </> "Foo.hs",- "driver" </> "multipleHomeUnits" </> "c-file" </> "C.hs",- "driver" </> "multipleHomeUnits" </> "cpp-includes" </> "CPPIncludes.hs",- "driver" </> "multipleHomeUnits" </> "cpp-includes" </> "CPPIncludes_Down.hs",- "driver" </> "recomp011" </> "Main.hs",- "driver" </> "recomp021" </> "A.hs",- "driver" </> "should_fail" </> "T12752.hs",- "eyeball" </> "inline2.hs", "ffi" </> "should_fail" </> "capi_wrapper.hs", "ffi" </> "should_fail" </> "ccall_value.hs",- "ffi" </> "should_run" </> "T22159.hs", "gadt" </> "records-fail1.hs", "generics" </> "Uniplate" </> "GUniplate.hs", "ghci.debugger" </> "mdo.hs",@@ -494,62 +471,28 @@ "ghci" </> "prog013" </> "Bad.hs", "ghci" </> "scripts" </> "ghci022.hs", "ghci" </> "scripts" </> "ghci044a.hs",- "ghci" </> "should_run" </> "PackedDataCon" </> "ByteCode.hs",- "ghci" </> "should_run" </> "PackedDataCon" </> "Obj.hs",- "ghci" </> "should_run" </> "UnboxedTuples" </> "ByteCode.hs",- "ghci" </> "should_run" </> "UnboxedTuples" </> "Obj.hs",- "ghci" </> "should_run" </> "UnliftedDataTypeInterp" </> "ByteCode.hs",- "ghci" </> "should_run" </> "UnliftedDataTypeInterp" </> "Obj.hs", "haddock" </> "should_compile_flag_haddock" </> "haddockA004.hs", "haddock" </> "should_compile_flag_haddock" </> "haddockA011.hs",- "haddock" </> "should_compile_flag_haddock" </> "haddockA041.hs", "haddock" </> "should_compile_noflag_haddock" </> "haddockC004.hs", "haddock" </> "should_compile_noflag_haddock" </> "haddockC011.hs", "haddock" </> "should_fail_flag_haddock" </> "haddockE003.hs",- "hiefile" </> "should_compile" </> "CPP.hs",- "hiefile" </> "should_compile" </> "T22416.hs",- "hiefile" </> "should_compile" </> "hie002.hs",- "indexed-types" </> "should_compile" </> "T12538.hs",- "javascript" </> "T23346.hs",- "lib" </> "integer" </> "IntegerConversionRules.hs", "linear" </> "should_fail" </> "LinearNoExt.hs", "linear" </> "should_fail" </> "LinearNoExtU.hs", "linear" </> "should_fail" </> "T20083.hs",- "mdo" </> "should_compile" </> "mdo001.hs",- "mdo" </> "should_compile" </> "mdo002.hs",- "mdo" </> "should_compile" </> "mdo003.hs",- "mdo" </> "should_compile" </> "mdo004.hs",- "mdo" </> "should_compile" </> "mdo005.hs",- "mdo" </> "should_compile" </> "mdo006.hs",- "mdo" </> "should_fail" </> "mdofail001.hs",- "mdo" </> "should_fail" </> "mdofail002.hs",- "mdo" </> "should_fail" </> "mdofail003.hs",- "mdo" </> "should_fail" </> "mdofail004.hs", "mdo" </> "should_fail" </> "mdofail005.hs",- "mdo" </> "should_fail" </> "mdofail006.hs",- "mdo" </> "should_run" </> "mdorun001.hs",- "mdo" </> "should_run" </> "mdorun002.hs",- "mdo" </> "should_run" </> "mdorun003.hs",- "mdo" </> "should_run" </> "mdorun005.hs", "module" </> "Mod178_2.hs", "module" </> "T11432.hs", "module" </> "T11432a.hs", "module" </> "T12026.hs",+ "module" </> "mod182.hs", "module" </> "mod183.hs", "module" </> "mod69.hs", "module" </> "mod70.hs", "module" </> "mod76.hs", "module" </> "mod89.hs", "module" </> "mod98.hs",- "numeric" </> "should_run" </> "T12136.hs",- "numeric" </> "should_run" </> "T20291.hs",- "numeric" </> "should_run" </> "foundation.hs", "parser" </> "should_compile" </> "T10582.hs",- "parser" </> "should_compile" </> "T15279.hs", "parser" </> "should_compile" </> "read023.hs",- "parser" </> "should_compile" </> "read039.hs",- "parser" </> "should_compile" </> "read046.hs",- "parser" </> "should_compile" </> "read058.hs", "parser" </> "should_fail" </> "ExportCommaComma.hs", "parser" </> "should_fail" </> "InfixAppPatErr.hs", "parser" </> "should_fail" </> "MultilineStringsInnerTab.hs",@@ -568,6 +511,7 @@ "parser" </> "should_fail" </> "OpaqueParseFail3.hs", "parser" </> "should_fail" </> "Or1.hs", "parser" </> "should_fail" </> "OrPatInExprErr.hs",+ "parser" </> "should_fail" </> "ParserNoForallUnicode.hs", "parser" </> "should_fail" </> "ParserNoLambdaCase.hs", "parser" </> "should_fail" </> "ParserNoMultiWayIf.hs", "parser" </> "should_fail" </> "ParserNoTH1.hs",@@ -594,12 +538,14 @@ "parser" </> "should_fail" </> "T1344c.hs", "parser" </> "should_fail" </> "T13450.hs", "parser" </> "should_fail" </> "T13450TH.hs",+ "parser" </> "should_fail" </> "T14588.hs", "parser" </> "should_fail" </> "T15730.hs", "parser" </> "should_fail" </> "T15730b.hs", "parser" </> "should_fail" </> "T15849.hs", "parser" </> "should_fail" </> "T16270.hs", "parser" </> "should_fail" </> "T16270h.hs", "parser" </> "should_fail" </> "T16999.hs",+ "parser" </> "should_fail" </> "T17162.hs", "parser" </> "should_fail" </> "T17865.hs", "parser" </> "should_fail" </> "T17879a.hs", "parser" </> "should_fail" </> "T17879b.hs",@@ -624,6 +570,7 @@ "parser" </> "should_fail" </> "T25258b.hs", "parser" </> "should_fail" </> "T25258c.hs", "parser" </> "should_fail" </> "T25530.hs",+ "parser" </> "should_fail" </> "T26418.hs", "parser" </> "should_fail" </> "T3095.hs", "parser" </> "should_fail" </> "T3153.hs", "parser" </> "should_fail" </> "T3751.hs",@@ -633,6 +580,7 @@ "parser" </> "should_fail" </> "T3811e.hs", "parser" </> "should_fail" </> "T3811f.hs", "parser" </> "should_fail" </> "T5425.hs",+ "parser" </> "should_fail" </> "T8258NoGADTs.hs", "parser" </> "should_fail" </> "T8431.hs", "parser" </> "should_fail" </> "T8501a.hs", "parser" </> "should_fail" </> "T8501b.hs",@@ -720,21 +668,7 @@ "patsyn" </> "should_fail" </> "export-syntax.hs", "patsyn" </> "should_fail" </> "import-syntax.hs", "perf" </> "compiler" </> "T12234.hs",- "perf" </> "compiler" </> "T14683.hs",- "perf" </> "compiler" </> "T18698" </> "T18698.hs",- "perf" </> "should_run" </> "T13623.hs", "plugins" </> "T20803a.hs",- "plugins" </> "plugin-recomp" </> "Common.hs",- "primops" </> "should_run" </> "T4442.hs",- "primops" </> "should_run" </> "UnalignedAddrPrimOps.hs",- "printer" </> "Ppr010.hs",- "printer" </> "Ppr027.hs",- "profiling" </> "should_compile" </> "T19894" </> "Fold.hs",- "profiling" </> "should_compile" </> "T19894" </> "Operations.hs",- "profiling" </> "should_compile" </> "T19894" </> "Step.hs",- "profiling" </> "should_compile" </> "T19894" </> "StreamD.hs",- "profiling" </> "should_compile" </> "T19894" </> "StreamK.hs",- "profiling" </> "should_compile" </> "T19894" </> "Unfold.hs", "profiling" </> "should_compile" </> "T19894" </> "inline.hs", "profiling" </> "should_fail" </> "T17916.hs", "profiling" </> "should_fail" </> "proffail001.hs",@@ -761,30 +695,17 @@ "quotes" </> "TH_spliceViewPat" </> "A.hs", "rename" </> "should_fail" </> "T12879.hs", "rename" </> "should_fail" </> "T14907a.hs",- "rename" </> "should_fail" </> "T9032.hs", "rename" </> "should_fail" </> "T9437.hs", "rename" </> "should_fail" </> "rnfail016.hs", "rename" </> "should_fail" </> "rnfail016a.hs",+ "rename" </> "should_fail" </> "rnfail052.hs", "roles" </> "should_fail" </> "Roles7.hs",- "rts" </> "T12497.hs",- "rts" </> "linker" </> "T20494.hs",- "rts" </> "linker" </> "T5435.hs",- "rts" </> "stack002.hs",- "runghc" </> "T6132.hs",- "safeHaskell" </> "flags" </> "Flags01.hs",- "safeHaskell" </> "safeLanguage" </> "SafeLang18.hs", "saks" </> "should_fail" </> "saks_fail007.hs", "saks" </> "should_fail" </> "saks_fail024.hs", "saks" </> "should_fail" </> "saks_fail025.hs",- "simplCore" </> "T9646" </> "Main.hs",- "simplCore" </> "T9646" </> "StrictPrim.hs",- "simplCore" </> "T9646" </> "Type.hs", "simplCore" </> "should_compile" </> "T13658.hs",- "simplCore" </> "should_compile" </> "T21694.hs", "simplCore" </> "should_compile" </> "T5821.hs",- "simplCore" </> "should_compile" </> "T8832.hs", "simplCore" </> "should_fail" </> "T25117b.hs",- "simplCore" </> "should_run" </> "T21575.hs", "splice-imports" </> "SI27.hs", "stage1" </> "T2632.hs", "th" </> "T10279.hs",@@ -792,15 +713,10 @@ "th" </> "T10819.hs", "th" </> "T10891.hs", "th" </> "T11484.hs",- "th" </> "T16180.hs", "th" </> "T16326_TH.hs", "th" </> "T16980a.hs",- "th" </> "T23309A.hs",- "th" </> "T23378A.hs",- "th" </> "T2817.hs", "th" </> "T3177.hs", "th" </> "T3177a.hs",- "th" </> "T4436.hs", "th" </> "T5217.hs", "th" </> "T6018th.hs", "th" </> "T8807.hs",@@ -808,7 +724,6 @@ "th" </> "TH_ExplicitForAllRules_a.hs", "th" </> "TH_class1.hs", "th" </> "TH_dataD1.hs",- "th" </> "TH_foreignCallingConventions.hs", "th" </> "TH_implicitParams.hs", "th" </> "TH_lookupName.hs", "th" </> "TH_raiseErr1.hs",@@ -820,7 +735,6 @@ "th" </> "TH_reifyExplicitForAllFams.hs", "th" </> "TH_reifyInstances.hs", "th" </> "TH_reifyLinear.hs",- "th" </> "TH_reifyLocalDefs.hs", "th" </> "TH_reifyMkName.hs", "th" </> "TH_repE2.hs", "th" </> "TH_repGuard.hs",@@ -840,23 +754,21 @@ "th" </> "TH_spliceE3.hs", "th" </> "TH_spliceE4.hs", "th" </> "TH_spliceExpr1.hs",- "th" </> "TH_spliceGuard.hs", "th" </> "TH_tf1.hs", "th" </> "TH_tf3.hs", "th" </> "TH_unresolvedInfix.hs", "th" </> "TH_unresolvedInfix2.hs",- "typecheck" </> "should_compile" </> "FloatFDs.hs", "typecheck" </> "should_compile" </> "tc134.hs", "typecheck" </> "should_fail" </> "ExplicitSpecificity3.hs", "typecheck" </> "should_fail" </> "ExplicitSpecificity8.hs", "typecheck" </> "should_fail" </> "T13446.hs", "typecheck" </> "should_fail" </> "T14761b.hs", "typecheck" </> "should_fail" </> "T2126.hs",- "typecheck" </> "should_fail" </> "T3102.hs",+ "typecheck" </> "should_fail" </> "T3155.hs", "typecheck" </> "should_fail" </> "T9634.hs", "typecheck" </> "should_fail" </> "tcfail089.hs",- "typecheck" </> "should_run" </> "T1735.hs",- "typecheck" </> "should_run" </> "T1735_Help" </> "Main.hs",+ "typecheck" </> "should_fail" </> "tcfail166.hs",+ "typecheck" </> "should_fail" </> "tcfail183.hs", "typecheck" </> "should_run" </> "T4809.hs", "unboxedsums" </> "UnboxedSumsTH_Fail.hs", "unboxedsums" </> "unboxedsums4.hs",@@ -867,10 +779,7 @@ "vdq-rta" </> "should_fail" </> "T24159_pat_parse_error_5.hs", "vdq-rta" </> "should_fail" </> "T24159_pat_parse_error_6.hs", "warnings" </> "should_fail" </> "CaretDiagnostics2.hs",- "warnings" </> "should_fail" </> "SpecEMultipleTys.hs",- "wcompat-warnings" </> "WCompatWarningsOff.hs",- "wcompat-warnings" </> "WCompatWarningsOn.hs",- "wcompat-warnings" </> "WCompatWarningsOnOff.hs"+ "warnings" </> "should_fail" </> "SpecEMultipleTys.hs" ] ----------------------------------------------------------------------------@@ -892,7 +801,7 @@ -- @.cabal@ file. 'Nothing' for a corpus whose examples are not modules -- of a package, see 'corpusInPackages', and for one whose package could -- not be read.- exampleExtensions :: Maybe [Extension]+ exampleExtensions :: Maybe Defaults } deriving (Eq, Show) @@ -908,7 +817,7 @@ -- releases keep theirs, which the fixity benchmarks read. obtain :: Corpus -> IO (Either Text [Example]) obtain corpus = case corpusSource corpus of- Vendored dir -> Right <$> examplesIn corpus dir+ Vendored dir -> Right <$> examplesIn corpus dir [dir] Fetched url root -> do home <- corpusCache let unpacked = home </> corpusName corpus@@ -918,14 +827,14 @@ Left problem -> pure (Left problem) Right () -> do removePathForcibly archive- Right <$> examplesIn corpus (unpacked </> root)+ Right <$> examplesIn corpus (unpacked </> root) [unpacked </> root] HackageReleases releases -> do home <- corpusCache let root = home </> corpusName corpus createDirectoryIfMissing True root inTurn root releases >>= \case Left problem -> pure (Left problem)- Right () -> Right <$> examplesIn corpus root+ Right () -> Right <$> examplesIn corpus root (fmap (root </>) releases) where inTurn _ [] = pure (Right ()) inTurn root (name : rest) =@@ -1037,10 +946,11 @@ ---------------------------------------------------------------------------- -- Enumerating --- | Every example in an unpacked corpus, in a settled order.-examplesIn :: Corpus -> FilePath -> IO [Example]-examplesIn corpus root = do- found <- sort <$> haskellFilesIn root+-- | Every example in these directories of an unpacked corpus, named from+-- its root, in a settled order.+examplesIn :: Corpus -> FilePath -> [FilePath] -> IO [Example]+examplesIn corpus root dirs = do+ found <- sort . concat <$> traverse haskellFilesIn dirs reader <- packageReaderFor corpus let present = Set.fromList found files = filter (not . skipped) found@@ -1081,18 +991,9 @@ fromMaybe "" (listToMaybe (filter (`isSuffixOf` f) sourceExtensions)) -- | What each of a corpus's examples has in force before its own pragmas.-packageReaderFor :: Corpus -> IO (FilePath -> IO (Maybe [Extension]))+packageReaderFor :: Corpus -> IO (FilePath -> IO (Maybe Defaults)) packageReaderFor corpus | not (corpusInPackages corpus) = pure (const (pure Nothing)) | otherwise = do- reader <- newPackageReader Nothing+ reader <- newPackageReader Nothing [] pure (fmap (either (const Nothing) Just) . reader)--haskellFilesIn :: FilePath -> IO [FilePath]-haskellFilesIn dir = do- isDir <- doesDirectoryExist dir- if not isDir- then pure [dir | any (`isSuffixOf` dir) sourceExtensions]- else do- entries <- quietly [] (listDirectory dir)- concat <$> traverse (haskellFilesIn . (dir </>)) entries
tests/Tilia/Corpus/Manifest.hs view
@@ -19,11 +19,9 @@ readReport, writeReport, reasonLines,- accepting, ) where -import Control.Exception (SomeException, try) import Crypto.Hash.SHA256 qualified as SHA256 import Data.ByteString.Base16 qualified as B16 import Data.List (sortOn)@@ -32,10 +30,9 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as T-import Data.Text.IO qualified as T import System.Directory (createDirectoryIfMissing)-import System.Environment (lookupEnv) import System.FilePath (takeDirectory)+import Tilia.Utils (readUtf8File, writeUtf8File) ---------------------------------------------------------------------------- -- What an example does@@ -107,9 +104,9 @@ -- with a message about a file. readManifest :: FilePath -> IO Manifest readManifest path =- try (T.readFile path) >>= \case- Left (_ :: SomeException) -> pure Map.empty- Right text -> pure (Map.fromList (concatMap entry (T.lines text)))+ readUtf8File path >>= \case+ Nothing -> pure Map.empty+ Just text -> pure (Map.fromList (concatMap entry (T.lines text))) where entry line = case T.words line of (what : digest : rest)@@ -124,7 +121,8 @@ writeManifest :: FilePath -> Manifest -> IO () writeManifest path manifest = do createDirectoryIfMissing True (takeDirectory path)- T.writeFile path (T.unlines (header <> fmap line (Map.toAscList manifest)))+ writeUtf8File path $+ T.unlines (header <> fmap line (Map.toAscList manifest)) where header = [ "# What each example of this corpus does today, one line each.",@@ -149,9 +147,9 @@ -- 'readManifest'. readReport :: FilePath -> IO Report readReport path =- try (T.readFile path) >>= \case- Left (_ :: SomeException) -> pure Map.empty- Right text -> pure (go Nothing Map.empty (T.lines text))+ readUtf8File path >>= \case+ Nothing -> pure Map.empty+ Just text -> pure (go Nothing Map.empty (T.lines text)) where go section report = \case [] -> report@@ -173,7 +171,8 @@ writeReport :: FilePath -> [(FilePath, Outcome, Text)] -> IO () writeReport path entries = do createDirectoryIfMissing True (takeDirectory path)- T.writeFile path (T.unlines (header <> concatMap section grouped))+ writeUtf8File path $+ T.unlines (header <> concatMap section grouped) where interesting = [e | e@(_, outcome, _) <- entries, outcome /= Formatted] grouped =@@ -204,11 +203,3 @@ -- | The lines a report gives an example's reason. reasonLines :: Text -> [Text] reasonLines = fmap (" " <>) . T.lines . T.strip---- | Is this run supposed to write the records rather than check against--- them?-accepting :: IO Bool-accepting =- lookupEnv "TILIA_CORPUS_ACCEPT" >>= \case- Just s | s `notElem` ["", "0", "no", "false"] -> pure True- _ -> pure False
tests/Tilia/CorpusSpec.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-} -- | Formatting other people's Haskell. module Tilia.CorpusSpec (spec) where@@ -15,8 +14,9 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding (decodeUtf8')-import GHC.LanguageExtensions.Type (Extension)-import System.FilePath (replaceExtension)+import System.Directory (createDirectoryIfMissing)+import System.FilePath (replaceExtension, (</>))+import System.IO.Temp (withSystemTempDirectory) import Test.Hspec hiding (Example, after, before, example) import Tilia.Corpus import Tilia.Corpus.Manifest@@ -29,7 +29,7 @@ ParsedModule (..), parseModule, )-import Tilia.Pragma (movesPositions)+import Tilia.Pragma (Defaults, movesPositions) import Tilia.Span (spanStartColumn, spanStartLine) import Tilia.Span.Ghc (spanOfSrcSpan) import Tilia.TestConfig (formatCppExample, printExample)@@ -39,10 +39,22 @@ countLeaves, effectiveExtensionsFor, parserConfigFor,+ switchedOn,+ withEnvironment, ) spec :: Spec spec = do+ describe "obtaining Hackage releases" $+ it "takes in only the releases listed, whatever else is in the cache" $+ withSystemTempDirectory "tilia-corpus" $ \cache -> do+ for_ ["listed-1.0", "unlisted-1.0"] $ \release -> do+ createDirectoryIfMissing True (cache </> "hackage" </> release)+ writeFile (cache </> "hackage" </> release </> "M.hs") "module M where\n"+ obtained <-+ withEnvironment [("TILIA_CORPUS_DIR", cache)] . obtain $+ hackagePackages{corpusSource = HackageReleases ["listed-1.0"]}+ fmap (fmap exampleName) obtained `shouldBe` Right ["listed-1.0/M.hs"] corpusSpec vendoredExamples corpusSpec ormoluExamples corpusSpec ghcTestSuite@@ -70,17 +82,19 @@ parallel $ for_ examples $ \example -> it (exampleName example) $ do result <- run example- case verdict (Set.member (exampleName example) declines) result of+ let holds = Set.member (exampleName example)+ case verdict (holds unreadable) (holds declines) result of Passes -> pure () Fails why -> expectationFailure (T.unpack why) Reserved why -> pendingWith (T.unpack why) where+ unreadable = Set.fromList (expectUnreadable listed) declines = Set.fromList (expectDeclined listed) -- | A corpus checked against a generated record of what it does. againstRecord :: FilePath -> (Example -> IO Result) -> [Example] -> Spec againstRecord path run examples = do- accept <- runIO accepting+ accept <- runIO (switchedOn "TILIA_CORPUS_ACCEPT") if accept then accepted else checked where reportPath = replaceExtension path ".report"@@ -234,11 +248,16 @@ -- | What is to be said about what one example produced. verdict ::+ -- | Does the corpus say this one cannot be read?+ Bool -> -- | Does the corpus say this one should be declined? Bool -> Result -> Verdict-verdict declines (Result outcome why _) = case outcome of+verdict True _ (Result outcome _ _)+ | outcome `elem` [DoesNotParse, NotUtf8] = Passes+ | otherwise = Fails "the corpus says this cannot be read, and it was read"+verdict False declines (Result outcome why _) = case outcome of Broken -> Fails why NotUtf8 -> Fails (unlisted "is not UTF-8") DoesNotParse -> Fails (unlisted ("does not parse, at " <> why))@@ -301,7 +320,7 @@ -- | Everything that can be established about one example without doing any -- more input or output.-checkPure :: Palette -> FilePath -> Maybe [Extension] -> Text -> Maybe Text -> Result+checkPure :: Palette -> FilePath -> Maybe Defaults -> Text -> Maybe Text -> Result checkPure palette path package source expected | movesPositions source = Result Declined "a pragma that moves positions, which we do not rewrite" noDigest@@ -367,7 +386,7 @@ -- | Everything that can be established about an example with conditionals in -- it.-checkCpp :: Palette -> FilePath -> Maybe [Extension] -> Text -> Maybe Text -> Result+checkCpp :: Palette -> FilePath -> Maybe Defaults -> Text -> Maybe Text -> Result checkCpp palette path package source expected = case formatCppExample package path source of Left why -> Result Declined (describeCppError why) noDigest Right formatted -> case (countLeaves source, countLeaves formatted) of
tests/Tilia/Cpp/PropertiesSpec.hs view
@@ -17,7 +17,7 @@ import Tilia.Equivalence (syntaxDifference) import Tilia.Parser (parseModule, pmModule) import Tilia.Render (defaultRenderConfig)-import Tilia.TestUtils (answeredLeaves, defaultParserConfig)+import Tilia.TestUtils (answeredLeaves, defaultParserConfig, parses) import Tilia.Utils (tshow) spec :: Spec@@ -102,11 +102,6 @@ formatted m k = case format (sourceOf m) of Left _ -> property Discard Right out -> property (k out)--parses :: Text -> Bool-parses t = case parseModule defaultParserConfig "M.hs" t of- Left _ -> False- Right _ -> True difference :: Text -> Text -> Maybe Text difference before after = do
tests/Tilia/CppSpec.hs view
@@ -4,6 +4,7 @@ -- | Whether documents printed from different configurations line up. module Tilia.CppSpec (spec) where +import Data.Either (isLeft, isRight) import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map import Data.Maybe (isJust, maybeToList)@@ -20,11 +21,14 @@ import Tilia.Source (directiveOnLine) import Tilia.Span (Span) import Tilia.TestUtils- ( configurations,+ ( answeredLeaves,+ answeredLinearLeaves,+ configurations, countLeaves, defaultParserConfig, leaves, linearLeaves,+ parses, ) -- | Format a module which uses the C preprocessor, configured with nothing.@@ -253,6 +257,14 @@ (said (linearLeaves atDeclarations), said (leaves atDeclarations)) `shouldSatisfy` uncurry (==) + it "leaves out a configuration that aborts with #error" $+ said+ ( (\every -> all (`elem` every))+ <$> answeredLeaves abortingBranches+ <*> answeredLinearLeaves abortingBranches+ )+ `shouldBe` Right True+ describe "directives it cannot read" $ do it "refuses a module whose conditionals do not balance" $ formatCpp unbalanced `shouldBe` Left "the #ifdef at line 3 is never closed"@@ -654,6 +666,28 @@ "#endif" ] +-- | Branches that abort with @#error@, the first of one conditional and the+-- last of another.+abortingBranches :: Text+abortingBranches =+ T.unlines+ [ "module M where",+ "",+ "#if !defined(__GLASGOW_HASKELL__)",+ "#error \"Only GHC is supported.\"",+ "#endif",+ "",+ "wordSize :: Int",+ "wordSize =",+ "#if WORD_SIZE_IN_BITS == 64",+ " 64",+ "#elif WORD_SIZE_IN_BITS == 32",+ " 32",+ "#else",+ "#error \"Unsupported word size.\"",+ "#endif"+ ]+ -- | An @#else@ that the @#if@ around it rules out, which no configuration -- takes. ruledOut :: Text@@ -689,10 +723,6 @@ ---------------------------------------------------------------------------- -- Asking it -isLeft, isRight :: Either a b -> Bool-isLeft = either (const True) (const False)-isRight = either (const False) (const True)- sameBranch :: (Maybe Text, Maybe Text) -> Either Text () sameBranch (Nothing, Nothing) = Right () sameBranch (Just input, Just output) = sameProgram input output@@ -707,10 +737,6 @@ Left _ -> Left ("did not parse:\n" <> text) Right parsed -> Right (pmModule parsed) sameBranch _ = Left "preprocessing changed between succeeding and failing"---- | Does a configuration parse, with what its own pragmas turn on?-parses :: Text -> Bool-parses t = either (const False) (const True) (parseModule defaultParserConfig "example.hs" t) -- | How many modules parsing every branch of a module comes to. branchesParsed :: Text -> Maybe Int
tests/Tilia/Doc/InternalSpec.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} --- | The span algebra and the rendering engine's primitives.+-- | The rendering engine's primitives. module Tilia.Doc.InternalSpec (spec) where import Data.Text (Text)@@ -15,18 +15,6 @@ spec :: Spec spec = do- describe "Span" $ do- it "recognises a single-line span" $- isSingleLine (mkSpan (3, 1) (3, 40)) `shouldBe` True- it "recognises a multi-line span" $- isSingleLine (mkSpan (3, 1) (4, 1)) `shouldBe` False- it "unions to cover both operands" $- mkSpan (1, 5) (1, 9) <> mkSpan (3, 2) (4, 1)- `shouldBe` mkSpan (1, 5) (4, 1)- it "takes the earlier start column when starts share a line" $- mkSpan (1, 9) (1, 20) <> mkSpan (1, 3) (1, 5)- `shouldBe` mkSpan (1, 3) (1, 20)- describe "atoms" $ do it "renders nothing for an empty document" $ out mempty `shouldBe` ""
tests/Tilia/Doc/PropertiesSpec.hs view
@@ -11,7 +11,6 @@ import Tilia.Doc import Tilia.Doc.Combinators import Tilia.Gen-import Tilia.Span spec :: Spec spec = do@@ -64,20 +63,6 @@ property $ \(AnyDoc d) -> do out (broken (mempty <> d)) === out (broken d) .&&. out (broken (d <> mempty)) === out (broken d)-- describe "Span" $ do- it "unions associatively" $- property $ \(AnySpan a) (AnySpan b) (AnySpan c) ->- (a <> b) <> c === a <> (b <> c)-- it "unions to something covering both operands" $- property $ \(AnySpan a) (AnySpan b) ->- let u = a <> b- in counterexample (show u) (covers u a && covers u b)-- it "is idempotent under union" $- property $- \(AnySpan a) -> a <> a === a -- | Everything that is not whitespace, in order. --
tests/Tilia/EditorSpec.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-}@@ -7,16 +6,14 @@ -- | Formatting what an editor holds of a module. module Tilia.EditorSpec (spec) where -import Data.ByteString qualified as BS+import Control.Exception (throwIO) import Data.Choice (pattern Don't) import Data.Map.Strict qualified as Map import Data.Text (Text) import Data.Text qualified as T-import Data.Text.Encoding qualified as T import Data.Version (makeVersion) import GHC.LanguageExtensions.Type (Extension (..))-import System.Directory (createDirectoryIfMissing)-import System.FilePath (takeDirectory, (</>))+import System.FilePath ((</>)) import System.IO.Temp (withSystemTempDirectory) import Test.Hspec import Tilia.Editor@@ -29,6 +26,7 @@ import Tilia.Fixity.HiFile (HiExport (..), HiFile (..), HiName (..)) import Tilia.Format (FormatError, formatErrorExitCode) import Tilia.Run (Outcome (..))+import Tilia.TestUtils (withOpsProject) spec :: Spec spec = describe "formatting what an editor holds" $ do@@ -120,6 +118,11 @@ buffer ("src" </> "Uses.hs") "module Uses where\nf = \\case\n _ -> 1\n" `shouldReturn` CameTo "module Uses where\n\nf = \\case\n _ -> 1\n" + it "fails a module whose formatting throws rather than throwing itself" $+ withCompiled $ \buffer ->+ buffer ("src" </> "Uses.hs") "module Uses where\nimport Boom\nx=a <?> b\n"+ `shouldReturn` CameFailed 19+ it "leaves out the branches the versions it is given rule out" $ withCompiled $ \buffer -> buffer ("src" </> "Uses.hs") versioned `shouldReturn` CameOut@@ -219,12 +222,7 @@ -- handed to the test as a way to format a buffer as one of its files. withProject :: ((FilePath -> Text -> IO Came) -> Expectation) -> Expectation withProject act =- inProject $ \dir project -> do- let planFile = dir </> "elsewhere" </> "plan.json"- write planFile $- "{\"compiler-id\":\"ghc-0.0\",\"install-plan\":[\- \{\"pkg-name\":\"fake\",\"pkg-version\":\"0.1.0.0\",\- \\"pkg-src\":{\"type\":\"local\",\"path\":\"./.\"}}]}"+ inProject $ \project planFile -> act $ \file text -> do let path = project </> file editorSession@@ -241,10 +239,11 @@ -- | The project of 'withProject', with what compiling it would establish -- given instead of a plan: @LambdaCase@ in force, version 0.1.0.0 of the--- package, and the interfaces of @Ops@ and of an empty @Prelude@.+-- package, and the interfaces of @Ops@ and of an empty @Prelude@; asking for+-- that of @Boom@ throws. withCompiled :: ((FilePath -> Text -> IO Came) -> Expectation) -> Expectation withCompiled act =- inProject $ \_ project ->+ inProject $ \project _ -> act $ \file text -> do let path = project </> file compiledSession path compiled (Don't #checkAst) (Don't #checkIdempotence)@@ -257,7 +256,9 @@ { compiledExtensions = [ImplicitPrelude, LambdaCase], compiledWith = makeVersion [9, 14, 1], compiledPackages = Map.singleton "fake" (makeVersion [0, 1, 0, 0]),- compiledInterface = pure . (`Map.lookup` interfaces)+ compiledInterface = \case+ "Boom" -> throwIO (userError "boom")+ m -> pure (Map.lookup m interfaces) } interfaces = Map.fromList@@ -273,40 +274,18 @@ -- | A project with a module declaring @infixl 6 <+>@, one beginning with a -- byte order mark declaring @infixr 0 <->@, and a @.tiliaignore@ excluding--- @src/Generated.hs@, given to the test with the directory it is in.+-- @src/Generated.hs@, given to the test with a plan for it. inProject :: (FilePath -> FilePath -> Expectation) -> Expectation-inProject act =- withSystemTempDirectory "tilia-editor" $ \dir -> do- let project = dir </> "project"- write (project </> "fake.cabal") $- T.unlines- [ "cabal-version: 2.4",- "name: fake",- "version: 0.1.0.0",- "library",- " exposed-modules: Ops, Marked",- " hs-source-dirs: src",- " default-language: Haskell2010"- ]- write (project </> "src" </> "Ops.hs") $- T.unlines- [ "module Ops ((<+>)) where",- "infixl 6 <+>",- "(<+>) :: a -> a -> a",- "(<+>) = const"- ]- write (project </> "src" </> "Marked.hs") $- T.unlines- [ "\xFEFFmodule Marked ((<->)) where",- "infixr 0 <->",- "(<->) :: a -> a -> a",- "(<->) = const"- ]- write (project </> ".tiliaignore") "src/Generated.hs\n"- act dir project---- | Write a file, with the directories it is in.-write :: FilePath -> Text -> IO ()-write path text = do- createDirectoryIfMissing True (takeDirectory path)- BS.writeFile path (T.encodeUtf8 text)+inProject =+ withOpsProject+ [ ( "src" </> "Marked.hs",+ T.unlines+ [ "\xFEFFmodule Marked ((<->)) where",+ "infixr 0 <->",+ "(<->) :: a -> a -> a",+ "(<->) = const"+ ]+ ),+ (".tiliaignore", "src/Generated.hs\n")+ ]+ []
tests/Tilia/EquivalenceSpec.hs view
@@ -137,6 +137,77 @@ "module M where\nimport Data.Maybe\nimport Data.Char () -- not needed, just\n -- re-exported\n" `losesTheCommentsOf` "module M where\n\nimport Data.Char () -- not needed, just\nimport Data.Maybe\n" + describe "what the header puts in force" $ do+ it "does not mind language pragmas sorted where that changes nothing" $+ "{-# LANGUAGE NoStarIsType #-}\n{-# LANGUAGE TypeOperators #-}\nmodule M where\n"+ `keepsTheCommentsOf` "{-# LANGUAGE TypeOperators #-}\n{-# LANGUAGE NoStarIsType #-}\nmodule M where\n"++ it "does not mind options sorted below the language pragmas" $+ "{-# OPTIONS_GHC -Wall #-}\n{-# LANGUAGE LambdaCase #-}\nmodule M where\n"+ `keepsTheCommentsOf` "{-# LANGUAGE LambdaCase #-}\n{-# OPTIONS_GHC -Wall #-}\nmodule M where\n"++ it "does not mind Haddock's options sorted below GHC's" $+ "{-# OPTIONS_HADDOCK not-home #-}\n{-# OPTIONS_GHC -Wall #-}\nmodule M where\n"+ `keepsTheCommentsOf` "{-# OPTIONS_GHC -Wall #-}\n{-# OPTIONS_HADDOCK not-home #-}\nmodule M where\n"++ it "notices Haddock's options coming out in another order" $+ differsBecause+ "{-# OPTIONS_HADDOCK hide #-}\n{-# OPTIONS_HADDOCK not-home #-}\nmodule M where\n"+ "{-# OPTIONS_HADDOCK not-home #-}\n{-# OPTIONS_HADDOCK hide #-}\nmodule M where\n"+ "changed the OPTIONS_HADDOCK pragmas"++ it "notices the flags of options coming out in another order" $+ differsBecause+ "{-# OPTIONS_GHC -Wall #-}\n{-# OPTIONS -Wno-orphans #-}\nmodule M where\n"+ "{-# OPTIONS -Wno-orphans #-}\n{-# OPTIONS_GHC -Wall #-}\nmodule M where\n"+ "changed the flags of the options pragmas"++ it "notices an options pragma written twice coming out once" $+ differsBecause+ "{-# OPTIONS_GHC -fplugin-opt=P:x #-}\n{-# OPTIONS_GHC -fplugin-opt=P:x #-}\nmodule M where\n"+ "{-# OPTIONS_GHC -fplugin-opt=P:x #-}\nmodule M where\n"+ "changed the flags of the options pragmas"++ it "notices an extension turned off by names moved past each other" $+ differsBecause+ "{-# LANGUAGE NoLambdaCase #-}\n{-# LANGUAGE LambdaCase #-}\nmodule M where\n"+ "{-# LANGUAGE LambdaCase #-}\n{-# LANGUAGE NoLambdaCase #-}\nmodule M where\n"+ "switches LambdaCase"++ it "notices an extension turned off past one implying it" $+ differsBecause+ "{-# LANGUAGE NoMonoLocalBinds #-}\n{-# LANGUAGE TypeFamilies #-}\nmodule M where\n"+ "{-# LANGUAGE TypeFamilies #-}\n{-# LANGUAGE NoMonoLocalBinds #-}\nmodule M where\n"+ "switches MonoLocalBinds"++ it "notices the Prelude turned back on past RebindableSyntax" $+ differsBecause+ "{-# LANGUAGE ImplicitPrelude #-}\n{-# LANGUAGE RebindableSyntax #-}\nmodule M where\n"+ "{-# LANGUAGE RebindableSyntax #-}\n{-# LANGUAGE ImplicitPrelude #-}\nmodule M where\n"+ "switches ImplicitPrelude"++ it "notices an option turning off what a language pragma turns on" $+ differsBecause+ "{-# OPTIONS_GHC -XNoLambdaCase #-}\n{-# LANGUAGE LambdaCase #-}\nmodule M where\n"+ "{-# LANGUAGE LambdaCase #-}\n{-# OPTIONS_GHC -XNoLambdaCase #-}\nmodule M where\n"+ "switches LambdaCase"++ it "notices a pragma moved into the header, though GHC's defaults have it" $+ differsBecause+ "module M where\n{-# LANGUAGE MultiParamTypeClasses #-}\n"+ "{-# LANGUAGE MultiParamTypeClasses #-}\nmodule M where\n"+ "MultiParamTypeClasses"++ it "notices another edition named last" $+ differsBecause+ "{-# LANGUAGE Haskell2010 #-}\n{-# LANGUAGE GHC2021 #-}\nmodule M where\n"+ "{-# LANGUAGE GHC2021 #-}\n{-# LANGUAGE Haskell2010 #-}\nmodule M where\n"+ "changed the edition in force"++ it "does not mind a pragma below the header, which GHC does not read" $+ "module M where\n{-# LANGUAGE NoLambdaCase #-}\n{-# LANGUAGE LambdaCase #-}\nf = 1\n"+ `keepsTheCommentsOf` "module M where\n{-# LANGUAGE LambdaCase #-}\n{-# LANGUAGE NoLambdaCase #-}\nf = 1\n"+ ---------------------------------------------------------------------------- -- Helpers @@ -152,6 +223,12 @@ keepsTheCommentsOf :: Text -> Text -> Expectation keepsTheCommentsOf went came = difference went came `shouldBe` Nothing++-- | Two modules that differ, for a reason that says the given thing.+differsBecause :: Text -> Text -> Text -> Expectation+differsBecause went came reason = case difference went came of+ Nothing -> expectationFailure "saw no difference"+ Just why -> why `shouldSatisfy` T.isInfixOf reason losesTheCommentsOf :: Text -> Text -> Expectation losesTheCommentsOf went came =
tests/Tilia/Fixity/CabalSpec.hs view
@@ -1,16 +1,30 @@ {-# LANGUAGE OverloadedStrings #-} --- | Reading a @.cabal@ file's fields without a cabal parser.+-- | Reading a package's description out of its @.cabal@ file and its+-- tarball. module Tilia.Fixity.CabalSpec (spec) where import Codec.Archive.Tar qualified as Tar import Codec.Archive.Tar.Entry qualified as Tar+import Codec.Compression.GZip qualified as GZip import Data.ByteString.Lazy qualified as BL import Data.Text (Text)+import Data.Text qualified as T import Data.Text.Encoding qualified as T+import Distribution.PackageDescription (GenericPackageDescription) import GHC.LanguageExtensions.Type (Extension (..))+import System.FilePath ((</>))+import System.IO.Temp (withSystemTempDirectory) import Test.Hspec+import Tilia.Cabal.Package+ ( declaredModules,+ describePackageProblem,+ packageExtensions,+ packageSourceDirs,+ parsePackageDescription,+ ) import Tilia.Fixity.Cabal+import Tilia.Pragma (effectiveExtensions) spec :: Spec spec = do@@ -40,11 +54,28 @@ cabalFileInArchive ( archiveOf [ ("hspec-2.11.17/Setup.lhs", "main = undefined\n"),- ("hspec-2.11.17/hspec.cabal", "library\n exposed-modules: Test.Hspec, Test.Hspec.Runner\n")+ ( "hspec-2.11.17/hspec.cabal",+ header+ <> "library\n\+ \ exposed-modules: Test.Hspec, Test.Hspec.Runner\n"+ ) ] )- `shouldSatisfy` maybe False (elem "Test.Hspec.Runner" . containedModules)+ `shouldSatisfy` maybe False (elem "Test.Hspec.Runner" . modulesOf) + describe "a tarball that cannot be read" $ do+ it "leaves its package unread when it is not gzip" $+ withTarball "not really a tarball" $ \path ->+ packageModules path `shouldReturn` Nothing++ it "leaves its package unread when it is cut short in its cabal file" $ do+ let names = ["M" <> T.pack (show n) | n <- [1 .. 5000 :: Int]]+ cabal =+ header <> "library\n exposed-modules: " <> T.intercalate ", " names+ whole = GZip.compress (Tar.write [entryFor "big-1.0/big.cabal" cabal])+ withTarball (BL.take (BL.length whole `div` 2) whole) $ \path ->+ packageModules path `shouldReturn` Nothing+ describe "plain fields" $ do it "reads a one-line field" $ exposed "library\n exposed-modules: A.B, C.D\n"@@ -77,14 +108,11 @@ exposed "library\n exposed-modules:\n A\n build-depends: base\n" `shouldMatchList` ["A"] - it "ignores anything that is not a module name" $- exposed "library\n exposed-modules: A, base >=4, -Wall\n"- `shouldMatchList` ["A"]- describe "conditionals" $ do it "takes a branch nested inside an if" $ exposed- "library\n\+ "flag fancy\n\+ \library\n\ \ exposed-modules: A\n\ \ if flag(fancy)\n\ \ exposed-modules: B\n"@@ -101,7 +129,9 @@ it "takes a branch nested two deep" $ exposed- "library\n\+ "flag a\n\+ \flag b\n\+ \library\n\ \ if flag(a)\n\ \ if flag(b)\n\ \ exposed-modules: Deep\n"@@ -126,12 +156,25 @@ `shouldMatchList` ["A", "C"] it "keeps reading source directories past one" $- sourceDirs "library\n hs-source-dirs: src\n-- a comment\ntest-suite t\n hs-source-dirs: tests\n"+ sourceDirs+ "library\n\+ \ hs-source-dirs: src\n\+ \-- a comment\n\+ \test-suite t\n\+ \ type: exitcode-stdio-1.0\n\+ \ main-is: T.hs\n\+ \ hs-source-dirs: tests\n" `shouldBe` ["src", "tests", "."] describe "where a component with no hs-source-dirs lives" $ do it "offers the package directory even when other components name one" $- sourceDirs "library\n build-depends: base\ntest-suite t\n hs-source-dirs: tests\n"+ sourceDirs+ "library\n\+ \ build-depends: base\n\+ \test-suite t\n\+ \ type: exitcode-stdio-1.0\n\+ \ main-is: T.hs\n\+ \ hs-source-dirs: tests\n" `shouldBe` ["tests", "."] it "offers it last, so a named directory is tried first" $@@ -186,6 +229,8 @@ "library\n\ \ default-language: Haskell2010\n\ \test-suite spec\n\+ \ type: exitcode-stdio-1.0\n\+ \ main-is: T.hs\n\ \ default-language: GHC2021\n" `shouldSatisfy` elem TypeOperators @@ -194,6 +239,8 @@ "library\n\ \ default-language: Haskell2010\n\ \test-suite spec\n\+ \ type: exitcode-stdio-1.0\n\+ \ main-is: T.hs\n\ \ default-language: GHC2021\n" `shouldSatisfy` elem ImplicitPrelude @@ -204,7 +251,12 @@ it "takes every component's, since it does not know which one asks" $ do let found = extensions- "library\n default-extensions: LambdaCase\ntest-suite t\n default-extensions: MultiWayIf\n"+ "library\n\+ \ default-extensions: LambdaCase\n\+ \test-suite t\n\+ \ type: exitcode-stdio-1.0\n\+ \ main-is: T.hs\n\+ \ default-extensions: MultiWayIf\n" found `shouldSatisfy` elem LambdaCase found `shouldSatisfy` elem MultiWayIf @@ -221,17 +273,43 @@ -- | The modules a @.cabal@ of this shape holds. exposed :: Text -> [Text]-exposed = containedModules+exposed = modulesOf . described +-- | The directories a @.cabal@ of this shape offers modules under.+sourceDirs :: Text -> [FilePath]+sourceDirs = packageSourceDirs . described+ -- | What a @.cabal@ of this shape puts in force. extensions :: Text -> [Extension]-extensions = declaredExtensions+extensions = (`effectiveExtensions` "") . packageExtensions . described +-- | What a @.cabal@ file holding the fields given describes.+described :: Text -> GenericPackageDescription+described fields =+ either (error . T.unpack . describePackageProblem) id $+ parsePackageDescription "t.cabal" (T.encodeUtf8 (header <> fields))++-- | The fields a @.cabal@ file needs before its components.+header :: Text+header = "cabal-version: 2.4\nname: t\nversion: 0\n"++-- | The names of the modules a package declares.+modulesOf :: GenericPackageDescription -> [Text]+modulesOf = fmap fst . declaredModules+ -- | A tar entry at the given path, holding the given text. entryFor :: FilePath -> Text -> Tar.Entry entryFor path contents = case Tar.toTarPath False path of Left why -> error why Right tarPath -> Tar.fileEntry tarPath (BL.fromStrict (T.encodeUtf8 contents))++-- | Run something with a tarball holding these bytes.+withTarball :: BL.ByteString -> (FilePath -> Expectation) -> Expectation+withTarball bytes act =+ withSystemTempDirectory "tilia-tarball" $ \dir -> do+ let path = dir </> "big-1.0.tar.gz"+ BL.writeFile path bytes+ act path -- | An archive of those entries, in order. archiveOf :: [(FilePath, Text)] -> Tar.Entries e
tests/Tilia/Fixity/CacheSpec.hs view
@@ -17,7 +17,7 @@ import Tilia.Fixity import Tilia.Fixity.Cache import Tilia.Fixity.PackageDb (Installed (..), InstalledPackage (..))-import Tilia.TestUtils (withEnvironment)+import Tilia.TestUtils (underAsciiLocale, withEnvironment) spec :: Spec spec = do@@ -65,6 +65,14 @@ ] storeEstablished cache "thing-1.0" "Edges" established cachedEstablished cache "thing-1.0" "Edges" `shouldReturn` Just established++ it "round-trips a name that is not ASCII under any locale" $ \cache -> do+ let established =+ declaring [((InTerms, OpName "∘"), Fixity RightAssoc 9)]+ underAsciiLocale $ do+ storeEstablished cache "thing-1.0" "Compose" established+ cachedEstablished cache "thing-1.0" "Compose"+ `shouldReturn` Just established it "remembers that a module declares nothing" $ \cache -> do storeEstablished cache "thing-1.0" "Quiet" mempty
tests/Tilia/Fixity/DebugSpec.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-}@@ -17,14 +16,14 @@ ( Direction (..), Established (..), Fixity (..),+ Namespace (..), OpName (..),- inBothNamespaces, resolveScope, ) import Tilia.Fixity.Debug (fixityNotes, renderFixityNotes) import Tilia.Palette (Palette (Plain)) import Tilia.Parser (pmModule)-import Tilia.TestUtils (parsed)+import Tilia.TestUtils (inBothNamespaces, parsed) import Tilia.Utils (lineWidth, visibleLength) spec :: Spec@@ -90,6 +89,20 @@ "import Left\nimport Right\nf a b = a <+> b\n" >>= (`mentions` "the imports disagree about it: infixl 6 in Left but infixr 5 in Right") + it "says when they disagree about a constructor written among types" $ do+ let offers = [("Left", infixl' 6), ("Right", Fixity RightAssoc 5)]+ answerOf m =+ mempty+ { establishedFixities =+ Map.fromList+ [((InTerms, OpName ":>"), f) | Just f <- [lookup m offers]]+ }+ notesAgainst answerOf "import Left\nimport Right\ntype T = Int :> Int\n"+ >>= ( `mentions`+ "the imports disagree about it: \+ \infixl 6 in Left but infixr 5 in Right"+ )+ it "gives an operator one line however often it is written" $ do told <- notesFor [("Prelude", Just [])] "f a b c = a <?> b <?> c <?> a\n" length (filter (T.isInfixOf "<?>") told) `shouldBe` 1@@ -173,20 +186,26 @@ [(Text, Maybe [(Text, Fixity)])] -> Text -> IO [Text]-notesThrough chains world source =- renderFixityNotes Plain . Map.singleton "M.hs"- <$> fixityNotes (Is #implicitPrelude) (pure . answerOf) scope (pure module')+notesThrough chains world = notesAgainst answerOf where- scope = resolveScope (Is #implicitPrelude) answerOf (pure hsModule) answerOf m = case exportsOf m of Just fixities -> mempty{establishedFixities = fixities} Nothing -> mempty{establishedUntold = Set.singleton (fromMaybe [] (lookup m chains))}- hsModule = pmModule module'- module' = parsed ("module M where\n" <> source) exportsOf m = do declared <- lookup m world inBothNamespaces . Map.fromList . fmap (\(op, fixity) -> (OpName op, fixity)) <$> declared++-- | The account given of a module, against what reading each module it+-- imports established.+notesAgainst :: (Text -> Established) -> Text -> IO [Text]+notesAgainst answerOf source =+ renderFixityNotes Plain . Map.singleton "M.hs"+ <$> fixityNotes (Is #implicitPrelude) (pure . answerOf) scope (pure module')+ where+ scope = resolveScope (Is #implicitPrelude) answerOf (pure hsModule)+ hsModule = pmModule module'+ module' = parsed ("module M where\n" <> source) infixl' :: Int -> Fixity infixl' = Fixity LeftAssoc
tests/Tilia/Fixity/DependenciesSpec.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-}@@ -19,7 +18,6 @@ import Data.ByteString qualified as BS import Data.Choice (pattern Don't, pattern Is) import Data.Foldable (for_)-import Data.List (isSuffixOf, sort) import Data.Map.Strict qualified as Map import Data.Set qualified as Set import Data.Text (Text)@@ -27,8 +25,7 @@ import Data.Text.Encoding qualified as T import GHC.Hs (HsModule) import GHC.Hs.Extension (GhcPs)-import System.Directory (doesDirectoryExist, doesFileExist, listDirectory)-import System.FilePath ((</>))+import System.Directory (doesFileExist) import Test.Hspec import Tilia.BootFixities (bootFixities) import Tilia.Cabal.Plan@@ -40,7 +37,7 @@ import Tilia.Fixity.Plan import Tilia.Gathered (gathered) import Tilia.Parser-import Tilia.TestUtils (defaultParserConfig, routedResolver)+import Tilia.TestUtils (defaultParserConfig, haskellFilesIn, routedResolver) import Tilia.WithProjectPlan ( Dependency (..), compilerShipped,@@ -276,18 +273,6 @@ Left _ -> Nothing Right pm -> Just (pmModule pm) )--haskellFilesIn :: FilePath -> IO [FilePath]-haskellFilesIn dir = do- entries <- sort <$> listDirectory dir- concat <$> traverse below entries- where- below entry = do- let path = dir </> entry- isDir <- doesDirectoryExist path- if isDir- then haskellFilesIn path- else pure [path | ".hs" `isSuffixOf` path] -- | What a module exports, where reading it settles every name. fixitiesThrough :: Resolver -> Text -> IO (Maybe Fixities)
tests/Tilia/Fixity/HiFileSpec.hs view
@@ -83,7 +83,9 @@ shown <- maybe Nothing (parseInterface m) <$> readProgramOutput "ghc" ["--show-iface", path]- let found = [(m, x) | Just s <- [shown], x <- disagreements declared m d s]+ let found = case shown of+ Nothing -> [(m, "ghc --show-iface gave nothing to compare")]+ Just s -> [(m, x) | x <- disagreements declared m d s] found <$ evaluate (length found) _ -> pure [] @@ -138,7 +140,7 @@ byOperator (interfaceDeclares i) <> Set.unions [ byOperator (Map.filterWithKey (\(_, o) _ -> o == op) fixities)- | (from, op) <- interfaceReexports i,+ | (from, _, op) <- interfaceReexports i, from /= m, Just fixities <- [declared from] ]
tests/Tilia/Fixity/InterfaceSpec.hs view
@@ -7,6 +7,7 @@ -- an interface, not a documented one. module Tilia.Fixity.InterfaceSpec (spec) where +import Data.List (nub) import Data.Map.Strict qualified as Map import Data.Set qualified as Set import Data.Text (Text)@@ -77,11 +78,9 @@ ("Data.Aeson.Types.FromJSON", OpName "parseJSON") ] - it "takes a record field, written after a bar" $+ it "takes a record field written after a bar, and not its type" $ passesOn "exports:\n Data.Aeson.Encoding.Internal.Encoding'|{Data.Aeson.Encoding.Internal.fromEncoding}\n"- `shouldBe` [ ("Data.Aeson.Encoding.Internal", OpName "Encoding'"),- ("Data.Aeson.Encoding.Internal", OpName "fromEncoding")- ]+ `shouldBe` [("Data.Aeson.Encoding.Internal", OpName "fromEncoding")] it "keeps a type apart from the module holding it" $ passesOn "exports:\n Data.Aeson.Types.Internal.Value\n"@@ -103,8 +102,7 @@ it "reads them among the members of a type exported without itself" $ passesOn "exports:\n GHC.Internal.Bits.Bits|{GHC.Internal.Bits..&. GHC.Internal.Bits..|. GHC.Internal.Bits.complement}\n"- `shouldBe` [ ("GHC.Internal.Bits", OpName "Bits"),- ("GHC.Internal.Bits", OpName ".&."),+ `shouldBe` [ ("GHC.Internal.Bits", OpName ".&."), ("GHC.Internal.Bits", OpName ".|."), ("GHC.Internal.Bits", OpName "complement") ]@@ -193,6 +191,15 @@ (fromHiFile "M" (HiFile "M" [AvailTC (inM InTypes "T") [inM InTerms "field"]] [])) `shouldBe` Right (Just (Set.fromList [(InTerms, OpName "field")])) + it "passes on the members of a type exported without itself" $+ fmap+ interfaceReexports+ ( fromHiFile+ "M"+ (HiFile "M" [AvailTC (inN InTypes ":<:") [inN InTerms "inj"]] [])+ )+ `shouldBe` Right [("N", InTerms, OpName "inj")]+ describe "a fixity for a name the module does not export" $ do it "is left out of a decoded interface" $ fmap@@ -213,6 +220,9 @@ declares "exports:\n <+>\n parser\nfixities infixl 3 <+>, infixr 2 <~>\n" `shouldBe` [(OpName "<+>", Fixity LeftAssoc 3)] + it "is left out for a type a bar says is exported without itself" $+ declares "exports:\n :<:|{inj}\nfixities infixr 4 :<:\n" `shouldBe` []+ it "is left out where the module passes on another name spelled the same" $ declares "exports:\n GHC.Internal.Base.>>=\nfixities infixr 2 >>=\n" `shouldBe` []@@ -234,7 +244,10 @@ fmap (Map.toList . interfaceDeclares) (parseInterface "Data.Aeson" out) `shouldBe` Just [((InTerms, OpName "!"), Fixity LeftAssoc 9)] fmap interfaceReexports (parseInterface "Data.Aeson" out)- `shouldBe` Just [("Data.Aeson.Types.FromJSON", OpName ".:")]+ `shouldBe` Just+ [ ("Data.Aeson.Types.FromJSON", InTypes, OpName ".:"),+ ("Data.Aeson.Types.FromJSON", InTerms, OpName ".:")+ ] describe "output it will not read" $ do it "refuses what does not name a module at all" $@@ -264,8 +277,14 @@ declaresIn = maybe [] (Map.toList . interfaceDeclares) . parseInterface "M" . (header "M" <>) +-- | What a module of this shape passes on, namespace aside. passesOn :: Text -> [(Text, OpName)]-passesOn = maybe [] interfaceReexports . parseInterface "M" . (header "M" <>)+passesOn =+ nub+ . fmap (\(m, _, op) -> (m, op))+ . maybe [] interfaceReexports+ . parseInterface "M"+ . (header "M" <>) -- | The members of each exported name, in a settled order. members :: Text -> [(OpName, [OpName])]@@ -277,3 +296,7 @@ -- | A name the module @M@ declares. inM :: Namespace -> Text -> HiName inM namespace = HiName "M" namespace . OpName++-- | A name another module, N, declares.+inN :: Namespace -> Text -> HiName+inN namespace = HiName "N" namespace . OpName
tests/Tilia/Fixity/PlanSpec.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-}@@ -22,7 +21,7 @@ import Control.Monad (forM_, when) import Data.ByteString.Lazy qualified as BL import Data.Choice (pattern Do, pattern Don't, pattern Is)-import Data.Foldable (traverse_)+import Data.Foldable (for_, traverse_) import Data.IORef (IORef, modifyIORef', newIORef, readIORef, writeIORef) import Data.List (isInfixOf) import Data.List qualified@@ -32,7 +31,6 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as T-import Data.Text.IO qualified as T import Data.Time (addUTCTime, getCurrentTime) import System.Directory ( createDirectoryIfMissing,@@ -52,7 +50,12 @@ import Tilia.Cabal.Plan qualified as Plan import Tilia.Cpp.Macros (Macros (..)) import Tilia.Fixity-import Tilia.Fixity.Cache (PlanToken (..), openCache, storePackageCache)+import Tilia.Fixity.Cache+ ( PlanToken (..),+ openCache,+ storeFutileFetch,+ storePackageCache,+ ) import Tilia.Fixity.HiFile (HiExport (..), HiFile (..), HiName (..)) import Tilia.Fixity.PackageDb ( Installed (..),@@ -63,8 +66,14 @@ import Tilia.Fixity.Plan import Tilia.Parser import Tilia.Process (readProgramOutput)-import Tilia.TestUtils (parsed, routedResolver, unreadOnly, withEnvironment)-import Tilia.Utils (inParallel)+import Tilia.TestUtils+ ( parsed,+ routedResolver,+ unreadOnly,+ withEnvironment,+ withOpsProject,+ )+import Tilia.Utils (inParallel, writeUtf8File) import Tilia.WithProjectPlan (withProjectPlan) spec :: Spec@@ -202,6 +211,49 @@ `shouldReturn` Right () readIORef steps `shouldReturn` [] + it "does not fetch again what fetching did not bring in" $+ withTempProject (Just wantingATarball) $ \dir -> do+ steps <- newIORef []+ refused <- newIORef []+ let futility =+ undiscoveredFutility+ { fetchWasFutileFor = readIORef refused,+ rememberFutileFetch = writeIORef refused+ }+ missing = SourcesMissing ["tilia-phantom"]+ again = prepareWith (obliging steps) futility [] dir missing+ again `shouldReturn` Right ()+ readIORef refused `shouldReturn` ["tilia-phantom"]+ again `shouldReturn` Right ()+ readIORef steps `shouldReturn` [fetching]++ it "does not fetch after a solve what fetching did not bring in" $+ withTempProject Nothing $ \dir -> do+ steps <- newIORef []+ let cabal args = do+ record steps args+ when (args == solving) (writePlan dir wantingATarball)+ pure (Right ())+ futility =+ undiscoveredFutility{fetchWasFutileFor = pure ["tilia-phantom"]}+ prepareWith cabal futility [] dir PlanMissing `shouldReturn` Right ()+ readIORef steps `shouldReturn` [solving]++ it "takes nothing for futile when told not to download" $+ withTempProject (Just narrowAndWanting) $ \dir -> do+ refused <- newIORef []+ let futility =+ undiscoveredFutility+ { solveWasFutile = pure True,+ rememberFutileFetch = writeIORef refused+ }+ narrow = PlanNarrow ["thing:test:tests"]+ offline = Plan.prepareWith (Do #useCache) (Don't #download)+ steps <- newIORef []+ offline (obliging steps) futility [component "test:tests"] dir narrow+ `shouldReturn` Right ()+ readIORef refused `shouldReturn` []+ it "runs nothing at all when nothing is missing" $ do steps <- newIORef [] prepareWith (obliging steps) undiscoveredFutility [] "." Ready `shouldReturn` Right ()@@ -566,7 +618,7 @@ it "leaves a file+noindex repository's tarballs where they are" $ withSystemTempDirectory "tilia-noindex" $ \repo -> do- T.writeFile (repo </> "thing-1.0.tar.gz") "not really a tarball"+ writeUtf8File (repo </> "thing-1.0.tar.gz") "not really a tarball" withCache [] (fromRepository ("{\"type\":\"local-repo-no-index\",\"path\":\"" <> T.pack repo <> "\"}"))@@ -625,30 +677,37 @@ describe "and where it was remembered" $ do it "is not asked again where the plan's tarballs are there" $- withRemembered True $ \root asked found -> do+ withRemembered True [] $ \root asked found -> do asked `shouldBe` [] found `shouldBe` [root </> "hackage.haskell.org/thing/1.0/thing-1.0.tar.gz"] it "is asked again where a tarball of the plan is not there" $- withRemembered False $ \_ asked _ ->+ withRemembered False [] $ \_ asked _ -> asked `shouldBe` ["path"] + it "is not asked again for a tarball fetching did not bring in" $+ withRemembered False ["thing"] $ \_ asked _ ->+ asked `shouldBe` []+ -- | Find the tarballs of a plan wanting one package from Hackage, where the -- package cache was remembered to be a directory of its own, holding the--- tarball or not, and hand over the directory, the commands the stand-in--- for @cabal@ was given, and where each tarball was found.+-- tarball or not, and a fetch was remembered not to bring in the packages+-- given, and hand over the directory, the commands the stand-in for+-- @cabal@ was given, and where each tarball was found. withRemembered :: Bool ->+ [Text] -> (FilePath -> [String] -> [FilePath] -> Expectation) -> Expectation-withRemembered holding act =+withRemembered holding refused act = withSystemTempDirectory "tilia-remembered" $ \root -> do let at = root </> "hackage.haskell.org" </> "thing" </> "1.0" when holding $ do createDirectoryIfMissing True at- T.writeFile (at </> "thing-1.0.tar.gz") "not really a tarball"+ writeUtf8File (at </> "thing-1.0.tar.gz") "not really a tarball" cache <- openCache (Do #useCache) (PlanToken (T.pack (takeBaseName root))) storePackageCache cache root+ storeFutileFetch cache refused withTempProject (Just (fromRepository hackage)) $ \dir -> readBuildPlan (planPathFor dir) >>= \case Left why -> expectationFailure (T.unpack why)@@ -709,43 +768,14 @@ <> "\",\"pkg-src\":{\"type\":\"repo-tar\",\"repo\":\ \{\"type\":\"secure-repo\",\"uri\":\"http://hackage.haskell.org/\"}}}" --- | A project with one local module declaring @infixl 6 <+>@, and a plan--- for it kept outside of it that names the project relatively, as--- @haskell.nix@ writes it, along with the other entries given.+-- | 'withOpsProject' with the other entries of the plan given, and a cache+-- of its own. withGivenPlan :: [Text] -> (FilePath -> FilePath -> Expectation) -> Expectation withGivenPlan others act =- withSystemTempDirectory "tilia-given" $ \dir -> do- let project = dir </> "project"- planFile = dir </> "elsewhere" </> "plan.json"- createDirectoryIfMissing True (project </> "src")- createDirectoryIfMissing True (takeDirectory planFile)- T.writeFile (project </> "fake.cabal") $- T.unlines- [ "cabal-version: 2.4",- "name: fake",- "version: 0.1.0.0",- "library",- " exposed-modules: Ops",- " hs-source-dirs: src",- " default-language: Haskell2010"- ]- T.writeFile (project </> "src" </> "Ops.hs") $- T.unlines- [ "module Ops ((<+>)) where",- "infixl 6 <+>",- "(<+>) :: a -> a -> a",- "(<+>) = const"- ]- T.writeFile planFile $- "{\"compiler-id\":\"ghc-0.0\",\"install-plan\":["- <> T.intercalate- ","- ( "{\"pkg-name\":\"fake\",\"pkg-version\":\"0.1.0.0\",\- \\"pkg-src\":{\"type\":\"local\",\"path\":\"./.\"}}"- : others- )- <> "]}"- withEnvironment [("XDG_CACHE_HOME", dir </> "cache")] (act project planFile)+ withOpsProject [] others $ \project planFile ->+ withEnvironment+ [("XDG_CACHE_HOME", takeDirectory project </> "cache")]+ (act project planFile) -- | The @cabal@ commands something runs, as told by a stand-in put first -- on the path that fails, and what it came to.@@ -782,7 +812,7 @@ withIndexIn :: FilePath -> IO () withIndexIn root = do createDirectoryIfMissing True (root </> "hackage.haskell.org")- T.writeFile (root </> "hackage.haskell.org" </> "01-index.tar") ""+ writeUtf8File (root </> "hackage.haskell.org" </> "01-index.tar") "" -- | Run something on where @plannedTarballs@ looked, against a package -- cache holding the entries given.@@ -800,7 +830,7 @@ withEnvironment [("CABAL_DIR", cabalDir)] $ withSystemTempDirectory "tilia-repo-plan" $ \dir -> do createDirectoryIfMissing True (takeDirectory (planPathFor dir))- T.writeFile (planPathFor dir) planText+ writeUtf8File (planPathFor dir) planText readBuildPlan (planPathFor dir) >>= \case Left why -> expectationFailure (T.unpack why) Right plan ->@@ -818,7 +848,7 @@ </> T.unpack held </> T.unpack version createDirectoryIfMissing True at- T.writeFile+ writeUtf8File (at </> T.unpack (held <> "-" <> version <> ".tar.gz")) "not really a tarball" @@ -890,7 +920,7 @@ withFakeCheckout clones act = withSystemTempDirectory "tilia-checkout" $ \dir -> do createDirectoryIfMissing True (takeDirectory (planPathFor dir))- T.writeFile (planPathFor dir) fromAGitRepository+ writeUtf8File (planPathFor dir) fromAGitRepository traverse_ (unpack dir) clones withEnvironment [("XDG_CACHE_HOME", dir </> "cache")] (act dir) where@@ -898,8 +928,10 @@ let at = dir </> "dist-newstyle" </> "src" </> named belongsTo = takeWhile (/= '-') named createDirectoryIfMissing True (at </> "src" </> "Private")- T.writeFile (at </> belongsTo <> ".cabal") (describing (T.pack belongsTo) version)- T.writeFile (at </> "src" </> "Private" </> "Ops.hs") privateOps+ writeUtf8File+ (at </> belongsTo <> ".cabal")+ (describing (T.pack belongsTo) version)+ writeUtf8File (at </> "src" </> "Private" </> "Ops.hs") privateOps describing named' version = T.unlines [ "cabal-version: 2.4",@@ -1001,6 +1033,16 @@ it "says nothing about a module given an interface of another" $ do resolver <- newResolverFrom (\_ -> given "Ops") fixitiesOf resolver "Facade" `shouldReturn` Nothing++ it "gives no fixity of a class to a module passing on its methods" $ do+ resolver <- newResolverFrom given+ fixitiesOf resolver "Methods" `shouldReturn` Just Map.empty++ it "copies a fixity passed on in the namespace it is passed on in" $ do+ resolver <- newResolverFrom given+ fixitiesOf resolver "Terms"+ `shouldReturn` Just+ (Map.singleton (InTerms, OpName "+++") (Fixity RightAssoc 5)) where plus = Map.singleton (InTerms, OpName "<+>") (Fixity RightAssoc 5) given m = pure (Map.lookup m interfaces)@@ -1019,8 +1061,49 @@ hiExports = [Avail (HiName "Ops" InTerms (OpName "<+>"))], hiFixities = [] }+ ),+ ( "Classes",+ HiFile+ { hiModule = "Classes",+ hiExports =+ [ AvailTC+ (inClasses InTypes ":<:")+ [inClasses InTypes ":<:", inClasses InTerms "inj"]+ ],+ hiFixities = [(InTypes, OpName ":<:", Fixity RightAssoc 4)]+ }+ ),+ ( "Methods",+ HiFile+ { hiModule = "Methods",+ hiExports =+ [AvailTC (inClasses InTypes ":<:") [inClasses InTerms "inj"]],+ hiFixities = []+ }+ ),+ ( "Both",+ HiFile+ { hiModule = "Both",+ hiExports =+ [ Avail (inBoth InTerms "+++"),+ AvailTC (inBoth InTypes "+++") [inBoth InTypes "+++"]+ ],+ hiFixities =+ [ (InTerms, OpName "+++", Fixity RightAssoc 5),+ (InTypes, OpName "+++", Fixity LeftAssoc 6)+ ]+ }+ ),+ ( "Terms",+ HiFile+ { hiModule = "Terms",+ hiExports = [Avail (inBoth InTerms "+++")],+ hiFixities = []+ } ) ]+ inClasses namespace = HiName "Classes" namespace . OpName+ inBoth namespace = HiName "Both" namespace . OpName -- | A module of the project's own, which an earlier run has read. readBefore :: Spec@@ -1035,7 +1118,8 @@ it "is read again once its text has changed" $ withFakePlan [("src/Ops.hs", declaring "infixl 6")] $ \plan -> do _ <- newResolver plan >>= (`fixitiesOf` "Ops")- traverse_ (\dir -> T.writeFile (dir </> "src" </> "Ops.hs") (declaring "infixr 2")) (localDirsOf plan)+ for_ (localDirsOf plan) $ \dir ->+ writeUtf8File (dir </> "src" </> "Ops.hs") (declaring "infixr 2") newResolver plan >>= (`fixitiesOf` "Ops") >>= (`shouldBe` Just (Map.singleton (InTerms, OpName "<+>") (Fixity RightAssoc 2)))@@ -1860,7 +1944,7 @@ act dir writePlan :: FilePath -> Text -> IO ()-writePlan dir = T.writeFile (planPathFor dir)+writePlan dir = writeUtf8File (planPathFor dir) -- | A project whose @.cabal@ file changed after its plan was written, under -- a plan of its own, so that what it leaves in the cache is its own.@@ -1875,7 +1959,7 @@ \\"pkg-src\":{\"type\":\"local\",\"path\":\"/nowhere\"}}]}" now <- getCurrentTime setModificationTime (planPathFor dir) (addUTCTime (-60) now)- T.writeFile (dir </> "thing.cabal") ""+ writeUtf8File (dir </> "thing.cabal") "" setModificationTime (dir </> "thing.cabal") (addUTCTime (-30) now) act dir @@ -2056,7 +2140,7 @@ if any ((".cabal" `Data.List.isSuffixOf`) . fst) sources then pure () else- T.writeFile (dir </> "fake.cabal") $+ writeUtf8File (dir </> "fake.cabal") $ T.unlines [ "cabal-version: 2.4", "name: fake",@@ -2066,8 +2150,8 @@ " hs-source-dirs: src", " default-language: Haskell2010" ]- traverse_ (\(path, text) -> T.writeFile (dir </> path) text) sources- T.writeFile (dir </> "plan.json") $+ traverse_ (\(path, text) -> writeUtf8File (dir </> path) text) sources+ writeUtf8File (dir </> "plan.json") $ "{\"compiler-id\":\"ghc-0.0\",\"install-plan\":\ \[{\"pkg-name\":\"fake\",\"pkg-version\":\"0.1.0.0\",\ \\"pkg-src\":{\"type\":\"local\",\"path\":\""@@ -2104,7 +2188,7 @@ writeTarball tarball $ (held </> T.unpack name <> ".cabal", cabal) : [(held </> path, text) | (path, text) <- sources]- T.writeFile (dir </> "plan.json") plan+ writeUtf8File (dir </> "plan.json") plan withEnvironment [("CABAL_DIR", dir), ("XDG_CACHE_HOME", dir </> "cache")] $ readBuildPlan (dir </> "plan.json") >>= \case Left why -> error (T.unpack why)
tests/Tilia/FixitySpec.hs view
@@ -20,7 +20,7 @@ import Tilia.Fixity import Tilia.Parser import Tilia.Span (spanStartColumn, spanStartLine)-import Tilia.TestUtils (parsed, unreadOnly)+import Tilia.TestUtils (inBothNamespaces, parsed, unreadOnly) spec :: Spec spec = do@@ -143,6 +143,10 @@ fmap snd (unsettledIn "module M where\nimport Terms\nimport Other.Terms\nf a b = a :> b\n") `shouldBe` [Ambiguous termsDisagreeing] + it "declines a type that writes a constructor they disagree about" $+ fmap snd (unsettledIn "module M where\nimport Terms\nimport Other.Terms\ntype T = Int :> Int\n")+ `shouldBe` [Ambiguous termsDisagreeing]+ describe "what a module says it exports" $ do it "has nothing to say about a module with no export list" $ exportsOfSource "module M where\nf = 1\n" `shouldBe` Nothing@@ -241,6 +245,22 @@ it "passes over every one for a name the module defines itself" $ lookupFixity (fullScope definesItsOwn) InTerms Nothing (OpName "clamp") `shouldBe` Resolved defaultFixity ReportDefault++ it "passes over every one for a name an as-pattern binds" $+ lookupFixity+ (fullScope "module M where\nimport Opaque\nplus@_ = (+)\n")+ InTerms+ Nothing+ (OpName "plus")+ `shouldBe` Resolved defaultFixity ReportDefault++ it "still blames one for a name bound inside the function of a view pattern" $+ lookupFixity+ (fullScope "{-# LANGUAGE ViewPatterns #-}\nmodule M where\nimport Opaque\n((\\clamp -> clamp) -> n) = 1\n")+ InTerms+ Nothing+ (OpName "clamp")+ `shouldBe` Unresolved (unreadOnly "Opaque") it "passes over every one for a use under the module's own name" $ lookupFixity (fullScope definesItsOwn) InTerms (Just "M") (OpName "clamp")
tests/Tilia/IgnoreSpec.hs view
@@ -5,12 +5,11 @@ import Data.Map.Strict qualified as Map import Data.Text (Text)-import Data.Text.IO qualified as T-import System.Directory (canonicalizePath, createDirectoryIfMissing) import System.FilePath (splitDirectories, takeDirectory, (</>))-import System.IO.Temp (withSystemTempDirectory) import Test.Hspec import Tilia.Ignore+import Tilia.TestUtils (underAsciiLocale, withFiles)+import Tilia.Utils (writeUtf8File) spec :: Spec spec = do@@ -91,6 +90,14 @@ isIgnoredInProject root (root </> "src" </> "inner" </> ".." </> "Generated.hs") `shouldReturn` True isIgnoredInProject root (root </> "src" </> "New.hs") `shouldReturn` False + it "is ignored by a .tiliaignore that is not ASCII under any locale" $+ withProject $ \root -> do+ writeUtf8File (root </> ".tiliaignore") $+ "# generated code — do not format\nsrc/Generated.hs\n"+ let generated = root </> "src" </> "Generated.hs"+ underAsciiLocale (isIgnoredInProject root generated)+ `shouldReturn` True+ it "is not ignored when it is outside of the project" $ withProject $ \root -> isIgnoredInProject root (takeDirectory root </> "Generated.hs") `shouldReturn` False@@ -98,16 +105,12 @@ -- | A project with @src/Generated.hs@, which its root's @.tiliaignore@ -- ignores, and a @.tiliaignore@ in @src/inner@ ignoring boot files. withProject :: (FilePath -> Expectation) -> Expectation-withProject act =- withSystemTempDirectory "tilia-ignore" $ \dir -> do- root <- canonicalizePath dir- let write path text = do- createDirectoryIfMissing True (takeDirectory (root </> path))- T.writeFile (root </> path) text- write ".tiliaignore" "src/Generated.hs\n"- write ("src" </> "Generated.hs") "module Generated where\n"- write ("src" </> "inner" </> ".tiliaignore") "*.hs-boot\n"- act root+withProject =+ withFiles+ [ (".tiliaignore", "src/Generated.hs\n"),+ ("src" </> "Generated.hs", "module Generated where\n"),+ ("src" </> "inner" </> ".tiliaignore", "*.hs-boot\n")+ ] -- | Check which of the paths a single ignore file at the root ignores. ignores :: Text -> [FilePath] -> [FilePath] -> Expectation
tests/Tilia/PragmaSpec.hs view
@@ -3,10 +3,27 @@ -- | What a module's pragmas say, read off the text. module Tilia.PragmaSpec (spec) where +import Data.Map.Strict (Map)+import Data.Set qualified as Set import Data.Text (Text)+import Data.Text qualified as T+import GHC.Data.EnumSet qualified as EnumSet+import GHC.Driver.Session+ ( Language (..),+ OnOff (..),+ extensionFlags,+ fLangFlags,+ flagSpecName,+ glasgowExtsFlags,+ languageExtensions,+ xFlags,+ ) import GHC.LanguageExtensions.Type (Extension (..)) import Test.Hspec+import Test.Hspec.QuickCheck (modifyMaxSuccess)+import Test.QuickCheck import Tilia.Pragma+import Tilia.Pragma.Ghc (InForce (..), inForceAfter, readFlags) spec :: Spec spec = do@@ -19,7 +36,10 @@ ("one written without spaces", "{-#LINE 1 \"Other.hs\"#-}\n"), ("one written over several lines", "{-# LINE 1\n \"Other.hs\" #-}\n"), ("one below the header", "module M where\nx = 1\n{-# LINE 9 \"O.hs\" #-}\n"),- ("one among pragmas that do not move anything", langThenLine)+ ("one among pragmas that do not move anything", langThenLine),+ ( "one after a character literal holding a double quote",+ "module M where\nq = '\"'\n{-# LINE 9 \"O.hs\" #-}\n"+ ) ] (\source -> movesPositions source `shouldBe` True) @@ -30,51 +50,167 @@ ("an INLINE pragma", "module M where\n{-# INLINE f #-}\nf = id\n"), ("a pragma whose name merely starts with one", "{-# LINEAR 1 #-}\n"), ("an unclosed pragma", "{-# LINE 1 \"Other.hs\"\n"),- ("the word in a comment", "-- {-* LINE 1 *-}\nmodule M where\n")+ ("the word in a comment", "-- {-* LINE 1 *-}\nmodule M where\n"),+ ("one in a string", "module M where\nx = \"{-# LINE 1 #-}\"\n"),+ ( "one in a string after a character literal holding a double quote",+ "module M where\nq = '\"'\nx = \"{-# LINE 1 #-}\"\n"+ ),+ ( "one in a string after a character literal holding an escaped one",+ "module M where\nq = '\\\"'\nx = \"{-# LINE 1 #-}\"\n"+ ),+ ( "one in a string after a name ending in a prime",+ "module M where\nx = f'\"'\"\ny = \"{-# LINE 1 #-}\"\n"+ ) ] (\source -> movesPositions source `shouldBe` False) describe "the extensions in force" $ do it "keeps what the package puts in force" $- effectiveExtensions [ImplicitPrelude, GADTs] "module M where\n"+ inForceUnder [ImplicitPrelude, GADTs] "module M where\n" `shouldBe` [ImplicitPrelude, GADTs] it "reads one extension" $- effectiveExtensions [] "{-# LANGUAGE BangPatterns #-}\nmodule M where\n"+ inForceUnder [] "{-# LANGUAGE BangPatterns #-}\nmodule M where\n" `shouldBe` [BangPatterns] it "reads several from one pragma" $- effectiveExtensions+ inForceUnder []- "{-# LANGUAGE GADTs, RankNTypes #-}\nmodule M where\n"- `shouldBe` [GADTs, RankNTypes]+ "{-# LANGUAGE LambdaCase, MagicHash #-}\nmodule M where\n"+ `shouldBe` [LambdaCase, MagicHash] it "reads several pragmas" $- effectiveExtensions+ inForceUnder []- "{-# LANGUAGE GADTs #-}\n{-# LANGUAGE MagicHash #-}\n"- `shouldBe` [GADTs, MagicHash]- it "ignores other pragmas" $+ "{-# LANGUAGE LambdaCase #-}\n{-# LANGUAGE MagicHash #-}\n"+ `shouldBe` [LambdaCase, MagicHash]+ it "ignores flags that turn on no extension" $+ inForceUnder+ []+ "{-# OPTIONS_GHC -Wall #-}\n{-# LANGUAGE LambdaCase #-}\n"+ `shouldBe` [LambdaCase]+ it "reads the -X flags of OPTIONS_GHC" $+ inForceUnder+ [GADTs]+ "{-# OPTIONS_GHC -Wall -XLambdaCase -XNoGADTs #-}\nmodule M where\n"+ `shouldBe` [LambdaCase]+ it "reads the flags of OPTIONS" $+ inForceUnder [] "{-# OPTIONS -XLambdaCase #-}\n"+ `shouldBe` [LambdaCase]+ it "reads the flags of an options pragma as GHC splits them" $+ inForceUnder [] "{-# OPTIONS_GHC \"-XLambdaCase\" #-}\n"+ `shouldBe` [LambdaCase]+ it "reads -cpp as turning on CPP" $+ inForceUnder [] "{-# OPTIONS_GHC -cpp #-}\n"+ `shouldBe` [Cpp]+ it "reads the old -f spellings of extensions" $+ inForceUnder+ [ImplicitPrelude]+ "{-# OPTIONS_GHC -fbang-patterns -fno-implicit-prelude #-}\n"+ `shouldBe` [BangPatterns]+ it "reads -fglasgow-exts as turning on what it stands for" $+ inForceUnder [] "{-# OPTIONS_GHC -fglasgow-exts #-}\n"+ `shouldSatisfy` \es -> all (`elem` es) glasgowExtsFlags+ it "reads -fno-glasgow-exts as turning that off again" $+ inForceUnder+ []+ "{-# OPTIONS_GHC -fglasgow-exts -fno-glasgow-exts #-}\n"+ `shouldSatisfy` not . any (`elem` glasgowExtsFlags)+ it "reads the pragmas in the order they are written" $+ inForceUnder+ []+ ( "{-# LANGUAGE LambdaCase #-}\n"+ <> "{-# OPTIONS_GHC -XNoLambdaCase -XMagicHash #-}\n"+ <> "{-# LANGUAGE NoMagicHash #-}\n"+ )+ `shouldBe` []+ it "reads the names of a language pragma past the comments in it" $+ inForceUnder+ []+ "{-# LANGUAGE\n LambdaCase -- for f\n , {- c -} MagicHash\n #-}\nmodule M where\n"+ `shouldBe` [LambdaCase, MagicHash]+ it "ignores a pragma below the module line, which GHC does not read" $+ inForceUnder [] "module M where\n{-# LANGUAGE LambdaCase #-}\n"+ `shouldBe` []+ it "reads the pragmas between the directives of a header" $+ inForceUnder+ []+ "{-# LANGUAGE CPP #-}\n#if 1\n{-# LANGUAGE LambdaCase #-}\n#endif\nmodule M where\n"+ `shouldBe` [Cpp, LambdaCase]+ it "takes an edition the module names in place of the component's" $+ inForceUnder+ (languageExtensions (Just Haskell2010))+ "{-# LANGUAGE GHC2024 #-}\nmodule M where\n"+ `shouldBe` languageExtensions (Just GHC2024)+ it "keeps what the component switches on top of the module's edition" $ effectiveExtensions+ (defaultsOf (languageExtensions (Just Haskell2010)) [On OverloadedStrings])+ "{-# LANGUAGE GHC2021 #-}\nmodule M where\n"+ `shouldSatisfy` \es -> all (`elem` es) [OverloadedStrings, ImportQualifiedPost]+ it "switches what the module switches on top of its edition, wherever named" $+ inForceUnder []- "{-# OPTIONS_GHC -Wall #-}\n{-# LANGUAGE GADTs #-}\n"- `shouldBe` [GADTs]+ "{-# LANGUAGE NoLambdaCase #-}\n{-# LANGUAGE GHC2024 #-}\nmodule M where\n"+ `shouldSatisfy` notElem LambdaCase+ it "takes the edition named last" $+ inForceUnder+ []+ "{-# LANGUAGE GHC2024 #-}\n{-# LANGUAGE Haskell2010 #-}\nmodule M where\n"+ `shouldBe` languageExtensions (Just Haskell2010)+ it "reads an edition from a -X flag" $+ inForceUnder [] "{-# OPTIONS_GHC -XGHC2024 #-}\nmodule M where\n"+ `shouldBe` languageExtensions (Just GHC2024)+ it "ignores the options of other compilers" $+ inForceUnder [] "{-# OPTIONS_JHC -fffi -cpp #-}\n"+ `shouldBe` [] it "ignores an unknown extension rather than failing" $- effectiveExtensions [] "{-# LANGUAGE GADTs, NotARealExtension #-}\n"- `shouldBe` [GADTs]+ inForceUnder [] "{-# LANGUAGE LambdaCase, NotARealExtension #-}\n"+ `shouldBe` [LambdaCase] it "treats a No-prefix as turning one off" $- effectiveExtensions+ inForceUnder [ImplicitPrelude] "{-# LANGUAGE NoImplicitPrelude #-}\n" `shouldBe` [] it "lets a module refuse what its package put in force" $- effectiveExtensions [ImplicitPrelude, GADTs] "{-# LANGUAGE NoGADTs #-}\n"+ inForceUnder [ImplicitPrelude, GADTs] "{-# LANGUAGE NoGADTs #-}\n" `shouldBe` [ImplicitPrelude] it "lets a module take back a Prelude its package turned off" $- effectiveExtensions [GADTs] "{-# LANGUAGE ImplicitPrelude #-}\n"+ inForceUnder [GADTs] "{-# LANGUAGE ImplicitPrelude #-}\n" `shouldBe` [GADTs, ImplicitPrelude] it "does not repeat an extension named twice" $- effectiveExtensions+ inForceUnder []- "{-# LANGUAGE GADTs #-}\n{-# LANGUAGE GADTs #-}\n"- `shouldBe` [GADTs]+ "{-# LANGUAGE LambdaCase #-}\n{-# LANGUAGE LambdaCase #-}\n"+ `shouldBe` [LambdaCase]+ it "turns on what an extension implies, as GHC does" $+ inForceUnder [] "{-# LANGUAGE RankNTypes #-}\n"+ `shouldBe` [RankNTypes, ExplicitForAll]+ it "turns off what an extension implies is off" $+ inForceUnder+ [ImplicitPrelude]+ "{-# LANGUAGE RebindableSyntax #-}\n"+ `shouldBe` [RebindableSyntax]+ it "leaves on what an extension turned off had implied" $+ inForceUnder [] "{-# LANGUAGE RankNTypes, NoRankNTypes #-}\n"+ `shouldBe` [ExplicitForAll]+ it "lets a later name turn off what an earlier one implied" $+ inForceUnder [] "{-# LANGUAGE RankNTypes, NoExplicitForAll #-}\n"+ `shouldBe` [RankNTypes] + modifyMaxSuccess (const 5000) . describe "against GHC's own flag reader" $ do+ it "leaves on what GHC leaves on" $+ forAll (listOf1 (pragmaOf headerNames)) $ \pragmas ->+ let ours =+ effectiveExtensions+ (defaultsOf (languageExtensions Nothing) [])+ (headerOf pragmas)+ theirs =+ EnumSet.toList (extensionFlags (readFlags (concatMap flagsOf pragmas)))+ in counterexample (T.unpack (headerOf pragmas)) $+ Set.fromList ours === Set.fromList theirs+ it "takes two orders to be alike where GHC switches alike" $+ forAll (listOf1 (pragmaOf extensionNames)) $ \pragmas ->+ forAll (shuffle pragmas) $ \shuffled ->+ counterexample (T.unpack (headerOf pragmas <> headerOf shuffled)) $+ sameInForce (fmap namedBody pragmas) (fmap namedBody shuffled)+ === (ghcSwitches pragmas == ghcSwitches shuffled)+ describe "lookupExtension" $ do it "knows an extension by the name one writes" $ lookupExtension "LambdaCase" `shouldBe` Just LambdaCase@@ -87,3 +223,56 @@ langThenLine :: Text langThenLine = "{-# LANGUAGE LambdaCase #-}\nmodule M where\n{-# LINE 3 \"O.hs\" #-}\n"++-- | The extensions in force in a module of a component whose edition puts+-- these in force and which switches nothing itself.+inForceUnder :: [Extension] -> Text -> [Extension]+inForceUnder edition = effectiveExtensions (defaultsOf edition [])++-- | A pragma of a header, as its name and its body, with the flags it hands+-- GHC.+type HeaderPragma = (Text, Text, [Text])++-- | A pragma that names one of these, or spells an extension as an old flag.+pragmaOf :: [Text] -> Gen HeaderPragma+pragmaOf names = oneof [language, options]+ where+ language = do+ name <- elements names+ pure ("LANGUAGE", name, ["-X" <> name])+ options = do+ flag <- elements oldFlags+ pure ("OPTIONS_GHC", flag, [flag])+ oldFlags =+ ["-cpp", "-fglasgow-exts", "-fno-glasgow-exts"]+ <> concat+ [ ["-f" <> name, "-fno-" <> name]+ | name <- T.pack . flagSpecName <$> fLangFlags+ ]++-- | Every name of an extension, with @No@ in front and without.+extensionNames :: [Text]+extensionNames = names <> fmap ("No" <>) names+ where+ names = T.pack . flagSpecName <$> xFlags++-- | Every name a @LANGUAGE@ pragma can hold: the extensions and the editions.+headerNames :: [Text]+headerNames =+ extensionNames <> [T.pack (show l) | l <- [minBound .. maxBound :: Language]]++-- | A module of nothing but a header of these pragmas.+headerOf :: [HeaderPragma] -> Text+headerOf pragmas =+ T.concat ["{-# " <> n <> " " <> b <> " #-}\n" | (n, b, _) <- pragmas]+ <> "module M where\n"++flagsOf :: HeaderPragma -> [Text]+flagsOf (_, _, flags) = flags++namedBody :: HeaderPragma -> (Text, Text)+namedBody (n, b, _) = (n, b)++-- | How GHC takes these pragmas to switch each extension.+ghcSwitches :: [HeaderPragma] -> Map Extension Bool+ghcSwitches = inForceSwitches . inForceAfter . concatMap flagsOf
tests/Tilia/RunSpec.hs view
@@ -30,7 +30,7 @@ import Tilia.Format (FormatError (..), formatErrorExitCode, refused) import Tilia.Palette (Color (Bad), Palette (..)) import Tilia.Run-import Tilia.Utils (inParallel, lineWidth, visibleLength, wrapTo)+import Tilia.Utils (inParallel, lineWidth, visibleLength, wrapTo, writeUtf8File) spec :: Spec spec = do@@ -52,9 +52,10 @@ NoProject "A.hs", NoBuildPlan "." "cabal said no", NotEquivalent "A.hs" "f = 1 became f = 2",- NotIdempotent "A.hs" "line 12 differs"+ NotIdempotent "A.hs" "line 12 differs",+ Threw "A.hs" "boom" ]- `shouldBe` [False, False, False, False, False, False]+ `shouldBe` [False, False, False, False, False, False, False] describe "what became of a file" $ do it "counts a rewrite as a difference and nothing else" $@@ -453,6 +454,10 @@ it "copes with having nothing to do" $ inParallel pure ([] :: [Int]) `shouldReturn` [] + it "passes on what one of them throws rather than waiting for it" $+ inParallel (\n -> if n == 3 then error "boom" else pure n) [1 .. 8 :: Int]+ `shouldThrow` errorCall "boom"+ it "really does run them at once" $ do capabilities <- getNumCapabilities let rounds = ceiling (20 / fromIntegral capabilities :: Double) :: Int@@ -479,7 +484,7 @@ withSource contents act = withSystemTempDirectory "tilia-run" $ \directory -> do let path = directory </> "A.hs"- T.writeFile path contents+ writeUtf8File path contents act path -- | The same, for a file that has to hold exactly these bytes.
+ tests/Tilia/SpanSpec.hs view
@@ -0,0 +1,33 @@+-- | The span algebra.+module Tilia.SpanSpec (spec) where++import Test.Hspec+import Test.QuickCheck+import Tilia.Gen (AnySpan (..))+import Tilia.Span++spec :: Spec+spec = do+ it "recognises a single-line span" $+ isSingleLine (mkSpan (3, 1) (3, 40)) `shouldBe` True+ it "recognises a multi-line span" $+ isSingleLine (mkSpan (3, 1) (4, 1)) `shouldBe` False+ it "unions to cover both operands" $+ mkSpan (1, 5) (1, 9) <> mkSpan (3, 2) (4, 1)+ `shouldBe` mkSpan (1, 5) (4, 1)+ it "takes the earlier start column when starts share a line" $+ mkSpan (1, 9) (1, 20) <> mkSpan (1, 3) (1, 5)+ `shouldBe` mkSpan (1, 3) (1, 20)++ it "unions associatively" $+ property $ \(AnySpan a) (AnySpan b) (AnySpan c) ->+ (a <> b) <> c === a <> (b <> c)++ it "unions to something covering both operands" $+ property $ \(AnySpan a) (AnySpan b) ->+ let u = a <> b+ in counterexample (show u) (covers u a && covers u b)++ it "is idempotent under union" $+ property $+ \(AnySpan a) -> a <> a === a
tests/Tilia/TestConfig.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-}@@ -16,7 +15,6 @@ import Data.Map.Strict qualified as Map import Data.Set qualified as Set import Data.Text (Text)-import GHC.LanguageExtensions.Type (Extension) import Tilia.BootFixities (bootFixities) import Tilia.Cpp (CppError, everyBranch, formatWithCpp) import Tilia.Doc (printDoc)@@ -30,19 +28,19 @@ Provenance (..), Reach (..), Scope (..),- inBothNamespaces, operatorsUsed, resolveScope, unreadable, ) import Tilia.Parser (ParsedModule (..))+import Tilia.Pragma (Defaults) import Tilia.Render (RenderConfig (..), defaultRenderConfig, renderModule)-import Tilia.TestUtils (effectiveExtensionsFor, parserConfigFor)+import Tilia.TestUtils (effectiveExtensionsFor, inBothNamespaces, parserConfigFor) -- | How to format one corpus example. exampleRenderConfig :: -- | What the package around it puts in force, if it is a module of one.- Maybe [Extension] ->+ Maybe Defaults -> Text -> -- | Its configurations, parsed. NonEmpty ParsedModule ->@@ -81,7 +79,7 @@ -- | Print a corpus example that does not use the preprocessor. printExample :: -- | What the package around it puts in force, if it is a module of one.- Maybe [Extension] ->+ Maybe Defaults -> Text -> -- | It, parsed. ParsedModule ->@@ -93,7 +91,7 @@ -- | Format a corpus example that uses the preprocessor. formatCppExample :: -- | What the package around it puts in force, if it is a module of one.- Maybe [Extension] ->+ Maybe Defaults -> FilePath -> Text -> Either CppError Text
tests/Tilia/TestUtils.hs view
@@ -1,16 +1,23 @@-{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} -- | Helpers several specs share. module Tilia.TestUtils ( withEnvironment,+ withFiles,+ withOpsProject,+ switchedOn,+ underAsciiLocale, parserConfigFor, defaultParserConfig, effectiveExtensionsFor, parsed,+ parses, commentsIn,+ haskellFilesIn, unreadOnly,+ inBothNamespaces, fingerprint, routedResolver, @@ -28,17 +35,30 @@ import Data.Bits (xor) import Data.Char (ord) import Data.Choice (pattern Do)-import Data.Foldable (traverse_)+import Data.Either (isRight)+import Data.Foldable (for_, traverse_)+import Data.List (isSuffixOf, sort) import Data.List.NonEmpty (NonEmpty (..))+import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map import Data.Maybe (fromMaybe, isJust, listToMaybe) import Data.Text (Text) import Data.Text qualified as T import Data.Word (Word64) import GHC.Driver.Session qualified as GHC+import GHC.IO.Encoding (getLocaleEncoding, setLocaleEncoding) import GHC.LanguageExtensions.Type (Extension (..))+import System.Directory+ ( canonicalizePath,+ createDirectoryIfMissing,+ doesDirectoryExist,+ listDirectory,+ ) import System.Environment (lookupEnv, setEnv, unsetEnv)-import Tilia.Cabal.Package (newPackageReader)+import System.FilePath (takeBaseName, takeDirectory, takeExtension, (</>))+import System.IO (mkTextEncoding)+import System.IO.Temp (withSystemTempDirectory)+import Tilia.Cabal.Package (newPackageReader, sourceExtensions) import Tilia.Cabal.Plan (BuildPlan, plannedCompiler) import Tilia.Comments (Comment) import Tilia.Cpp.Directives@@ -52,11 +72,11 @@ implied, nestedIn, readConditionals,- resolved, scanConditionals, unconditionalErrors,+ withoutOpaque, )-import Tilia.Fixity (ModuleChain (..))+import Tilia.Fixity (Fixities, Fixity, ModuleChain (..), Namespace (..), OpName) import Tilia.Fixity.Plan (Resolver, Route, newResolverVia) import Tilia.Parser ( ParsedModule (..),@@ -64,8 +84,9 @@ describeParseError, parseModule, )-import Tilia.Pragma (effectiveExtensions)+import Tilia.Pragma (Defaults, defaultsOf, effectiveExtensions) import Tilia.Source (comments)+import Tilia.Utils (quietly, writeUtf8File) -- | Run something with the given variables set, and the environment as it -- was afterwards however it turns out.@@ -79,11 +100,90 @@ pure (key, was) restore = traverse_ (\(key, was) -> maybe (unsetEnv key) (setEnv key) was) +-- | Run something on a new temporary directory, by its canonical path,+-- holding these files at their paths in it.+withFiles :: [(FilePath, Text)] -> (FilePath -> IO a) -> IO a+withFiles files act =+ withSystemTempDirectory "tilia" $ \dir -> do+ root <- canonicalizePath dir+ for_ files $ \(path, text) -> do+ createDirectoryIfMissing True (takeDirectory (root </> path))+ writeUtf8File (root </> path) text+ act root++-- | Run something on a project of one package, @fake@, whose module @Ops@+-- declares @infixl 6 <+>@, and on a plan for it kept outside it that names+-- the project relatively, as @haskell.nix@ writes it.+withOpsProject ::+ -- | Further files of the project, by their paths in it, each @.hs@ file+ -- one more module of the package.+ [(FilePath, Text)] ->+ -- | Further entries of the plan.+ [Text] ->+ -- | Given the project's directory and the plan's path.+ (FilePath -> FilePath -> IO a) ->+ IO a+withOpsProject files others act =+ withFiles+ ( [ ("project" </> "fake.cabal", cabal),+ ("project" </> "src" </> "Ops.hs", ops),+ ("elsewhere" </> "plan.json", plan)+ ]+ <> [("project" </> path, text) | (path, text) <- files]+ )+ $ \dir -> act (dir </> "project") (dir </> "elsewhere" </> "plan.json")+ where+ cabal =+ T.unlines+ [ "cabal-version: 2.4",+ "name: fake",+ "version: 0.1.0.0",+ "library",+ " exposed-modules: " <> T.intercalate ", " ("Ops" : modules),+ " hs-source-dirs: src",+ " default-language: Haskell2010"+ ]+ modules =+ [ T.pack (takeBaseName path)+ | (path, _) <- files,+ takeExtension path == ".hs"+ ]+ ops =+ T.unlines+ [ "module Ops ((<+>)) where",+ "infixl 6 <+>",+ "(<+>) :: a -> a -> a",+ "(<+>) = const"+ ]+ plan =+ "{\"compiler-id\":\"ghc-0.0\",\"install-plan\":["+ <> T.intercalate+ ","+ ( "{\"pkg-name\":\"fake\",\"pkg-version\":\"0.1.0.0\",\+ \\"pkg-src\":{\"type\":\"local\",\"path\":\"./.\"}}"+ : others+ )+ <> "]}"++-- | Is an environment variable set to anything but empty, @0@, @no@ or+-- @false@?+switchedOn :: String -> IO Bool+switchedOn name =+ maybe False (`notElem` ["", "0", "no", "false"]) <$> lookupEnv name++-- | Run something with files opened in ASCII, as under @LANG=C@, and the+-- locale's encoding as it was afterwards however it turns out.+underAsciiLocale :: IO a -> IO a+underAsciiLocale act = do+ ascii <- mkTextEncoding "ASCII"+ bracket getLocaleEncoding setLocaleEncoding $ \_ ->+ setLocaleEncoding ascii >> act+ -- | What to parse a module with, given what its package puts in force, or -- 'Nothing' for a module of no package, which is read as GHC2021.-parserConfigFor :: Maybe [Extension] -> ParserConfig+parserConfigFor :: Maybe Defaults -> ParserConfig parserConfigFor =- ParserConfig . fromMaybe (GHC.languageExtensions (Just GHC.GHC2021))+ ParserConfig . fromMaybe (defaultsOf (GHC.languageExtensions (Just GHC.GHC2021)) []) -- | What to parse a module of no package with. defaultParserConfig :: ParserConfig@@ -96,8 +196,8 @@ -- That is less than 'parserConfigFor' reads such a module with, so that -- what is printed, @import qualified@ for one, reads the same under every -- edition.-effectiveExtensionsFor :: Maybe [Extension] -> Text -> [Extension]-effectiveExtensionsFor = effectiveExtensions . fromMaybe [ImplicitPrelude]+effectiveExtensionsFor :: Maybe Defaults -> Text -> [Extension]+effectiveExtensionsFor = effectiveExtensions . fromMaybe (defaultsOf [ImplicitPrelude] []) -- | Parse a module, or stop the test saying why it does not parse. parsed :: Text -> ParsedModule@@ -105,6 +205,20 @@ Left problem -> error (T.unpack (describeParseError problem)) Right m -> m +-- | Does a module parse, with what its own pragmas turn on?+parses :: Text -> Bool+parses = isRight . parseModule defaultParserConfig "M.hs"++-- | Every file of Haskell source at or under a path, in order.+haskellFilesIn :: FilePath -> IO [FilePath]+haskellFilesIn path = do+ isDir <- doesDirectoryExist path+ if not isDir+ then pure [path | any (`isSuffixOf` path) sourceExtensions]+ else do+ entries <- sort <$> quietly [] (listDirectory path)+ concat <$> traverse (haskellFilesIn . (path </>)) entries+ -- | Every comment in a module. commentsIn :: Text -> [Comment] commentsIn = comments . pmSource . parsed@@ -114,6 +228,15 @@ unreadOnly :: Text -> NonEmpty ModuleChain unreadOnly m = ModuleChain (m :| []) :| [] +-- | Take fixities that say nothing about namespaces to govern both.+inBothNamespaces :: Map OpName Fixity -> Fixities+inBothNamespaces declared =+ Map.fromList+ [ ((namespace, op), fixity)+ | (op, fixity) <- Map.toList declared,+ namespace <- [InTypes, InTerms]+ ]+ -- | FNV-1a, a hash of a string that is the same on every machine and every -- run. fingerprint :: String -> Word64@@ -125,7 +248,7 @@ -- it reads. routedResolver :: [Route] -> BuildPlan -> IO Resolver routedResolver routes plan = do- packageReader <- newPackageReader (plannedCompiler plan)+ packageReader <- newPackageReader (plannedCompiler plan) [] newResolverVia (Do #useCache) routes packageReader plan -- | Split a module on its first outermost conditional, and on every other@@ -187,8 +310,7 @@ fmap (filter (isJust . implied . Map.toList . fst)) . go Map.empty where go answers source = case configurations source of- Nothing | not (null (unconditionalErrors source)) -> Right []- Nothing -> (\t -> [(answers, t)]) <$> resolved source+ Nothing -> answeredLeaf answers source Just c -> concat <$> traverse@@ -202,21 +324,30 @@ fmap (filter (isJust . implied . Map.toList . fst)) . go Map.empty where go answers source = case configurations source of- Nothing -> (\t -> [(answers, t)]) <$> resolved source+ Nothing -> answeredLeaf answers source Just c -> case zip [0 ..] (cfgTexts c) of [] -> Right [] (i, first) : rest -> (<>) <$> go (Map.insert (cfgGuards c) i answers) first- <*> traverse (held answers (cfgGuards c)) rest+ <*> (concat <$> traverse (held answers (cfgGuards c)) rest) where held before gs (i, t) = answeredBaseline (Map.insert gs i before) t -- | The configuration in which every question still to be asked takes its--- first branch, and the answers that gives.-answeredBaseline :: Assignment -> Text -> Either CppError (Assignment, Text)+-- first branch, and the answers that give it, unless it aborts with+-- @#error@.+answeredBaseline :: Assignment -> Text -> Either CppError [(Assignment, Text)] answeredBaseline answers source = case configurations source of- Nothing -> (answers,) <$> resolved source+ Nothing -> answeredLeaf answers source Just c -> case cfgTexts c of- [] -> Right (answers, source)+ [] -> Right [(answers, source)] t : _ -> answeredBaseline (Map.insert (cfgGuards c) 0 answers) t++-- | A module with every conditional answered, and the answers that reach+-- it, unless it aborts with @#error@.+answeredLeaf :: Assignment -> Text -> Either CppError [(Assignment, Text)]+answeredLeaf answers source+ | null (unconditionalErrors source) =+ [(answers, withoutOpaque source)] <$ readConditionals source+ | otherwise = Right []
tilia.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: tilia-version: 0.1.2.0+version: 0.1.3.0 license: BSD-3-Clause license-file: LICENSE.md author: Mark Karpov <markkarpov92@gmail.com>@@ -25,6 +25,7 @@ extra-doc-files: CHANGELOG.md+ HACKING.md README.md source-repository head@@ -36,7 +37,9 @@ default: False manual: True -common common-ghc-options+common shared+ default-language: GHC2021+ build-depends: base >=4.20 && <5 ghc-options: -O2 -Wall@@ -50,8 +53,12 @@ -haddock -Winvalid-haddock +common ghc-lib-parser+ build-depends: ghc-lib-parser >=9.14 && <9.15+ library- import: common-ghc-options+ import: shared+ import: ghc-lib-parser exposed-modules: Tilia.Cabal.Package Tilia.Cabal.Plan@@ -94,6 +101,7 @@ Tilia.Palette Tilia.Parser Tilia.Pragma+ Tilia.Pragma.Ghc Tilia.Process Tilia.Render Tilia.Render.Class@@ -119,12 +127,10 @@ Tilia.Utils hs-source-dirs: src- default-language: GHC2021 build-depends:- Cabal-syntax >=3.12 && <3.17,+ Cabal-syntax >=3.18 && <3.19, aeson >=2.1 && <3, array >=0.5 && <0.6,- base >=4.20 && <5, base16-bytestring >=1 && <2, bytestring >=0.11 && <0.13, choice >=0.2 && <0.3,@@ -133,7 +139,6 @@ deepseq >=1.4 && <1.6, directory ^>=1.3, filepath >=1.4 && <1.6,- ghc-lib-parser >=9.14 && <9.15, process >=1.6 && <1.7, syb >=0.7 && <0.8, tar >=0.6 && <0.8,@@ -143,14 +148,12 @@ zlib >=0.6 && <0.8, executable tilia- import: common-ghc-options+ import: shared main-is: Main.hs hs-source-dirs: app other-modules: Paths_tilia autogen-modules: Paths_tilia- default-language: GHC2021 build-depends:- base >=4.20 && <5, bytestring >=0.11 && <0.13, choice >=0.2 && <0.3, directory ^>=1.3,@@ -176,7 +179,8 @@ "-with-rtsopts=-N12 -A32m" test-suite tests- import: common-ghc-options+ import: shared+ import: ghc-lib-parser type: exitcode-stdio-1.0 main-is: Main.hs build-tool-depends: hspec-discover:hspec-discover >=2 && <3@@ -219,15 +223,15 @@ Tilia.Render.OperatorSpec Tilia.RenderSpec Tilia.RunSpec+ Tilia.SpanSpec Tilia.TestConfig Tilia.TestUtils Tilia.WithProjectPlan - default-language: GHC2021 build-depends:+ Cabal-syntax >=3.18 && <3.19, QuickCheck >=2.14 && <3, array >=0.5 && <0.6,- base >=4.20 && <5, base16-bytestring >=1 && <2, bytestring >=0.11 && <0.13, choice >=0.2 && <0.3,@@ -235,7 +239,6 @@ cryptohash-sha256 >=0.11 && <0.12, directory ^>=1.3, filepath >=1.4 && <1.6,- ghc-lib-parser >=9.14 && <9.15, hspec >=2 && <3, http-client >=0.7 && <0.8, req >=3.13 && <4,@@ -252,7 +255,8 @@ "-with-rtsopts=-M8G -N" benchmark bench- import: common-ghc-options+ import: shared+ import: ghc-lib-parser type: exitcode-stdio-1.0 main-is: Main.hs hs-source-dirs:@@ -270,9 +274,7 @@ Tilia.TestUtils c-sources: bench/cbits/counter.c- default-language: GHC2021 build-depends:- base >=4.20 && <5, base16-bytestring >=1 && <2, bytestring >=0.11 && <0.13, choice >=0.2 && <0.3,@@ -281,12 +283,12 @@ deepseq >=1.4 && <1.6, directory ^>=1.3, filepath >=1.4 && <1.6,- ghc-lib-parser >=9.14 && <9.15, http-client >=0.7 && <0.8, optparse-applicative >=0.14 && <0.20, process >=1.6.12 && <1.7, req >=3.13 && <4, tar >=0.6 && <0.8,+ temporary ^>=1.3, text >=2.1 && <3, tilia, zlib >=0.6 && <0.8,