Skip to content

Commit adc3b81

Browse files
authored
Check package name conflicts (#3352)
- Progress towards #3331 This pr adds a check that ensures no two packages in the dependency tree have the same name but different versions. Example error message: ``` /home/jan/projects/juvix/tests/negative/PackageNameConflict/dep2/Package.juvix:1:1: error: The package dep is used with different versions: • version 0.11.0-5075d2e2d2ea0ceedb49a53ad8daf559c89cb9c3f86b11853bcc1586907cf3d1 • at /home/jan/projects/juvix/tests/negative/PackageNameConflict/dep2/ • version 1.0.0-5075d2e2d2ea0ceedb49a53ad8daf559c89cb9c3f86b11853bcc1586907cf3d1 • at /home/jan/projects/juvix/tests/negative/PackageNameConflict/dep1/ ``` I've added a global flag that disables this check: `--unsafe-ignore-package-name-conflicts`. I've added this flag just in case some existing Juvix project relied on the existing behaviour. We should probably remove it in the near future.
1 parent 7662bd1 commit adc3b81

File tree

26 files changed

+167
-119
lines changed

26 files changed

+167
-119
lines changed

app/App.hs

+5-16
Original file line numberDiff line numberDiff line change
@@ -298,16 +298,6 @@ runPipeline opts input_ =
298298
runPipelineLogger opts input_
299299
. inject
300300

301-
runPipelineRecursive ::
302-
forall r.
303-
(Members '[App, EmbedIO, Logger, TaggedLock] r) =>
304-
Maybe (AppPath File) ->
305-
Sem r (InternalTypedResult, [InternalTypedResult])
306-
runPipelineRecursive input_ = do
307-
args <- askArgs
308-
entry <- getEntryPoint' args input_
309-
runReader defaultPipelineOptions (runPipelineHtmlEither entry) >>= fromRightJuvixError
310-
311301
runPipelineHtml ::
312302
(Members '[App, EmbedIO, Logger, TaggedLock] r) =>
313303
Bool ->
@@ -320,16 +310,15 @@ runPipelineHtml bNonRecursive input_
320310
| otherwise = do
321311
args <- askArgs
322312
entry <- getEntryPoint' args input_
323-
runReader defaultPipelineOptions (runPipelineHtmlEither entry) >>= fromRightJuvixError
313+
runPipelineOptions (runPipelineHtmlEither entry) >>= fromRightJuvixError
324314

325315
runPipelineOptions :: (Members '[App] r) => Sem (Reader PipelineOptions ': r) a -> Sem r a
326316
runPipelineOptions m = do
327317
g <- askGlobalOptions
328-
let opt =
329-
defaultPipelineOptions
330-
{ _pipelineNumThreads = g ^. globalNumThreads,
331-
_pipelineShowThreadId = g ^. globalDevShowThreadIds
332-
}
318+
let opt = run . execState defaultPipelineOptions $ do
319+
modify (set pipelineNumThreads (g ^. globalNumThreads))
320+
modify (set (pipelineDependenciesConfig . dependenciesConfigIgnorePackageNameConflicts) (g ^. globalUnsafeIgnorePackageNameConflicts))
321+
modify (set pipelineShowThreadId (g ^. globalDevShowThreadIds))
333322
runReader opt m
334323

335324
runPipelineEntry :: (Members '[App, Logger, EmbedIO, TaggedLock] r) => EntryPoint -> Sem (PipelineEff r) a -> Sem r a

app/Commands/Doctor.hs

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import Data.Aeson.TH
77
import Juvix.Extra.Clang
88
import Juvix.Extra.Version qualified as V
99
import Network.HTTP.Simple
10-
import Safe (headMay)
1110
import System.Environment qualified as E
1211
import System.Process qualified as P
1312
import Text.Read (readMaybe)

app/GlobalOptions.hs

+8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ data GlobalOptions = GlobalOptions
2929
_globalFieldSize :: Maybe Natural,
3030
_globalOffline :: Bool,
3131
_globalLogLevel :: LogLevel,
32+
_globalUnsafeIgnorePackageNameConflicts :: Bool,
3233
_globalDevShowThreadIds :: Bool
3334
}
3435
deriving stock (Eq, Show)
@@ -75,6 +76,7 @@ defaultGlobalOptions =
7576
_globalNoCheck = False,
7677
_globalFieldSize = Nothing,
7778
_globalDevShowThreadIds = False,
79+
_globalUnsafeIgnorePackageNameConflicts = False,
7880
_globalOffline = False
7981
}
8082

@@ -155,6 +157,12 @@ parseGlobalFlags = do
155157
<> intercalate " < " [show l | l <- allElements @LogLevel]
156158
)
157159
)
160+
_globalUnsafeIgnorePackageNameConflicts <-
161+
switch
162+
( long "unsafe-ignore-package-name-conflicts"
163+
<> help "[UNSAFE] Allow the same package to be used with different versions"
164+
)
165+
158166
_globalNoCheck <-
159167
switch
160168
( long "dev-no-check"

src/Juvix/Compiler/Core/Transformation/IntToPrimInt.hs

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ module Juvix.Compiler.Core.Transformation.IntToPrimInt where
33
import Data.HashMap.Strict qualified as HashMap
44
import Juvix.Compiler.Core.Extra
55
import Juvix.Compiler.Core.Transformation.Base
6-
import Safe (headMay)
76

87
data BuiltinIntCtor
98
= BuiltinIntCtorOfNat

src/Juvix/Compiler/Internal/Translation/FromInternal/Analysis/FunctionCall.hs

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import Data.HashMap.Strict qualified as HashMap
77
import Juvix.Compiler.Internal.Extra
88
import Juvix.Compiler.Internal.Translation.FromInternal.Analysis.Termination.Data
99
import Juvix.Prelude
10-
import Safe (headMay)
1110

1211
viewCall ::
1312
forall r.

src/Juvix/Compiler/Pipeline/Loader/PathResolver.hs

+26-19
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module Juvix.Compiler.Pipeline.Loader.PathResolver
1212
findPackageJuvixFiles,
1313
importNodePackageId,
1414
mkPackageInfoPackageId,
15-
checkConflicts,
15+
checkPackageNameConflicts,
1616
)
1717
where
1818

@@ -41,22 +41,30 @@ import Juvix.Extra.Paths
4141
import Juvix.Extra.Stdlib (ensureStdlib)
4242
import Juvix.Prelude
4343

44-
checkConflicts :: forall r'. (Members '[Error JuvixError] r') => [PackageInfo] -> Sem r' ()
45-
checkConflicts pkgs = do
46-
let reps = findRepeatedOn (^. packageInfoPackageId) pkgs
47-
case nonEmpty reps of
48-
Just (rep :| _) -> errRep rep
49-
Nothing -> return ()
44+
-- | Checks that a package (identified by name) does not appear with different
45+
-- versions
46+
checkPackageNameConflicts :: forall r'. (Members '[Error JuvixError] r') => [PackageInfo] -> Sem r' ()
47+
checkPackageNameConflicts pkgs = do
48+
let byname = map fst (findRepeatedOn (^. packageInfoPackageId . packageIdName) pkgs)
49+
forM_ byname checkPackageName
5050
where
51-
errRep :: ((PackageInfo, NonEmpty PackageInfo), PackageId) -> Sem r' ()
52-
errRep ((p, ps), pid) =
53-
throw
54-
. JuvixError
55-
$ ErrAmbiguousPackageId
56-
AmbiguousPackageId
57-
{ _ambiguousPackageId = pid,
58-
_ambiguousPackageIdPackages = NonEmpty.cons p ps
59-
}
51+
checkPackageName :: (PackageInfo, NonEmpty PackageInfo) -> Sem r' ()
52+
checkPackageName (pkg, others) = ignoreFail $ do
53+
pkgsDiffVer :: NonEmpty PackageInfo <- failMaybe (nonEmpty (filter ((/= ver) . getVer) (toList others)))
54+
let indexedByVer :: HashMap SemVer (NonEmpty PackageInfo) = indexedByHashList getVer pkgsDiffVer
55+
diffVers :: NonEmpty (SemVer, NonEmpty PackageInfo) <- failMaybe (nonEmpty (HashMap.toList indexedByVer))
56+
let diffVers' :: NonEmpty (SemVer, NonEmpty (Path Abs Dir)) =
57+
over (each . _2 . each) (^. packageRoot) diffVers
58+
throw . JuvixError . ErrPackageNameConflict $
59+
PackageNameConflict
60+
{ _packageNameConflictPackage = pkg,
61+
_packageNameConflictVersions = NonEmpty.cons (ver, pure (pkg ^. packageRoot)) diffVers'
62+
}
63+
where
64+
getVer = (^. packageInfoPackageId . packageIdVersion)
65+
66+
ver :: SemVer
67+
ver = getVer pkg
6068

6169
mkPackage ::
6270
forall r.
@@ -470,9 +478,8 @@ runPathResolver2 st topEnv arg = do
470478
handler
471479
)
472480
$ do
473-
_pkgs <- toList <$> getPackageInfos
474-
-- I think we should not check for conflicts
475-
-- checkConflicts pkgs
481+
pkgs <- toList <$> getPackageInfos
482+
unless (depsConfig ^. dependenciesConfigIgnorePackageNameConflicts) (checkPackageNameConflicts pkgs)
476483
arg
477484
where
478485
handler ::

src/Juvix/Compiler/Pipeline/Loader/PathResolver/DependenciesConfig.hs

+8-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ module Juvix.Compiler.Pipeline.Loader.PathResolver.DependenciesConfig where
22

33
import Juvix.Prelude.Base
44

5-
newtype DependenciesConfig = DependenciesConfig
6-
{ _dependenciesConfigForceUpdateLockfile :: Bool
5+
data DependenciesConfig = DependenciesConfig
6+
{ _dependenciesConfigForceUpdateLockfile :: Bool,
7+
_dependenciesConfigIgnorePackageNameConflicts :: Bool
78
}
89

910
defaultDependenciesConfig :: DependenciesConfig
10-
defaultDependenciesConfig = DependenciesConfig {_dependenciesConfigForceUpdateLockfile = False}
11+
defaultDependenciesConfig =
12+
DependenciesConfig
13+
{ _dependenciesConfigForceUpdateLockfile = False,
14+
_dependenciesConfigIgnorePackageNameConflicts = False
15+
}
1116

1217
makeLenses ''DependenciesConfig

src/Juvix/Compiler/Pipeline/Loader/PathResolver/Error.hs

+24-19
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,7 @@ data PathResolverError
9494
= ErrDependencyConflict DependencyConflict
9595
| ErrMissingModule MissingModule
9696
| ErrPackageInvalidImport PackageInvalidImport
97-
| -- | The ErrAmbiguousPackageId error is unused at the moment. We append the
98-
-- hash of all project files to the pre-release tag of the package version.
99-
ErrAmbiguousPackageId AmbiguousPackageId
97+
| ErrPackageNameConflict PackageNameConflict
10098
deriving stock (Show)
10199

102100
instance ToGenericError PathResolverError where
@@ -118,14 +116,14 @@ instance HasLoc PathResolverError where
118116
getLoc _missingModule
119117
ErrPackageInvalidImport PackageInvalidImport {..} ->
120118
getLoc _packageInvalidImport
121-
ErrAmbiguousPackageId a -> getLoc a
119+
ErrPackageNameConflict a -> getLoc a
122120

123121
instance PrettyCodeAnn PathResolverError where
124122
ppCodeAnn = \case
125123
ErrDependencyConflict e -> ppCodeAnn e
126124
ErrMissingModule e -> ppCodeAnn e
127125
ErrPackageInvalidImport e -> ppCodeAnn e
128-
ErrAmbiguousPackageId e -> ppCodeAnn e
126+
ErrPackageNameConflict e -> ppCodeAnn e
129127

130128
data DependencyConflict = DependencyConflict
131129
{ _conflictPackages :: NonEmpty PackageInfo,
@@ -191,21 +189,28 @@ instance PrettyCodeAnn PackageInvalidImport where
191189
<> line
192190
<> "Package files may only import modules from the Juvix standard library, Juvix.Builtin modules, or from the PackageDescription module."
193191

194-
data AmbiguousPackageId = AmbiguousPackageId
195-
{ _ambiguousPackageId :: PackageId,
196-
_ambiguousPackageIdPackages :: NonEmpty PackageInfo
192+
data PackageNameConflict = PackageNameConflict
193+
{ _packageNameConflictPackage :: PackageInfo,
194+
-- | Must contain at least two elements
195+
_packageNameConflictVersions :: NonEmpty (SemVer, NonEmpty (Path Abs Dir))
197196
}
198197
deriving stock (Show)
199198

200-
instance HasLoc AmbiguousPackageId where
201-
getLoc AmbiguousPackageId {..} = intervalFromFile ((head _ambiguousPackageIdPackages) ^. packageRoot <//> packageFilePath)
199+
instance HasLoc PackageNameConflict where
200+
getLoc PackageNameConflict {..} = intervalFromFile (_packageNameConflictPackage ^. packageRoot <//> packageFilePath)
202201

203-
instance PrettyCodeAnn AmbiguousPackageId where
204-
ppCodeAnn AmbiguousPackageId {..} = do
205-
"Ambiguous package id:"
206-
<> line
207-
<> ppCodeAnn _ambiguousPackageId
208-
<> line
209-
<> "The above package id is the same for the following packages"
210-
<> line
211-
<> itemize ((pretty . (^. packageRoot)) <$> _ambiguousPackageIdPackages)
202+
instance PrettyCodeAnn PackageNameConflict where
203+
ppCodeAnn PackageNameConflict {..} = do
204+
"The package"
205+
<+> important (pretty pkgName)
206+
<+> "is used with different versions:"
207+
<> line
208+
<> itemize
209+
[ "version"
210+
<+> (important . pretty . prettySemVer $ ver)
211+
<> line
212+
<> indent' (itemize ["at" <+> important (pretty p) | p <- toList paths])
213+
| (ver, paths) <- toList _packageNameConflictVersions
214+
]
215+
where
216+
pkgName = _packageNameConflictPackage ^. packageInfoPackageId . packageIdName

src/Juvix/Compiler/Pipeline/Repl.hs

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ import Juvix.Compiler.Pipeline.Driver
2020
import Juvix.Compiler.Pipeline.EntryPoint
2121
import Juvix.Compiler.Pipeline.Loader.PathResolver
2222
import Juvix.Compiler.Pipeline.Loader.PathResolver.ImportTree (withImportTree)
23+
import Juvix.Compiler.Pipeline.Options
2324
import Juvix.Compiler.Pipeline.Package.Loader.Error
2425
import Juvix.Compiler.Pipeline.Package.Loader.EvalEff.IO
25-
import Juvix.Compiler.Pipeline.Run (defaultPipelineOptions, evalModuleInfoCacheHelper)
26+
import Juvix.Compiler.Pipeline.Run (evalModuleInfoCacheHelper)
2627
import Juvix.Compiler.Store.Extra qualified as Store
2728
import Juvix.Data.Effect.Git
2829
import Juvix.Data.Effect.Process (runProcessIO)
@@ -183,7 +184,7 @@ compileReplInputIO fp txt = do
183184
. mapError (JuvixError @PackageLoaderError)
184185
. runEvalFileEffIO
185186
. runDependencyResolver
186-
. runReader defaultDependenciesConfig
187+
. mapReader (^. pipelineDependenciesConfig)
187188
. runPathResolverArtifacts
188189
. runTopModuleNameChecker
189190
. runReader defaultImportScanStrategy

src/Juvix/Compiler/Pipeline/Run.hs

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.Scoping qualified
1010
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.Scoping qualified as Scoper
1111
import Juvix.Compiler.Concrete.Translation.FromSource qualified as P
1212
import Juvix.Compiler.Concrete.Translation.FromSource.TopModuleNameChecker (TopModuleNameChecker, runTopModuleNameChecker)
13-
import Juvix.Compiler.Concrete.Translation.ImportScanner (ImportScanStrategy, defaultImportScanStrategy)
13+
import Juvix.Compiler.Concrete.Translation.ImportScanner (ImportScanStrategy)
1414
import Juvix.Compiler.Core.Data.Module qualified as Core
1515
import Juvix.Compiler.Core.Translation.FromInternal.Data qualified as Core
1616
import Juvix.Compiler.Internal.Translation qualified as Internal
@@ -213,7 +213,7 @@ runReplPipelineIOEither' lockMode entry = do
213213
. runReader defaultPipelineOptions
214214
. runLoggerIO replLoggerOptions
215215
. runConcurrent
216-
. runReader defaultNumThreads
216+
. mapReader (^. pipelineNumThreads)
217217
. evalInternet hasInternet
218218
. evalHighlightBuilder
219219
. runError
@@ -229,10 +229,10 @@ runReplPipelineIOEither' lockMode entry = do
229229
. mapError (JuvixError @PackageLoaderError)
230230
. runEvalFileEffIO
231231
. runDependencyResolver
232-
. runReader defaultDependenciesConfig
232+
. mapReader (^. pipelineDependenciesConfig)
233233
. runPathResolver'
234234
. runTopModuleNameChecker
235-
. runReader defaultImportScanStrategy
235+
. mapReader (^. pipelineImportStrategy)
236236
. withImportTree (entry ^. entryPointModulePath)
237237
. runMigration
238238
. evalModuleInfoCacheHelper

src/Juvix/Data/CodeAnn.hs

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module Juvix.Data.CodeAnn
33
module Juvix.Data.NameKind,
44
module Juvix.Prelude.Pretty,
55
module Juvix.Data.CodeReference,
6+
module Data.Versions,
67
)
78
where
89

@@ -258,6 +259,9 @@ kwDot = delimiter "."
258259
kwAt :: Doc Ann
259260
kwAt = keyword Str.at_
260261

262+
important :: Doc Ann -> Doc Ann
263+
important = annotate AnnImportant
264+
261265
code :: Doc Ann -> Doc Ann
262266
code = annotate AnnCode
263267

0 commit comments

Comments
 (0)