Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.cabal files: Make a trailing colon for stanzas a parse failure #10660

Open
9999years opened this issue Dec 20, 2024 · 0 comments
Open

.cabal files: Make a trailing colon for stanzas a parse failure #10660

9999years opened this issue Dec 20, 2024 · 0 comments
Labels
re: user experience User experience (UX) issue

Comments

@9999years
Copy link
Collaborator

#10525 made this mistake an error in cabal.project files:

source-repository-package:
    type: git
    location: https://github.com/parsonsmatt/foundation
    tag: 688c32ccd9a951bc96dd09423a6e6684f091d510
    subdir: basement
    subdir: foundation

Now, it prints this error:

Error: [Cabal-7090]
Error parsing project file cabal.project:52:
'source-repository-package' is a stanza, not a field. Remove the trailing ':' to parse a stanza.

Unfortunately, .cabal files use a ~completely different system for parsing, so the fix in #10525 doesn't apply to those!

Implementation strategy

For parsing cabal.project, the parseFieldsAndSections function (which #10525 modifies to error when stanzas are used as fields) is used with a list of top-level fields and a list of top-level stanzas (which themselves contain a grammar) to parse the file:

parseLegacyProjectConfigFields :: ProjectConfigPath -> [ParseUtils.Field] -> ParseResult LegacyProjectConfig
parseLegacyProjectConfigFields (ConstraintSourceProjectConfig -> constraintSrc) =
parseFieldsAndSections
(legacyProjectConfigFieldDescrs constraintSrc)
legacyPackageConfigSectionDescrs
legacyPackageConfigFGSectionDescrs
mempty

E.g. Here's the section description for source-repository-package:

packageRepoSectionDescr
:: ( FieldGrammar c g
, Applicative (g SourceRepoList)
, c (Identity RepoType)
, c (List NoCommaFSep FilePathNT String)
, c (NonEmpty' NoCommaFSep Token String)
)
=> FGSectionDescr g LegacyProjectConfig
packageRepoSectionDescr =
FGSectionDescr
{ fgSectionName = "source-repository-package"
, fgSectionGrammar = sourceRepositoryPackageGrammar
, fgSectionGet = map (\x -> ("", x)) . legacyPackagesRepo
, fgSectionSet =
\lineno unused pkgrepo projconf -> do
unless (null unused) $
syntaxError lineno "the section 'source-repository-package' takes no arguments"
return
projconf
{ legacyPackagesRepo = legacyPackagesRepo projconf ++ [pkgrepo]
}
}

And here's the grammar for the fields contained in a source-repository-package:

sourceRepositoryPackageGrammar
:: ( FieldGrammar c g
, Applicative (g SourceRepoList)
, c (Identity RepoType)
, c (List NoCommaFSep FilePathNT String)
, c (NonEmpty' NoCommaFSep Token String)
)
=> g SourceRepoList SourceRepoList
sourceRepositoryPackageGrammar =
SourceRepositoryPackage
<$> uniqueField "type" srpTypeLens
<*> uniqueFieldAla "location" Token srpLocationLens
<*> optionalFieldAla "tag" Token srpTagLens
<*> optionalFieldAla "branch" Token srpBranchLens
<*> monoidalFieldAla "subdir" (alaList' NoCommaFSep FilePathNT) srpSubdirLens -- note: NoCommaFSep is somewhat important for roundtrip, as "." is there...
<*> fmap (maybe [] toList) pcc
where
pcc = optionalFieldAla "post-checkout-command" (alaNonEmpty' NoCommaFSep Token) srpCommandLensNE
{-# SPECIALIZE sourceRepositoryPackageGrammar :: ParsecFieldGrammar' SourceRepoList #-}
{-# SPECIALIZE sourceRepositoryPackageGrammar :: PrettyFieldGrammar' SourceRepoList #-}

However, for .cabal files, the top-level grammar is defined manually:

packageDescriptionFieldGrammar
:: ( FieldGrammar c g
, Applicative (g PackageDescription)
, Applicative (g PackageIdentifier)
, c (Identity BuildType)
, c (Identity PackageName)
, c (Identity Version)
, forall from to. c (List FSep (RelativePathNT from to) (RelativePath from to))
, forall from to. c (List VCat (RelativePathNT from to) (RelativePath from to))
, c (List FSep TestedWith (CompilerFlavor, VersionRange))
, c CompatLicenseFile
, c CompatDataDir
)
=> g PackageDescription PackageDescription
packageDescriptionFieldGrammar =
PackageDescription
<$> optionalFieldDefAla "cabal-version" SpecVersion L.specVersion CabalSpecV1_0
<*> blurFieldGrammar L.package packageIdentifierGrammar
<*> optionalFieldDefAla "license" SpecLicense L.licenseRaw (Left SPDX.NONE)
<*> licenseFilesGrammar
<*> freeTextFieldDefST "copyright" L.copyright
<*> freeTextFieldDefST "maintainer" L.maintainer
<*> freeTextFieldDefST "author" L.author
<*> freeTextFieldDefST "stability" L.stability
<*> monoidalFieldAla "tested-with" (alaList' FSep TestedWith) L.testedWith
<*> freeTextFieldDefST "homepage" L.homepage
<*> freeTextFieldDefST "package-url" L.pkgUrl
<*> freeTextFieldDefST "bug-reports" L.bugReports
<*> pure [] -- source-repos are stanza
<*> freeTextFieldDefST "synopsis" L.synopsis
<*> freeTextFieldDefST "description" L.description
<*> freeTextFieldDefST "category" L.category
<*> prefixedFields "x-" L.customFieldsPD
<*> optionalField "build-type" L.buildTypeRaw
<*> pure Nothing -- custom-setup
-- components
<*> pure Nothing -- lib
<*> pure [] -- sub libs
<*> pure [] -- executables
<*> pure [] -- foreign libs
<*> pure [] -- test suites
<*> pure [] -- benchmarks
-- * Files
<*> monoidalFieldAla "data-files" (alaList' VCat RelativePathNT) L.dataFiles
<*> optionalFieldDefAla "data-dir" CompatDataDir L.dataDir sameDirectory
^^^ fmap (\x -> if null (getSymbolicPath x) then sameDirectory else x) -- map empty directories to "."
<*> monoidalFieldAla "extra-source-files" formatExtraSourceFiles L.extraSrcFiles
<*> monoidalFieldAla "extra-tmp-files" (alaList' VCat RelativePathNT) L.extraTmpFiles
<*> monoidalFieldAla "extra-doc-files" formatExtraSourceFiles L.extraDocFiles
<*> monoidalFieldAla "extra-files" formatExtraSourceFiles L.extraFiles
^^^ availableSince CabalSpecV3_14 []
where
packageIdentifierGrammar =
PackageIdentifier
<$> uniqueField "name" L.pkgName
<*> uniqueField "version" L.pkgVersion
licenseFilesGrammar =
(++)
-- TODO: neither field is deprecated
-- should we pretty print license-file if there's single license file
-- and license-files when more
<$> monoidalFieldAla "license-file" CompatLicenseFile L.licenseFiles
<*> monoidalFieldAla "license-files" (alaList' FSep RelativePathNT) L.licenseFiles
^^^ hiddenField

And the goSections parsing function just handles each field name manually:

| name == "library" && null args = do

So we should adapt those into the system used by cabal.project.

@9999years 9999years added the re: user experience User experience (UX) issue label Dec 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
re: user experience User experience (UX) issue
Projects
None yet
Development

No branches or pull requests

1 participant