diff options
Diffstat (limited to 'source/Felix')
| -rw-r--r-- | source/Felix/Cache/Codec.hs | 2 | ||||
| -rw-r--r-- | source/Felix/Migration.hs | 284 | ||||
| -rw-r--r-- | source/Felix/Parse.hs | 147 | ||||
| -rw-r--r-- | source/Felix/Prelude.hs | 200 | ||||
| -rw-r--r-- | source/Felix/Source.hs | 20 | ||||
| -rw-r--r-- | source/Felix/Store.hs | 189 |
6 files changed, 418 insertions, 424 deletions
diff --git a/source/Felix/Cache/Codec.hs b/source/Felix/Cache/Codec.hs index 0bfa975..2d81e57 100644 --- a/source/Felix/Cache/Codec.hs +++ b/source/Felix/Cache/Codec.hs @@ -75,7 +75,7 @@ newtype CacheEpoch = CacheEpoch Word32 currentCacheEpoch :: CacheEpoch currentCacheEpoch = - CacheEpoch 19 + CacheEpoch 23 cacheEpochValue :: CacheEpoch -> Word32 cacheEpochValue (CacheEpoch value) = diff --git a/source/Felix/Migration.hs b/source/Felix/Migration.hs deleted file mode 100644 index 0fcb05d..0000000 --- a/source/Felix/Migration.hs +++ /dev/null @@ -1,284 +0,0 @@ -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE NoImplicitPrelude #-} - --- | Temporary routing data for the content-addressed migration. --- --- These values select current physical sources. They are not durable module --- names and must be deleted with the legacy driver. -module Felix.Migration - ( MigrationMountRole(..) - , migrationMountId - , MigrationModuleRef - , migrationModuleRefRole - , migrationModuleRefPath - , MigrationManifestError(..) - , migrationModuleCandidatePath - , ResolvedMigrationSelection - , resolveMigrationSelection - , migrationSelectionContains - , MigrationGraphRoute(..) - , classifyMigrationGraph - , activeMigrationRoots - , protectedMigrationModules - , phase53LibraryMigrationModules - , typedMigrationModules - , PreludeSourceFamily(..) - , finalPreludeSourceFamilies - , PreludePublicRole(..) - , expectedFinalPreludePublicRoles - ) where - -import Base -import Felix.Parse -import Felix.Source - -import Data.List qualified as List -import Data.Set qualified as Set - - -data MigrationMountRole - = MigrationProject - | MigrationLibrary - | MigrationDebug - deriving stock (Show, Eq, Ord, Enum, Bounded) - -migrationMountId :: MigrationMountRole -> SourceMountId -migrationMountId = \case - MigrationProject -> - sourceMountId "project" - MigrationLibrary -> - sourceMountId "library" - MigrationDebug -> - sourceMountId "debug" - - -data MigrationModuleRef = MigrationModuleRef - !MigrationMountRole - !SafeRelativePath - deriving stock (Show, Eq, Ord) - -migrationModuleRefRole - :: MigrationModuleRef - -> MigrationMountRole -migrationModuleRefRole (MigrationModuleRef role _path) = - role - -migrationModuleRefPath - :: MigrationModuleRef - -> SafeRelativePath -migrationModuleRefPath (MigrationModuleRef _role path) = - path - -data MigrationManifestError - = MissingMigrationMountRole !MigrationMountRole - deriving stock (Show, Eq) - -newtype ResolvedMigrationSelection = ResolvedMigrationSelection - (Set (SourceMountId, CanonicalPath, SafeRelativePath)) - -resolveMigrationSelection - :: Foldable references - => SourceMounts - -> references MigrationModuleRef - -> Either MigrationManifestError ResolvedMigrationSelection -resolveMigrationSelection mounts references = - ResolvedMigrationSelection . Set.fromList - <$> traverse resolveOne (toList references) - where - resolveOne reference = - case List.find - ((== migrationMountId role) . sourceMountIdentifier) - (sourceMountList mounts) of - Nothing -> - Left (MissingMigrationMountRole role) - Just mount -> - Right - ( sourceMountIdentifier mount - , sourceMountRoot mount - , migrationModuleRefPath reference - ) - where - role = migrationModuleRefRole reference - -migrationSelectionContains - :: ResolvedMigrationSelection - -> ResolvedSource - -> Bool -migrationSelectionContains - (ResolvedMigrationSelection selected) source = - ( resolvedSourceMount source - , resolvedSourceMountRoot source - , resolvedSourceRelativePath source - ) `Set.member` selected - -data MigrationGraphRoute - = TypedMigrationGraph - | LegacyMigrationGraph - deriving stock (Show, Eq) - -classifyMigrationGraph - :: ResolvedMigrationSelection - -> ParsedSourceWorkspace - -> MigrationGraphRoute -classifyMigrationGraph selected workspace - | all - (migrationSelectionContains selected . parsedModuleResolved) - (parsedWorkspaceModules workspace) = - TypedMigrationGraph - | otherwise = - LegacyMigrationGraph - --- | Resolve one routing reference against the current prepared mount table. --- --- This deliberately returns a physical candidate path, not a logical owner. -migrationModuleCandidatePath - :: SourceMounts - -> MigrationModuleRef - -> Either MigrationManifestError FilePath -migrationModuleCandidatePath mounts reference = - case List.find matchesRole candidates of - Nothing -> - Left (MissingMigrationMountRole role) - Just candidate -> - Right (sourceCandidatePath candidate) - where - role = migrationModuleRefRole reference - expectedMount = migrationMountId role - candidates = - sourceCandidates - mounts - (migrationModuleRefPath reference) - matchesRole candidate = - sourceCandidateMount candidate == expectedMount - - --- | Current supported verification root. -activeMigrationRoots :: NonEmpty MigrationModuleRef -activeMigrationRoots = - migrationLibraryModule "everything.tex" :| [] - --- | Dependency-closed set/naturals migration boundary. -protectedMigrationModules :: NonEmpty MigrationModuleRef -protectedMigrationModules = - migrationLibraryModule "set.tex" :| - [ migrationLibraryModule "set/cons.tex" - , migrationLibraryModule "set/regularity.tex" - , migrationLibraryModule "set/suc.tex" - , migrationLibraryModule "nat.tex" - ] - --- | Phase 5.3 ordinary library modules admitted by the typed driver. -phase53LibraryMigrationModules :: NonEmpty MigrationModuleRef -phase53LibraryMigrationModules = - migrationLibraryModule "set/symdiff.tex" :| - [ migrationLibraryModule "set/powerset.tex" - , migrationLibraryModule "set/partition.tex" - , migrationLibraryModule "set/bipartition.tex" - , migrationLibraryModule "set/product.tex" - , migrationLibraryModule "set/filter.tex" - , migrationLibraryModule "relation.tex" - , migrationLibraryModule "relation/properties.tex" - , migrationLibraryModule "relation/uniqueness.tex" - , migrationLibraryModule "function.tex" - , migrationLibraryModule "set/cantor.tex" - , migrationLibraryModule "set/fixpoint.tex" - ] - --- | Sources whose complete graphs may enter the typed driver. -typedMigrationModules :: NonEmpty MigrationModuleRef -typedMigrationModules = - protectedMigrationModules - <> phase53LibraryMigrationModules - <> (migrationProjectModule "test/phase3/typed-producer.tex" :| - [ migrationProjectModule "test/phase3/typed-unsupported.tex" - , migrationProjectModule "test/phase3/typed-shared-a.tex" - , migrationProjectModule "test/phase3/typed-shared-b.tex" - , migrationProjectModule "test/phase3/typed-shared-root.tex" - , migrationProjectModule "test/phase5/exact-producer.tex" - , migrationProjectModule "test/phase5/exact-importer.tex" - , migrationProjectModule "test/phase5/exact-failure.tex" - , migrationProjectModule "test/phase5/exact-proofs.tex" - , migrationProjectModule "test/phase5/exact-local-definition.tex" - , migrationProjectModule "test/phase5/exact-local-definition-failure.tex" - , migrationProjectModule "test/phase5/exact-contradiction.tex" - , migrationProjectModule "test/phase5/exact-contradiction-goal.tex" - , migrationProjectModule "test/phase5/exact-relation-expression.tex" - , migrationProjectModule "test/phase5/exact-relation-expression-missing-pair.tex" - , migrationProjectModule "test/phase5/exact-application.tex" - , migrationProjectModule "test/phase5/exact-application-missing.tex" - , migrationProjectModule "test/phase5/exact-quantified-subject.tex" - , migrationProjectModule "test/phase5/exact-quantified-subject-nested.tex" - , migrationProjectModule "test/phase5/exact-proof-failure.tex" - , migrationProjectModule "test/phase5/exact-induction-nested.tex" - , migrationProjectModule "test/phase5/exact-runtime-failure.tex" - , migrationProjectModule "test/phase5/exact-source-axiom.tex" - , migrationProjectModule "test/phase5/exact-source-axiom-assumptions.tex" - , migrationProjectModule "test/phase5/exact-omitted.tex" - , migrationProjectModule "test/phase5/exact-escape-producer.tex" - , migrationProjectModule "test/phase5/exact-escape-consumer.tex" - , migrationProjectModule "test/phase5/exact-separation.tex" - , migrationProjectModule "test/phase5/exact-replacement.tex" - , migrationProjectModule "test/phase5/exact-finite-set.tex" - , migrationProjectModule "test/phase5/exact-datatype.tex" - , migrationProjectModule "test/phase5/exact-datatype-nested.tex" - , migrationProjectModule "test/phase5/exact-inductive.tex" - , migrationProjectModule "test/phase5/exact-inductive-recursive.tex" - , migrationProjectModule "test/phase5/exact-inductive-nested.tex" - , migrationProjectModule "test/phase5/unmatched-proof.tex" - ]) - - --- | Source constructs required by the non-authoritative prelude skeleton. -data PreludeSourceFamily - = PreludeDefinition - | PreludeAbbreviation - | PreludeProposition - | PreludeProof - deriving stock (Show, Eq, Ord, Enum, Bounded) - -finalPreludeSourceFamilies :: Set PreludeSourceFamily -finalPreludeSourceFamilies = - Set.fromList - [ PreludeDefinition - , PreludeAbbreviation - , PreludeProposition - , PreludeProof - ] - --- | Stable public roles required at final-prelude cutover. --- --- Helper declarations used to derive these roles are intentionally not --- frozen by Phase 0. -data PreludePublicRole - = PreludeInfinityTheorem - | PreludeOmegaObject - | PreludeOmegaDefiningEquation - | PreludeNaturalsAlias - | PreludeNaturalsInductiveTheorem - | PreludeNaturalsMinimalTheorem - deriving stock (Show, Eq, Ord, Enum, Bounded) - -expectedFinalPreludePublicRoles :: Set PreludePublicRole -expectedFinalPreludePublicRoles = - Set.fromList [minBound .. maxBound] - - -migrationLibraryModule :: FilePath -> MigrationModuleRef -migrationLibraryModule = - MigrationModuleRef MigrationLibrary . checkedRelativePath - -migrationProjectModule :: FilePath -> MigrationModuleRef -migrationProjectModule = - MigrationModuleRef MigrationProject . checkedRelativePath - -checkedRelativePath :: FilePath -> SafeRelativePath -checkedRelativePath path = - case safeRelativePath path of - Left err -> - impossible - ("invalid migration path literal " - <> show path - <> ": " - <> show err) - Right relative -> - relative diff --git a/source/Felix/Parse.hs b/source/Felix/Parse.hs index b9d5c57..751f09c 100644 --- a/source/Felix/Parse.hs +++ b/source/Felix/Parse.hs @@ -8,6 +8,7 @@ module Felix.Parse ( ParseException(..) , ParseWorkspaceError(..) , renderParseWorkspaceError + , renderSyntaxMaterializationError , ParseExecutionError(..) , ParsedArtifactIntegrityError(..) , SyntaxDeclarationError(..) @@ -89,7 +90,9 @@ module Felix.Parse , parseMeasurementTargetInspectionCount , parseSourceWorkspaceMeasured , parseSourceWorkspaceMeasuredWithSyntaxInputs + , parseSourceWorkspaceMeasuredWithSyntaxInputsAndGraphValidation , parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputs + , parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndGraphValidation , parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndCallback , parseSourceWorkspaceWith , parseResolvedSourceGraph @@ -1149,10 +1152,28 @@ parseSourceWorkspaceMeasuredWithSyntaxInputs (ParsedSourceWorkspace, ParseMeasurements)) -- Implicit syntax inputs must be self-contained module interfaces. parseSourceWorkspaceMeasuredWithSyntaxInputs mounts request syntaxInputs = - parseSourceWorkspaceMeasuredWithSyntaxInputsAndCallback + parseSourceWorkspaceMeasuredWithSyntaxInputsAndGraphValidation mounts request syntaxInputs + (const (Right ())) + +parseSourceWorkspaceMeasuredWithSyntaxInputsAndGraphValidation + :: SourceMounts + -> RootRequest + -> (ResolvedSource -> [ModuleSyntaxInterface]) + -> (ResolvedSourceGraph -> Either SourceError ()) + -> IO + (Either + ParseWorkspaceError + (ParsedSourceWorkspace, ParseMeasurements)) +parseSourceWorkspaceMeasuredWithSyntaxInputsAndGraphValidation + mounts request syntaxInputs validateGraph = + parseSourceWorkspaceMeasuredWithSyntaxInputsAndGraphValidationAndCallback + mounts + request + syntaxInputs + validateGraph (\_source _block -> pure ()) parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputs @@ -1166,8 +1187,24 @@ parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputs (ParsedSourceWorkspace, ParseMeasurements)) parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputs store mounts request syntaxInputs = - parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndCallback - store mounts request syntaxInputs (\_source _block -> pure ()) + parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndGraphValidation + store mounts request syntaxInputs (const (Right ())) + +parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndGraphValidation + :: Store.Store + -> SourceMounts + -> RootRequest + -> (ResolvedSource -> [ModuleSyntaxInterface]) + -> (ResolvedSourceGraph -> Either SourceError ()) + -> IO + (Either + ParseExecutionError + (ParsedSourceWorkspace, ParseMeasurements)) +parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndGraphValidation + store mounts request syntaxInputs validateGraph = + parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndGraphValidationAndCallback + store mounts request syntaxInputs validateGraph + (\_source _block -> pure ()) parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndCallback :: Store.Store @@ -1181,6 +1218,22 @@ parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndCallback (ParsedSourceWorkspace, ParseMeasurements)) parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndCallback store mounts request syntaxInputs emitBlock = do + parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndGraphValidationAndCallback + store mounts request syntaxInputs (const (Right ())) emitBlock + +parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndGraphValidationAndCallback + :: Store.Store + -> SourceMounts + -> RootRequest + -> (ResolvedSource -> [ModuleSyntaxInterface]) + -> (ResolvedSourceGraph -> Either SourceError ()) + -> (ResolvedSource -> Raw.Block -> IO ()) + -> IO + (Either + ParseExecutionError + (ParsedSourceWorkspace, ParseMeasurements)) +parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndGraphValidationAndCallback + store mounts request syntaxInputs validateGraph emitBlock = do resolutionStart <- getMonotonicTimeNSec graphResult <- buildResolvedSourceGraphMeasured mounts request resolutionEnd <- getMonotonicTimeNSec @@ -1191,20 +1244,27 @@ parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndCallback (ParseExecutionWorkspaceError (SourceWorkspaceError err))) Right (graph, selectionMeasurements) -> - fmap - (fmap - (\(workspace, measurements) -> - ( workspace - , withResolutionMeasurements - measurements - (resolutionEnd - resolutionStart) - selectionMeasurements - ))) - (parseResolvedSourceGraphMeasuredWithArtifacts - (PersistentParsedArtifacts store) - graph - syntaxInputs - emitBlock) + case validateGraph graph of + Left err -> + pure + (Left + (ParseExecutionWorkspaceError + (SourceWorkspaceError err))) + Right () -> + fmap + (fmap + (\(workspace, measurements) -> + ( workspace + , withResolutionMeasurements + measurements + (resolutionEnd - resolutionStart) + selectionMeasurements + ))) + (parseResolvedSourceGraphMeasuredWithArtifacts + (PersistentParsedArtifacts store) + graph + syntaxInputs + emitBlock) -- | Resolve, strictly load, and parse one source closure with a block -- callback. @@ -1250,6 +1310,21 @@ parseSourceWorkspaceMeasuredWithSyntaxInputsAndCallback (ParsedSourceWorkspace, ParseMeasurements)) parseSourceWorkspaceMeasuredWithSyntaxInputsAndCallback mounts request syntaxInputs emitBlock = do + parseSourceWorkspaceMeasuredWithSyntaxInputsAndGraphValidationAndCallback + mounts request syntaxInputs (const (Right ())) emitBlock + +parseSourceWorkspaceMeasuredWithSyntaxInputsAndGraphValidationAndCallback + :: SourceMounts + -> RootRequest + -> (ResolvedSource -> [ModuleSyntaxInterface]) + -> (ResolvedSourceGraph -> Either SourceError ()) + -> (ResolvedSource -> Raw.Block -> IO ()) + -> IO + (Either + ParseWorkspaceError + (ParsedSourceWorkspace, ParseMeasurements)) +parseSourceWorkspaceMeasuredWithSyntaxInputsAndGraphValidationAndCallback + mounts request syntaxInputs validateGraph emitBlock = do resolutionStart <- getMonotonicTimeNSec graphResult <- buildResolvedSourceGraphMeasured mounts request resolutionEnd <- getMonotonicTimeNSec @@ -1257,23 +1332,27 @@ parseSourceWorkspaceMeasuredWithSyntaxInputsAndCallback Left err -> pure (Left (SourceWorkspaceError err)) Right (graph, selectionMeasurements) -> - fmap - (fmap - (\(workspace, measurements) -> - let - -- Resolution includes source loading and import - -- scanning for graph construction. - measured = - withResolutionMeasurements - measurements - (resolutionEnd - resolutionStart) - selectionMeasurements - in - (workspace, measured))) - (parseResolvedSourceGraphMeasuredWith - graph - syntaxInputs - emitBlock) + case validateGraph graph of + Left err -> + pure (Left (SourceWorkspaceError err)) + Right () -> + fmap + (fmap + (\(workspace, measurements) -> + let + -- Resolution includes source loading and + -- import scanning for graph construction. + measured = + withResolutionMeasurements + measurements + (resolutionEnd - resolutionStart) + selectionMeasurements + in + (workspace, measured))) + (parseResolvedSourceGraphMeasuredWith + graph + syntaxInputs + emitBlock) parseResolvedSourceGraph :: ResolvedSourceGraph diff --git a/source/Felix/Prelude.hs b/source/Felix/Prelude.hs index d159b21..28ad961 100644 --- a/source/Felix/Prelude.hs +++ b/source/Felix/Prelude.hs @@ -1,7 +1,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE NoImplicitPrelude #-} --- | The reserved in-memory prelude source used during migration. +-- | The reserved packaged final-prelude source and its authority-free parser. module Felix.Prelude ( ReservedPreludeSourceInput , reservedPreludeSourceInput @@ -9,11 +9,15 @@ module Felix.Prelude , emptyBootstrapSourceInput , reservedPreludeSourceOwner , reservedPreludeSourceLabel + , reservedPreludeSourceCanonicalPath , reservedPreludeSourceBytes , reservedPreludeSourceText + , rejectOrdinaryPreludeSourceGraph , preludeDiagnosticLabel , PreludeLoadError(..) + , renderPreludeLoadError , PreludeParseError(..) + , renderPreludeParseError , ReservedParsedPrelude , reservedParsedPreludeInput , reservedParsedPreludeModule @@ -24,6 +28,8 @@ import Base import Felix.Module import Felix.Parse import Felix.Parsed.Identity qualified as Parsed +import Felix.Source +import Felix.Source.Graph import Report.Location import Syntax.Adapt (SyntaxMaterializationError) import Syntax.Interface @@ -41,6 +47,7 @@ import Paths_felix qualified as Paths data ReservedPreludeSourceInput = ReservedPreludeSourceInput !ModuleName !FilePath + !(Maybe CanonicalPath) !ByteString !Text @@ -51,38 +58,76 @@ reservedPreludeSourceInput bytes = ReservedPreludeSourceInput preludeModuleName preludeDiagnosticLabel + Nothing bytes <$> Text.decodeUtf8' bytes data PreludeLoadError = PreludeSourceReadFailed !FilePath !Text + | PreludeSourceCanonicalizationFailed !FilePath !SourceError | PreludeSourceUtf8Failed !UnicodeException - deriving stock (Show) + +instance Show PreludeLoadError where + show = Text.unpack . renderPreludeLoadError + +renderPreludeLoadError :: PreludeLoadError -> Text +renderPreludeLoadError = \case + PreludeSourceReadFailed path failure -> + Text.pack path + <> ": unable to read packaged final prelude: " + <> failure + PreludeSourceCanonicalizationFailed path failure -> + Text.pack path + <> ": unable to resolve packaged final prelude: " + <> renderSourceError failure + PreludeSourceUtf8Failed failure -> + preludeErrorPrefix + <> "malformed UTF-8: " + <> Text.pack (displayException failure) loadReservedPreludeSourceInput :: IO (Either PreludeLoadError ReservedPreludeSourceInput) loadReservedPreludeSourceInput = do path <- Paths.getDataFileName "data/felix-prelude.tex" - loaded <- try (ByteString.readFile path) - :: IO (Either IOException ByteString) - pure case loaded of + canonicalized <- canonicalizeExistingSourcePath path + case canonicalized of Left failure -> - Left - (PreludeSourceReadFailed - path - (Text.pack (displayException failure))) - Right bytes -> - case reservedPreludeSourceInput bytes of + pure + (Left + (PreludeSourceCanonicalizationFailed path failure)) + Right canonical -> do + loaded <- try + (ByteString.readFile + (canonicalPathFilePath canonical)) + :: IO (Either IOException ByteString) + pure case loaded of Left failure -> - Left (PreludeSourceUtf8Failed failure) - Right input -> - Right input + Left + (PreludeSourceReadFailed + path + (Text.pack (displayException failure))) + Right bytes -> + case Text.decodeUtf8' bytes of + Left failure -> + Left (PreludeSourceUtf8Failed failure) + Right sourceText -> + Right + (ReservedPreludeSourceInput + preludeModuleName + preludeDiagnosticLabel + (Just canonical) + bytes + sourceText) +-- | Empty final-prelude input used only by focused compiler fixtures. +-- +-- Production verification always calls 'loadReservedPreludeSourceInput'. emptyBootstrapSourceInput :: ReservedPreludeSourceInput emptyBootstrapSourceInput = ReservedPreludeSourceInput preludeModuleName preludeDiagnosticLabel + Nothing ByteString.empty Text.empty @@ -90,28 +135,58 @@ reservedPreludeSourceOwner :: ReservedPreludeSourceInput -> ModuleName reservedPreludeSourceOwner - (ReservedPreludeSourceInput owner _label _bytes _text) = + (ReservedPreludeSourceInput owner _label _canonical _bytes _text) = owner reservedPreludeSourceLabel :: ReservedPreludeSourceInput -> FilePath reservedPreludeSourceLabel - (ReservedPreludeSourceInput _owner label _bytes _text) = + (ReservedPreludeSourceInput _owner label _canonical _bytes _text) = label +reservedPreludeSourceCanonicalPath + :: ReservedPreludeSourceInput + -> Maybe CanonicalPath +reservedPreludeSourceCanonicalPath + (ReservedPreludeSourceInput _owner _label canonical _bytes _text) = + canonical + reservedPreludeSourceBytes :: ReservedPreludeSourceInput -> ByteString reservedPreludeSourceBytes - (ReservedPreludeSourceInput _owner _label bytes _text) = + (ReservedPreludeSourceInput _owner _label _canonical bytes _text) = bytes reservedPreludeSourceText :: ReservedPreludeSourceInput -> Text reservedPreludeSourceText - (ReservedPreludeSourceInput _owner _label _bytes sourceText) = + (ReservedPreludeSourceInput + _owner _label _canonical _bytes sourceText) = sourceText +-- | Reject an ordinary source graph containing the physical packaged +-- prelude. Synthetic reserved inputs deliberately have no physical path. +rejectOrdinaryPreludeSourceGraph + :: ReservedPreludeSourceInput + -> ResolvedSourceGraph + -> Either SourceError () +rejectOrdinaryPreludeSourceGraph sourceInput graph = + case reservedPreludeSourceCanonicalPath sourceInput of + Nothing -> + Right () + Just reservedPath -> + case listToMaybe + [ source + | node <- sourceGraphNodes graph + , let source = sourceNodeResolved node + , resolvedSourceCanonicalPath source == reservedPath + ] of + Nothing -> + Right () + Just source -> + Left (PackagedPreludeSelectedAsOrdinarySource source) + preludeDiagnosticLabel :: FilePath preludeDiagnosticLabel = "<felix-prelude>" @@ -130,7 +205,92 @@ data PreludeParseError | PreludeFreshInputFailed !FreshModuleInputError | PreludeParsedModuleKeyFailed !Parsed.ParsedModuleKeyError | PreludeParseInvariantFailed !Text - deriving stock (Show) + +instance Show PreludeParseError where + show = Text.unpack . renderPreludeParseError + +renderPreludeParseError :: PreludeParseError -> Text +renderPreludeParseError = \case + PreludeLocationRegistrationFailed FileIdSpaceExhausted -> + preludeErrorPrefix + <> "could not register source locations: file identifier space exhausted" + PreludeSyntaxPragmaFailed failure -> + renderSyntaxPragmaError failure + PreludeImportNotSupported imports -> + preludeErrorPrefix + <> "imports are not supported" + <> case imports of + [] -> "" + _ -> + ": " + <> Text.intercalate + ", " + (Text.pack . show <$> imports) + PreludeLexicalScanFailed failure -> + Text.pack (show failure) + PreludeSyntaxDeclarationFailed failure -> + Text.pack (show failure) + PreludeLexiconCollision collision -> + Text.pack (show collision) + PreludeSyntaxDeltaFailed collision -> + preludeErrorPrefix + <> "syntax delta has conflicting entries for pattern " + <> Text.pack (show (canonicalCollisionPattern collision)) + <> ": " + <> Text.pack + (show (toList (canonicalCollisionEntries collision))) + PreludeSyntaxInterfaceFailed failure -> + preludeErrorPrefix <> renderSyntaxInterfaceError failure + PreludeSyntaxMaterializationFailed failure -> + preludeErrorPrefix <> renderSyntaxMaterializationError failure + PreludeParseFailed failure -> + preludeErrorPrefix <> Text.pack (show failure) + PreludeFreshInputFailed failure -> + preludeErrorPrefix <> renderFreshModuleInputError failure + PreludeParsedModuleKeyFailed failure -> + preludeErrorPrefix <> renderParsedModuleKeyError failure + PreludeParseInvariantFailed failure -> + preludeErrorPrefix <> "parse invariant failed: " <> failure + +preludeErrorPrefix :: Text +preludeErrorPrefix = + Text.pack preludeDiagnosticLabel <> ": " + +renderSyntaxInterfaceError :: SyntaxInterfaceError -> Text +renderSyntaxInterfaceError = \case + DuplicateDirectSyntaxInterface duplicate -> + "duplicate direct syntax input " <> Text.pack (show duplicate) + UnexpectedBaseSyntaxInterface actual expected -> + "base syntax interface mismatch: expected " + <> Text.pack (show expected) + <> ", got " + <> Text.pack (show actual) + SyntaxInterfaceIdMismatch asserted computed -> + "syntax interface identity mismatch: asserted " + <> Text.pack (show asserted) + <> ", computed " + <> Text.pack (show computed) + +renderFreshModuleInputError :: FreshModuleInputError -> Text +renderFreshModuleInputError = \case + FreshModuleTextDoesNotMatchBytes -> + "decoded source text does not match its source bytes" + FreshPhysicalOwnerMismatch expected actual -> + "physical source owner mismatch: expected " + <> Text.pack (show expected) + <> ", got " + <> Text.pack (show actual) + FreshPhysicalLocationPathMismatch expected actual -> + "physical source location mismatch: expected " + <> Text.pack (show expected) + <> ", got " + <> Text.pack (show actual) + +renderParsedModuleKeyError :: Parsed.ParsedModuleKeyError -> Text +renderParsedModuleKeyError = \case + Parsed.DuplicateParsedDirectSyntaxInput duplicate -> + "parsed module has duplicate direct syntax input " + <> Text.pack (show duplicate) data ReservedParsedPrelude = ReservedParsedPrelude !FreshModuleInput @@ -148,7 +308,7 @@ reservedParsedPreludeModule reservedParsedPreludeModule (ReservedParsedPrelude _input parsed) = parsed --- | Parse a reserved migration input through the ordinary fresh-source path. +-- | Parse reserved final-prelude input through the ordinary fresh-source path. parseReservedPreludeSource :: ReservedPreludeSourceInput -> IO (Either PreludeParseError ReservedParsedPrelude) diff --git a/source/Felix/Source.hs b/source/Felix/Source.hs index a30b5f9..64a88cd 100644 --- a/source/Felix/Source.hs +++ b/source/Felix/Source.hs @@ -25,6 +25,7 @@ module Felix.Source , RootRequest , searchedRoot , existingRoot + , canonicalizeExistingSourcePath , rootRequestSpelling , ResolvedSource , resolvedSourceCanonicalPath @@ -230,6 +231,21 @@ existingRoot path fmap (\canonical -> ExistingRoot canonical path) <$> canonicalize ExistingRootCanonicalizationFailed path +-- | Validate and canonicalize an existing source path without assigning it +-- to a source mount. +canonicalizeExistingSourcePath + :: FilePath + -> IO (Either SourceError CanonicalPath) +canonicalizeExistingSourcePath path = + fmap extract <$> existingRoot path + where + extract = \case + ExistingRoot canonical _spelling -> + canonical + SearchedRoot{} -> + impossible + "canonicalizeExistingSourcePath produced a searched root" + -- | The user-facing spelling retained only for diagnostics. rootRequestSpelling :: RootRequest -> FilePath rootRequestSpelling = \case @@ -450,6 +466,7 @@ data SourceError !ResolvedSource !LocationRegistrationError | SourceImportCycle !(NonEmpty SourceCycleStep) + | PackagedPreludeSelectedAsOrdinarySource !ResolvedSource | SourceGraphInvariantViolation !Text deriving stock (Show, Eq) @@ -551,6 +568,9 @@ renderSourceError = \case <> sourceLabel (cycleImported step) | step <- toList steps ] + PackagedPreludeSelectedAsOrdinarySource source -> + "the packaged final prelude cannot be used as ordinary source " + <> quotePath (resolvedSourceLocationPath source) SourceGraphInvariantViolation reason -> "source graph invariant failed: " <> reason diff --git a/source/Felix/Store.hs b/source/Felix/Store.hs index 973a172..bf84f6e 100644 --- a/source/Felix/Store.hs +++ b/source/Felix/Store.hs @@ -47,6 +47,9 @@ module Felix.Store , newStoreMemo , StoreMemoVisits(..) , storeMemoVisits + , StoreCoordinator + , newStoreCoordinator + , withStoreCoordinator ) where import Base @@ -61,6 +64,11 @@ import Felix.Parsed.Identity qualified as Parsed import Felix.Parsed.Payload qualified as ParsedPayload import Syntax.Interface qualified as Syntax +import Control.Concurrent.MVar + ( MVar + , newMVar + , withMVar + ) import Control.Exception qualified as Exception import Control.Monad (foldM, unless) import Control.Monad.Except qualified as Except @@ -84,6 +92,18 @@ data Store = Store !TheoryId !SQLite.Connection +-- | The single invocation-local gateway for a store connection and its +-- ordinary 'IORef'-backed validation memo. Module checkers may run in +-- parallel, but every SQLite and memo operation remains coordinator-owned. +newtype StoreCoordinator = StoreCoordinator (MVar ()) + +newStoreCoordinator :: IO StoreCoordinator +newStoreCoordinator = StoreCoordinator <$> newMVar () + +withStoreCoordinator :: StoreCoordinator -> IO value -> IO value +withStoreCoordinator (StoreCoordinator ownership) action = + withMVar ownership (const action) + -- | A completely validated, inert cached module. Runtime authority is -- minted only after this value is adopted by 'Checking.Module'. data CachedModuleInstallation = CachedModuleInstallation @@ -157,8 +177,6 @@ data StoreMemo = StoreMemo , memoCheckedObjects :: !(IORef.IORef CheckedObjectClosure) , memoValidatedPropositions :: !(IORef.IORef (Map PropositionId CheckedPropositionContent)) - , memoSemanticInventories :: !(IORef.IORef - (Map SemanticInterfaceId SemanticInventory)) , memoValidatedArtifacts :: !(IORef.IORef (Set ModuleArtifactId)) , memoSyntaxValidationVisits :: !(IORef.IORef Int) , memoSemanticValidationVisits :: !(IORef.IORef Int) @@ -167,8 +185,9 @@ data StoreMemo = StoreMemo , memoArtifactValidationVisits :: !(IORef.IORef Int) } --- Complete aggregate inventories keep validation direct for the current --- walking subset. Measure long chains before adding Phase 6 sharing. +-- Root-scoped validation carries one flat inventory through a deterministic +-- visited fold. Immutable rows and their validation results are memoized +-- above, but complete transitive inventories are not retained per interface. data SemanticInventory = SemanticInventory !(Set SemanticFactOccurrenceFingerprint) !(Map SemanticName SemanticFactOccurrenceFingerprint) @@ -203,7 +222,6 @@ newStoreMemo (Store _path theory _connection) = do validatedSemantic <- IORef.newIORef Set.empty checkedObjects <- IORef.newIORef emptyClosure validatedPropositions <- IORef.newIORef Map.empty - semanticInventories <- IORef.newIORef Map.empty validatedArtifacts <- IORef.newIORef Set.empty syntaxVisits <- IORef.newIORef 0 semanticVisits <- IORef.newIORef 0 @@ -221,7 +239,6 @@ newStoreMemo (Store _path theory _connection) = do , memoValidatedSemantic = validatedSemantic , memoCheckedObjects = checkedObjects , memoValidatedPropositions = validatedPropositions - , memoSemanticInventories = semanticInventories , memoValidatedArtifacts = validatedArtifacts , memoSyntaxValidationVisits = syntaxVisits , memoSemanticValidationVisits = semanticVisits @@ -1654,7 +1671,6 @@ validateModuleArtifactClosure memo store root = do (moduleArtifactResultSemantic artifact) void (validateSemanticInventory - Set.empty (moduleArtifactResultSemantic artifact)) case checked of Left failure -> @@ -1734,6 +1750,8 @@ validateModuleArtifactClosure memo store root = do traverse_ (validateSemantic (Set.insert identity path)) (semanticInterfaceDirectInputs interface) + operationBindings <- + semanticOperationBindings Set.empty identity validateObjectRoots objects closure <- Except.liftIO (IORef.readIORef (memoCheckedObjects memo)) @@ -1745,7 +1763,7 @@ validateModuleArtifactClosure memo store root = do (semanticGlobalBindingKey binding) (semanticGlobalBindingTarget binding)) (validateSemanticGlobalBindingTarget - closure binding))) + operationBindings closure binding))) bindings traverse_ validateProposition propositions traverse_ validateOccurrence @@ -1758,6 +1776,33 @@ validateModuleArtifactClosure memo store root = do (memoValidatedSemantic memo) (Set.insert identity)) + semanticOperationBindings path identity + | identity `Set.member` path = pure Set.empty + | otherwise = do + interface <- requireMemo + SemanticInterfaces + (cacheDigestBytes + (semanticInterfaceIdDigest identity)) + (memoSemantic memo store identity) + inherited <- + traverse + (semanticOperationBindings + (Set.insert identity path)) + (semanticInterfaceDirectInputs interface) + let local = + Set.fromList + [ ( semanticStructureOperationSymbol operation + , semanticStructureOperationObject operation + ) + | declaration <- + semanticInterfaceDeclarations interface + , descriptor <- semanticEnvironmentStructures + (declarationDeltaEnvironment declaration) + , operation <- + semanticStructureDescriptorOperations descriptor + ] + pure (Set.unions (local : inherited)) + validateObjectRoots identities = do closure <- Except.liftIO (IORef.readIORef (memoCheckedObjects memo)) @@ -1843,83 +1888,57 @@ validateModuleArtifactClosure memo store root = do proposition (semanticFactAuthority occurrence)))) - validateSemanticInventory path identity = do - inventories <- Except.liftIO - (IORef.readIORef (memoSemanticInventories memo)) - case Map.lookup identity inventories of - Just inventory -> - pure inventory - Nothing - | identity `Set.member` path -> - pure - (SemanticInventory - Set.empty Map.empty Map.empty) - | otherwise -> do - interface <- requireMemo - SemanticInterfaces - (cacheDigestBytes - (semanticInterfaceIdDigest identity)) - (memoSemantic memo store identity) - parents <- traverse - (validateSemanticInventory - (Set.insert identity path)) - (semanticInterfaceDirectInputs interface) - SemanticInventory - parentFacts parentAliases parentGlobals <- - foldM mergeInventory - (SemanticInventory - Set.empty Map.empty Map.empty) - parents - let declarations = - semanticInterfaceDeclarations interface - localFacts = Set.fromList - [ semanticFactFingerprint occurrence - | declaration <- declarations - , occurrence <- declarationDeltaFacts declaration - ] - visibleFacts = parentFacts <> localFacts - aliases <- foldM - (insertAlias visibleFacts) - parentAliases - [ alias - | declaration <- declarations - , alias <- declarationDeltaAliases declaration - ] - globals <- foldM - insertGlobal - parentGlobals - [ binding - | declaration <- declarations - , binding <- semanticEnvironmentBindings - (declarationDeltaEnvironment declaration) - ] - let inventory = - SemanticInventory - visibleFacts aliases globals - Except.liftIO - (IORef.modifyIORef' - (memoSemanticInventories memo) - (Map.insert identity inventory)) - pure inventory - - mergeInventory - (SemanticInventory facts aliases globals) - (SemanticInventory moreFacts moreAliases moreGlobals) = do - merged <- foldM - (\current (name, target) -> - insertNamedAlias name target current) - aliases - (Map.toAscList moreAliases) - mergedGlobals <- foldM - (\current (key, target) -> - insertGlobalBinding key target current) - globals - (Map.toAscList moreGlobals) - pure - (SemanticInventory - (facts <> moreFacts) - merged - mergedGlobals) + validateSemanticInventory identity = + snd + <$> foldSemanticInventory + Set.empty + (SemanticInventory Set.empty Map.empty Map.empty) + identity + + foldSemanticInventory visited inventory identity + | identity `Set.member` visited = + pure (visited, inventory) + | otherwise = do + interface <- requireMemo + SemanticInterfaces + (cacheDigestBytes + (semanticInterfaceIdDigest identity)) + (memoSemantic memo store identity) + (parentVisited, parentInventory) <- + foldM + (\(seen, current) parent -> + foldSemanticInventory seen current parent) + (Set.insert identity visited, inventory) + (semanticInterfaceDirectInputs interface) + let declarations = semanticInterfaceDeclarations interface + SemanticInventory + parentFacts parentAliases parentGlobals = + parentInventory + localFacts = Set.fromList + [ semanticFactFingerprint occurrence + | declaration <- declarations + , occurrence <- declarationDeltaFacts declaration + ] + visibleFacts = parentFacts <> localFacts + aliases <- foldM + (insertAlias visibleFacts) + parentAliases + [ alias + | declaration <- declarations + , alias <- declarationDeltaAliases declaration + ] + globals <- foldM + insertGlobal + parentGlobals + [ binding + | declaration <- declarations + , binding <- semanticEnvironmentBindings + (declarationDeltaEnvironment declaration) + ] + pure + ( parentVisited + , SemanticInventory visibleFacts aliases globals + ) insertGlobal globals binding = insertGlobalBinding |
