module Check.Core ( ImportedView(..) , importedViewsFromResolvedModules , importedViewsFromResolvedModulesEither , checkProgramWithEnvAndImportedViews , checkSourceWithEnv , checkSourceWithEnvAndImportedViews , lowerSource , lowerSourceWithDebug , lowerSourceWithImportedViews , lowerSourceWithImportedViewsDebug , lowerViewExpr ) where import Control.Applicative ((<|>)) import Control.Monad.State.Strict import Data.Char (isDigit) import Data.Maybe (mapMaybe) import qualified Data.Map as Map import qualified Data.Set as Set import qualified Data.Text as T import ContentStore.Alias (ObjectRef(..)) import Eval (evalTricu, result) import Module.Resolver ( ResolvedExport(..) , ResolvedModule(..) ) import Parser (parseTricu) import Research data ImportedView = ImportedView { importedViewName :: String , importedViewType :: ViewType , importedViewProvenance :: ViewProvenance } deriving (Show, Eq) -- Convert module-resolution metadata into checker evidence inputs. The loader -- decodes a portable view artifact into a syntactic ViewType, but this function -- does not judge compatibility or policy. It only says: this resolved imported -- name has an advertised view fact that should be emitted into the typed program. importedViewsFromResolvedModules :: (ObjectRef -> IO (Maybe ViewType)) -> [ResolvedModule] -> IO [ImportedView] importedViewsFromResolvedModules loadView = importedViewsFromResolvedModulesEither loadViewEither where loadViewEither ref = do mView <- loadView ref pure $ maybe (Left "artifact not found or could not be decoded") Right mView importedViewsFromResolvedModulesEither :: (ObjectRef -> IO (Either String ViewType)) -> [ResolvedModule] -> IO [ImportedView] importedViewsFromResolvedModulesEither loadView modules = concat <$> mapM fromModule modules where fromModule m = concat <$> mapM fromExport (resolvedModuleExports m) fromExport ex = case resolvedExportView ex of Nothing -> pure [] Just ref -> do eView <- loadView ref case eView of Left err -> errorWithoutStackTrace $ "View Contract artifact invalid for imported export " ++ show (resolvedExportLocalName ex) ++ " (kind " ++ showRefKind ref ++ ", hash " ++ showRefHash ref ++ "): " ++ err Right view -> pure [ImportedView (resolvedExportLocalName ex) view (maybe ViewUnchecked id (resolvedExportProvenance ex))] showRefKind = T.unpack . objectRefKind showRefHash = T.unpack . objectRefHash checkSourceWithEnv :: Env -> String -> IO String checkSourceWithEnv checkerEnv = checkSourceWithEnvAndImportedViews checkerEnv [] checkSourceWithEnvAndImportedViews :: Env -> [ImportedView] -> String -> IO String checkSourceWithEnvAndImportedViews checkerEnv imports source = checkProgramWithEnvAndImportedViews checkerEnv imports (parseTricu source) checkProgramWithEnvAndImportedViews :: Env -> [ImportedView] -> [TricuAST] -> IO String checkProgramWithEnvAndImportedViews checkerEnv imports asts = do case lowerProgramWithImportedViewsDebugInEnv checkerEnv imports asts of Left err -> pure err Right (typedProgramSource, debugNames) -> do let input = "matchResult " ++ "(diag env : renderDiagnostic diag) " ++ "(exec env : matchResult (runtimeDiag runtimeEnv : renderDiagnostic runtimeDiag) (_ runtimeEnv : \"ok\") (runChecked exec)) " ++ "(checkTypedProgramWith policyStrict " ++ parens typedProgramSource ++ ")" let env = evalTricu checkerEnv (parseTricu input) pure $ case toString (result env) of Right s -> annotateDiagnostic debugNames s Left _ -> formatT Decode (result env) -- Debug names are a frontend-only side table. The portable checker renders -- canonical numeric-symbol diagnostics; the CLI annotates that presentation -- afterward without feeding labels back into checker semantics. annotateDiagnostic :: Map.Map Integer String -> String -> String annotateDiagnostic debugNames message = case words message of ("symbol" : symText : rest) | all isDigit symText , Just label <- Map.lookup (read symText) debugNames -> "symbol " ++ symText ++ " (" ++ label ++ ") " ++ unwords rest _ -> message viewExprHasParametricBinder :: ViewExpr -> Bool viewExprHasParametricBinder expr = case expr of VEVar _ -> True VEVarId _ -> True VEList items -> any viewExprHasParametricBinder items VEApp fn arg -> viewExprHasParametricBinder fn || viewExprHasParametricBinder arg VEForall binders body -> not (null binders) || viewExprHasParametricBinder body VEExists binders body -> not (null binders) || viewExprHasParametricBinder body VEName _ -> False VEInt _ -> False VEString _ -> False VERaw _ -> False rawTaintedDefinitions :: Set.Set String -> [TricuAST] -> Map.Map String String rawTaintedDefinitions allowedExternalFacts asts = fixedPoint initiallyRaw where allowedFacts = allowedExternalFacts definitions = Map.fromList [ (name, (args, body)) | ast <- asts , Just (name, args, body) <- [definitionBody ast] ] localNames = Map.keysSet definitions initiallyRaw = Map.mapMaybeWithKey (\name (args, body) -> if name `Set.member` allowedFacts then Nothing else definitionUnsafeBaseReason localNames allowedFacts (Set.fromList args) body) definitions fixedPoint tainted = let tainted' = Map.mapMaybeWithKey (transitiveReason tainted) definitions combined = Map.union tainted tainted' in if combined == tainted then tainted else fixedPoint combined transitiveReason tainted name (args, body) | name `Map.member` tainted = Nothing | name `Set.member` allowedFacts = Nothing | otherwise = case filter (`Map.member` tainted) (astFreeRefs (foldr Set.delete localNames args) body) of helper : _ -> Just $ "depends on raw-tainted local helper " ++ show helper ++ " (" ++ tainted Map.! helper ++ ")" [] -> Nothing definitionBody ast = case ast of SDef name args body -> Just (name, args, body) SDefAnn name args _ body -> Just (name, defArgNames args, body) _ -> Nothing definitionUnsafeBaseReason :: Set.Set String -> Set.Set String -> Set.Set String -> TricuAST -> Maybe String definitionUnsafeBaseReason localNames allowedExternalFacts bound ast = case ast of SVar name _ | name `Set.member` bound -> Nothing | name `Set.member` localNames -> Nothing | name `Set.member` allowedExternalFacts -> Nothing | name == "triage" -> Just "uses raw triage directly" | otherwise -> Just $ "depends on unchecked or unknown external name " ++ show name SInt _ -> Nothing SStr _ -> Nothing SList items -> firstJust (map (definitionUnsafeBaseReason localNames allowedExternalFacts bound) items) SDef _ args body -> definitionUnsafeBaseReason localNames allowedExternalFacts (foldr Set.insert bound args) body SDefAnn _ args _ body -> definitionUnsafeBaseReason localNames allowedExternalFacts (foldr Set.insert bound (defArgNames args)) body SApp fn arg -> definitionUnsafeBaseReason localNames allowedExternalFacts bound fn <|> definitionUnsafeBaseReason localNames allowedExternalFacts bound arg TLeaf -> Just "uses raw t directly" TStem _ -> Just "uses raw t directly" TFork _ _ -> Just "uses raw t directly" SLambda args body -> definitionUnsafeBaseReason localNames allowedExternalFacts (foldr Set.insert bound args) body SEmpty -> Nothing SImport _ _ -> Nothing firstJust :: [Maybe a] -> Maybe a firstJust [] = Nothing firstJust (Just x : _) = Just x firstJust (Nothing : xs) = firstJust xs astFreeRefs :: Set.Set String -> TricuAST -> [String] astFreeRefs candidates ast = case ast of SVar name _ | name `Set.member` candidates -> [name] SVar _ _ -> [] SInt _ -> [] SStr _ -> [] SList items -> concatMap (astFreeRefs candidates) items SDef _ args body -> astFreeRefs (foldr Set.delete candidates args) body SDefAnn _ args _ body -> astFreeRefs (foldr Set.delete candidates (defArgNames args)) body SApp fn arg -> astFreeRefs candidates fn ++ astFreeRefs candidates arg TLeaf -> [] TStem inner -> astFreeRefs candidates inner TFork left right -> astFreeRefs candidates left ++ astFreeRefs candidates right SLambda args body -> astFreeRefs (foldr Set.delete candidates args) body SEmpty -> [] SImport _ _ -> [] defArgNames :: [DefArg] -> [String] defArgNames = mapMaybe defArgName where defArgName (DefBinder name _) = Just name defArgName (DefPhantom _) = Nothing lowerSource :: String -> Either String String lowerSource = lowerProgram . parseTricu lowerSourceWithDebug :: String -> Either String (String, Map.Map Integer String) lowerSourceWithDebug = lowerProgramWithDebug . parseTricu lowerSourceWithImportedViews :: [ImportedView] -> String -> Either String String lowerSourceWithImportedViews imports = lowerProgramWithImportedViews imports . parseTricu lowerSourceWithImportedViewsDebug :: [ImportedView] -> String -> Either String (String, Map.Map Integer String) lowerSourceWithImportedViewsDebug imports = lowerProgramWithImportedViewsDebug imports . parseTricu -- Symbol allocation is intentionally deterministic so emitted view-tree -- nodes are stable and lower-only tests can inspect them directly: -- -- * top-level definitions receive symbols 0..n-1 in source order; -- * local binders, literals, application results, and synthetic typed nodes -- are allocated monotonically from nextSym; -- * external names are allocated on first reference and then reused. -- -- Symbols are view-tree node identifiers only. Checker semantics remain in -- lib/view.tri; the frontend only emits typed/checkable structure about these -- symbols. data LowerState = LowerState { nextSym :: Integer , topSyms :: Map.Map String Integer , scopes :: [Map.Map String Integer] , externSyms :: Map.Map String Integer , knownNodeViews :: Map.Map Integer ViewExpr , nodePayloads :: Map.Map Integer T , debugNames :: Map.Map Integer String , rawTaintedDefs :: Map.Map String String } type LowerM a = StateT LowerState (Either String) a lowerProgram :: [TricuAST] -> Either String String lowerProgram asts = fst <$> lowerProgramWithDebug asts lowerProgramWithDebug :: [TricuAST] -> Either String (String, Map.Map Integer String) lowerProgramWithDebug = lowerProgramWithImportedViewsDebug [] lowerProgramWithImportedViews :: [ImportedView] -> [TricuAST] -> Either String String lowerProgramWithImportedViews imports asts = fst <$> lowerProgramWithImportedViewsDebug imports asts lowerProgramWithImportedViewsDebug :: [ImportedView] -> [TricuAST] -> Either String (String, Map.Map Integer String) lowerProgramWithImportedViewsDebug = lowerProgramWithImportedViewsDebugInEnv Map.empty lowerProgramWithImportedViewsDebugInEnv :: Env -> [ImportedView] -> [TricuAST] -> Either String (String, Map.Map Integer String) lowerProgramWithImportedViewsDebugInEnv checkerEnvForLowering imports asts = do let definitions = [ def | def <- asts, isDefinition def ] topNames = map definitionName definitions tops = Map.fromList (zip topNames [0..]) topCount = Map.size tops importCandidates = Set.fromList (map importedViewName imports) `Set.difference` Set.fromList topNames usedImportNames = Set.fromList (concatMap (astFreeRefs importCandidates) asts) activeImports = filter (\imported -> importedViewName imported `Set.member` usedImportNames) imports importedSyms = Map.fromList [ (importedViewName imported, fromIntegral (topCount + idx)) | (idx, imported) <- zip [0..] activeImports ] topDebug = Map.fromList [ (sym, name) | (name, sym) <- Map.toList tops ] importDebug = Map.fromList [ (sym, "imported " ++ name) | (name, sym) <- Map.toList importedSyms ] localFactByName = Map.fromList [(importedViewName imported, imported) | imported <- imports, importedViewName imported `elem` topNames] trustedLocalFacts = [ (sym, viewTypeToExpr (importedViewType imported), importedViewProvenance imported) | (name, sym) <- Map.toList tops , Just imported <- [Map.lookup name localFactByName] , importedViewProvenance imported `elem` [ViewChecked, ViewTrusted] ] trustedLocalKnown = Map.fromList [(sym, view) | (sym, view, _) <- trustedLocalFacts] importKnown = Map.fromList [ (sym, viewTypeToExpr (importedViewType imported)) | imported <- activeImports , Just sym <- [Map.lookup (importedViewName imported) importedSyms] ] payloads = Map.fromList $ [ (sym, term) | (name, sym) <- Map.toList tops , Just term <- [Map.lookup name checkerEnvForLowering] ] ++ [ (sym, term) | (name, sym) <- Map.toList importedSyms , Just term <- [Map.lookup name checkerEnvForLowering] ] annotated = [ def | def@SDefAnn {} <- asts ] allowedExternalFacts = Set.fromList [ importedViewName imported | imported <- imports , importedViewProvenance imported `elem` [ViewChecked, ViewTrusted] ] taintedDefs = rawTaintedDefinitions allowedExternalFacts asts initialState = LowerState { nextSym = fromIntegral (Map.size tops + Map.size importedSyms) , topSyms = tops , scopes = [] , externSyms = importedSyms , knownNodeViews = Map.union trustedLocalKnown importKnown , nodePayloads = payloads , debugNames = Map.union topDebug importDebug , rawTaintedDefs = taintedDefs } (localNodes, finalState) <- runStateT (lowerAnnotatedProgram annotated) initialState trustedLocalNodes <- mapM (lowerImportedView (nodePayloads finalState)) trustedLocalFacts importNodes <- mapM (lowerImportedView (nodePayloads finalState)) [ (sym, viewTypeToExpr (importedViewType imported), importedViewProvenance imported) | imported <- activeImports , Just sym <- [Map.lookup (importedViewName imported) importedSyms] ] let nodes = trustedLocalNodes ++ importNodes ++ localNodes rootSym = if null nodes then 0 else nextSym finalState - 1 typedProgramSource = "typedProgram " ++ show rootSym ++ " [" ++ unwords (map parens nodes) ++ "]" pure (typedProgramSource, debugNames finalState) lowerImportedView :: Map.Map Integer T -> (Integer, ViewExpr, ViewProvenance) -> Either String String lowerImportedView payloadsBySym (sym, view, provenance) = do viewExpr <- lowerViewExpr view let payload = maybe "t" treeSource (Map.lookup sym payloadsBySym) pure $ "typedValueWithProvenance " ++ show sym ++ " " ++ parens viewExpr ++ " " ++ payload ++ " " ++ viewProvenanceSource provenance lowerAnnotatedProgram :: [TricuAST] -> LowerM [String] lowerAnnotatedProgram defs = do declarations <- concat <$> mapM lowerDefinitionDeclaration defs flows <- concat <$> mapM lowerDefinitionFlow defs pure (declarations ++ flows) lowerDefinitionDeclaration :: TricuAST -> LowerM [String] lowerDefinitionDeclaration (SDefAnn name args ret _) = do let (_, _, declaredView) = canonicalDefinitionViews args ret tainted <- gets rawTaintedDefs if viewExprHasParametricBinder declaredView && name `Map.member` tainted then liftEither (Left $ "parametric View definition " ++ show name ++ " depends on raw intensional Tree Calculus machinery (" ++ tainted Map.! name ++ "); use a trusted eliminator boundary instead") else do sym <- symbolForTop name recordKnown sym declaredView node <- typedValueNode sym declaredView pure [node] lowerDefinitionDeclaration _ = liftEither (Left "internal check error: expected annotated definition") lowerDefinitionFlow :: TricuAST -> LowerM [String] lowerDefinitionFlow (SDefAnn _ args ret body) = withDefinitionScope args $ do let (flowArgs, flowRet, _) = canonicalDefinitionViews args ret binderNodes <- concat <$> mapM lowerBinderDeclaration flowArgs let phantomViews = map lowerPhantomArgType (phantomArgs flowArgs) (returnArgs, returnResult) <- lowerReturnObligation flowRet bodyNodes <- lowerBodyWithPhantoms (phantomViews ++ returnArgs) returnResult body pure (binderNodes ++ bodyNodes) lowerDefinitionFlow _ = liftEither (Left "internal check error: expected annotated definition") viewAnyType :: ViewExpr viewAnyType = VEName "Any" canonicalDefinitionViews :: [DefArg] -> Maybe ViewExpr -> ([DefArg], Maybe ViewExpr, ViewExpr) canonicalDefinitionViews args ret = let rawView = declaredDefinitionView args ret vars = Set.toList (freeViewVars rawView) binderIds = zip vars [0..] binderMap = Map.fromList binderIds mappedArgs = map (mapDefArgView (rewriteViewVars binderMap)) args mappedRet = fmap (rewriteViewVars binderMap) ret mappedView = declaredDefinitionView mappedArgs mappedRet binders = map snd binderIds declaredView = if null vars then mappedView else VEForall binders mappedView in (mappedArgs, mappedRet, declaredView) declaredDefinitionView :: [DefArg] -> Maybe ViewExpr -> ViewExpr declaredDefinitionView args ret = case map argType args of [] -> resultType views -> viewExprFn views resultType where resultType = maybe viewAnyType id ret mapDefArgView :: (ViewExpr -> ViewExpr) -> DefArg -> DefArg mapDefArgView f (DefBinder name mTy) = DefBinder name (fmap f mTy) mapDefArgView f (DefPhantom ty) = DefPhantom (f ty) argType :: DefArg -> ViewExpr argType (DefBinder _ Nothing) = viewAnyType argType (DefBinder _ (Just ty)) = ty argType (DefPhantom ty) = ty emitDeclaration :: Integer -> [String] -> String -> LowerM String emitDeclaration sym [] retExpr = do payload <- payloadSourceFor sym pure $ "typedValue " ++ show sym ++ " " ++ parens retExpr ++ " " ++ payload emitDeclaration sym views retExpr = do payload <- payloadSourceFor sym pure $ "typedValue " ++ show sym ++ " (viewFn [" ++ unwords (map parens views) ++ "] " ++ parens retExpr ++ ") " ++ payload typedValueNode :: Integer -> ViewExpr -> LowerM String typedValueNode sym view = typedValueNodeWithProvenance sym view ViewChecked typedValueNodeWithProvenance :: Integer -> ViewExpr -> ViewProvenance -> LowerM String typedValueNodeWithProvenance sym view provenance = do viewExpr <- liftEither (lowerViewExpr view) payload <- payloadSourceFor sym pure ("typedValueWithProvenance " ++ show sym ++ " " ++ parens viewExpr ++ " " ++ payload ++ " " ++ viewProvenanceSource provenance) typedRequireNode :: Integer -> ViewExpr -> LowerM String typedRequireNode sym view = do viewExpr <- liftEither (lowerViewExpr view) payload <- payloadSourceFor sym pure ("typedRequire " ++ show sym ++ " " ++ parens viewExpr ++ " " ++ payload) viewProvenanceSource :: ViewProvenance -> String viewProvenanceSource ViewChecked = "viewProvenanceChecked" viewProvenanceSource ViewTrusted = "viewProvenanceTrusted" viewProvenanceSource ViewUnchecked = "viewProvenanceUnchecked" declareKnown :: Integer -> ViewExpr -> LowerM String declareKnown sym view = do recordKnown sym view typedValueNode sym view declareKnownWithPayload :: Integer -> ViewExpr -> T -> LowerM String declareKnownWithPayload sym view payload = do recordPayload sym payload declareKnown sym view declareKnownFresh :: ViewExpr -> LowerM (Integer, [String]) declareKnownFresh view = do sym <- freshSym node <- declareKnown sym view pure (sym, [node]) declareKnownFreshWithPayload :: ViewExpr -> T -> LowerM (Integer, [String]) declareKnownFreshWithPayload view payload = do sym <- freshSym node <- declareKnownWithPayload sym view payload pure (sym, [node]) declareAndRequireFresh :: ViewExpr -> LowerM (Integer, [String]) declareAndRequireFresh view = do sym <- freshSym declareNode <- declareKnown sym view requireNode <- typedRequireNode sym view pure (sym, [declareNode, requireNode]) declareAndRequireFreshWithPayload :: ViewExpr -> T -> LowerM (Integer, [String]) declareAndRequireFreshWithPayload view payload = do sym <- freshSym declareNode <- declareKnownWithPayload sym view payload requireNode <- typedRequireNode sym view pure (sym, [declareNode, requireNode]) lowerBinderDeclaration :: DefArg -> LowerM [String] lowerBinderDeclaration (DefBinder name mTy) = do sym <- symbolForLocal name node <- declareKnown sym (maybe viewAnyType id mTy) pure [node] lowerBinderDeclaration (DefPhantom _) = pure [] lowerBodyWithPhantoms :: [ViewExpr] -> ViewExpr -> TricuAST -> LowerM [String] lowerBodyWithPhantoms [] _ SLambda {} = pure [] lowerBodyWithPhantoms [] expected body = lowerExprAgainst body expected lowerBodyWithPhantoms phantomViews expected (SLambda params body) = lowerLambdaSpine phantomViews expected params body lowerBodyWithPhantoms phantomViews expected body = lowerExprAgainst body (residualViewExpr phantomViews expected) lowerLambdaSpine :: [ViewExpr] -> ViewExpr -> [String] -> TricuAST -> LowerM [String] lowerLambdaSpine phantomViews expected [] body = lowerBodyWithPhantoms phantomViews expected body lowerLambdaSpine [] _ _ _ = pure [] lowerLambdaSpine (view : views) expected (param : params) body = withLocalBinder param $ \paramSym -> do declareParam <- declareKnown paramSym view restNodes <- lowerLambdaSpine views expected params body pure (declareParam : restNodes) residualViewExpr :: [ViewExpr] -> ViewExpr -> ViewExpr residualViewExpr [] resultView = resultView residualViewExpr args resultView = viewExprFn args resultView phantomArgs :: [DefArg] -> [DefArg] phantomArgs [] = [] phantomArgs (DefPhantom ty : rest) = DefPhantom ty : phantomArgs rest phantomArgs (_ : rest) = phantomArgs rest lowerPhantomArgType :: DefArg -> ViewExpr lowerPhantomArgType (DefPhantom ty) = ty lowerPhantomArgType _ = error "internal check error: expected phantom arg" lowerReturnObligation :: Maybe ViewExpr -> LowerM ([ViewExpr], ViewExpr) lowerReturnObligation Nothing = pure ([], viewAnyType) lowerReturnObligation (Just ty) = pure (peelFnObligation ty) peelFnObligation :: ViewExpr -> ([ViewExpr], ViewExpr) peelFnObligation ty = case viewExprFnParts ty of Just (args, resultView) -> let (restArgs, finalResult) = peelFnObligation resultView in (args ++ restArgs, finalResult) Nothing -> ([], ty) withDefinitionScope :: [DefArg] -> LowerM a -> LowerM a withDefinitionScope args action = do binderEntries <- mapM allocateBinder [ name | DefBinder name _ <- args ] modify $ \st -> st { scopes = Map.fromList binderEntries : scopes st } resultValue <- action modify $ \st -> st { scopes = drop 1 (scopes st) } pure resultValue allocateBinder :: String -> LowerM (String, Integer) allocateBinder name = do sym <- freshSym recordDebugName sym name pure (name, sym) withLocalBinder :: String -> (Integer -> LowerM a) -> LowerM a withLocalBinder name action = do sym <- freshSym recordDebugName sym name withLocalAlias name sym (action sym) withLocalAlias :: String -> Integer -> LowerM a -> LowerM a withLocalAlias name sym action = do modify $ \st -> st { scopes = Map.singleton name sym : scopes st } resultValue <- action modify $ \st -> st { scopes = drop 1 (scopes st) } pure resultValue recordKnown :: Integer -> ViewExpr -> LowerM () recordKnown sym view = modify $ \st -> st { knownNodeViews = Map.insert sym view (knownNodeViews st) } recordPayload :: Integer -> T -> LowerM () recordPayload sym payload = modify $ \st -> st { nodePayloads = Map.insert sym payload (nodePayloads st) } payloadFor :: Integer -> LowerM (Maybe T) payloadFor sym = do st <- get pure (Map.lookup sym (nodePayloads st)) payloadSourceFor :: Integer -> LowerM String payloadSourceFor sym = maybe "t" treeSource <$> payloadFor sym knownNodeViewFor :: Integer -> LowerM (Maybe ViewExpr) knownNodeViewFor sym = do st <- get pure (Map.lookup sym (knownNodeViews st)) recordDebugName :: Integer -> String -> LowerM () recordDebugName sym label = modify $ \st -> st { debugNames = Map.insertWith keepExisting sym label (debugNames st) } where keepExisting _ old = old lowerExpr :: TricuAST -> LowerM (Integer, [String]) lowerExpr expr = do (sym, nodes, _) <- lowerExprKnown expr pure (sym, nodes) lowerExprAgainst :: TricuAST -> ViewExpr -> LowerM [String] lowerExprAgainst body expected = do (_, nodes, _) <- lowerExprKnownAgainst body expected pure nodes lowerExprKnownAgainst :: TricuAST -> ViewExpr -> LowerM (Integer, [String], Maybe ViewExpr) lowerExprKnownAgainst expr expected = case (expr, viewExprAsType expected) of (SApp (SApp (SVar "pair" _) left) right, Just (VTPair leftView rightView)) -> let leftExpr = viewTypeToExpr leftView rightExpr = viewTypeToExpr rightView in lowerUnshadowedConstructor "pair" expr expected $ do (_, leftNodes, _) <- lowerExprKnownAgainst left leftExpr (_, rightNodes, _) <- lowerExprKnownAgainst right rightExpr (sym, nodes) <- declareAndRequireFresh expected pure (sym, leftNodes ++ rightNodes ++ nodes, Just expected) (SApp (SVar "just" _) value, Just (VTMaybe elemView)) -> let elemExpr = viewTypeToExpr elemView in lowerUnshadowedConstructor "just" expr expected $ do (_, valueNodes, _) <- lowerExprKnownAgainst value elemExpr (sym, nodes) <- declareAndRequireFresh expected pure (sym, valueNodes ++ nodes, Just expected) (SVar "nothing" _, Just (VTMaybe _)) -> lowerUnshadowedConstructor "nothing" expr expected $ do (sym, nodes) <- declareAndRequireFresh expected pure (sym, nodes, Just expected) (SApp (SApp (SVar "ok" _) value) rest, Just (VTResult _ okView)) -> lowerUnshadowedConstructor "ok" expr expected $ lowerResultConstructor expected (viewTypeToExpr okView) value rest (SApp (SApp (SVar "err" _) value) rest, Just (VTResult errView _)) -> lowerUnshadowedConstructor "err" expr expected $ lowerResultConstructor expected (viewTypeToExpr errView) value rest (SApp (SLambda [name] body) value, _) -> do (valueSym, valueNodes, _) <- lowerExprKnown value bodyResult <- withLocalAlias name valueSym (lowerExprKnownAgainst body expected) let (bodySym, bodyNodes, bodyKnown) = bodyResult pure (bodySym, valueNodes ++ bodyNodes, bodyKnown) (SList items, Just (VTList elemView)) -> do let elemExpr = viewTypeToExpr elemView lowered <- mapM (`lowerExprKnownAgainst` elemExpr) items let itemNodes = concat [ nodes | (_, nodes, _) <- lowered ] (sym, nodes) <- declareAndRequireFresh expected pure (sym, itemNodes ++ nodes, Just expected) (SLambda _ _, _) -> case peelFnObligation expected of ([], _) -> lowerExprKnownAndRequire expr expected (argViews, resultView) -> lowerLambdaAgainst argViews resultView expr _ -> lowerExprKnownAndRequire expr expected lowerUnshadowedConstructor :: String -> TricuAST -> ViewExpr -> LowerM (Integer, [String], Maybe ViewExpr) -> LowerM (Integer, [String], Maybe ViewExpr) lowerUnshadowedConstructor name fallback expected lowerCtor = do ctorIsUnbound <- nameIsUnbound name if ctorIsUnbound then lowerCtor else lowerExprKnownAndRequire fallback expected lowerResultConstructor :: ViewExpr -> ViewExpr -> TricuAST -> TricuAST -> LowerM (Integer, [String], Maybe ViewExpr) lowerResultConstructor expected valueView value rest = do (_, valueNodes, _) <- lowerExprKnownAgainst value valueView (_, restNodes, _) <- lowerExprKnown rest (sym, nodes) <- declareAndRequireFresh expected pure (sym, valueNodes ++ restNodes ++ nodes, Just expected) lowerExprKnownAndRequire :: TricuAST -> ViewExpr -> LowerM (Integer, [String], Maybe ViewExpr) lowerExprKnownAndRequire body expected = do (bodySym, bodyNodes, known) <- lowerExprKnown body requireNode <- typedRequireNode bodySym expected pure (bodySym, bodyNodes ++ [requireNode], known) lowerLambdaAgainst :: [ViewExpr] -> ViewExpr -> TricuAST -> LowerM (Integer, [String], Maybe ViewExpr) lowerLambdaAgainst argViews resultView (SLambda params body) = do nodes <- lowerLambdaSpine argViews resultView params body sym <- freshSym let fnView = residualViewExpr argViews resultView declareNode <- declareKnown sym fnView pure (sym, nodes ++ [declareNode], Just fnView) lowerLambdaAgainst argViews resultView body = lowerExprKnownAndRequire body (residualViewExpr argViews resultView) lowerExprKnown :: TricuAST -> LowerM (Integer, [String], Maybe ViewExpr) lowerExprKnown (SVar name _) = do sym <- symbolForName name known <- knownNodeViewFor sym pure (sym, [], known) lowerExprKnown (SStr s) = do let view = VEName "String" (sym, nodes) <- declareKnownFreshWithPayload view (ofString s) recordDebugName sym "string literal" pure (sym, nodes, Just view) lowerExprKnown (SInt n) | n >= 0 && n <= 255 = do let view = VEName "Byte" (sym, nodes) <- declareKnownFreshWithPayload view (ofNumber n) recordDebugName sym "byte literal" pure (sym, nodes, Just view) | otherwise = do sym <- freshSym pure (sym, [], Nothing) lowerExprKnown TLeaf = do let view = VEName "Unit" (sym, nodes) <- declareKnownFreshWithPayload view Leaf recordDebugName sym "unit literal" pure (sym, nodes, Just view) lowerExprKnown (SList items) = do (sym, nodes, view, _) <- lowerListLiteral items pure (sym, nodes, Just view) lowerExprKnown (SApp (SLambda [name] body) value) = do (valueSym, valueNodes, known) <- lowerExprKnown value bodyResult <- withLocalAlias name valueSym (lowerExprKnown body) let (bodySym, bodyNodes, bodyKnown) = bodyResult pure (bodySym, valueNodes ++ bodyNodes, bodyKnown) lowerExprKnown (SApp func arg) = do (funcSym, funcNodes, funcKnown) <- lowerExprKnown func (argSym, argNodes, _) <- lowerApplicationArgument funcKnown arg outSym <- freshSym recordDebugName outSym (applicationDebugLabel func) funcPayload <- payloadFor funcSym argPayload <- payloadFor argSym case (funcPayload, argPayload) of (Just f, Just a) -> recordPayload outSym (apply f a) _ -> pure () applyPayload <- payloadSourceFor outSym let applyNode = "typedApply " ++ show outSym ++ " " ++ show funcSym ++ " " ++ show argSym ++ " " ++ applyPayload outKnown = applicationResultView funcKnown mapM_ (recordKnown outSym) outKnown pure (outSym, funcNodes ++ argNodes ++ [applyNode], outKnown) lowerExprKnown (SLambda params body) = do nodes <- lowerUnannotatedLambda params body sym <- freshSym pure (sym, nodes, Nothing) lowerExprKnown _ = do sym <- freshSym pure (sym, [], Nothing) lowerListLiteral :: [TricuAST] -> LowerM (Integer, [String], ViewExpr, [Integer]) lowerListLiteral items = do lowered <- mapM lowerExprKnown items let itemSyms = [ itemSym | (itemSym, _, _) <- lowered ] itemNodes = concat [ nodes | (_, nodes, _) <- lowered ] view = listLiteralView [ known | (_, _, known) <- lowered ] itemPayloads <- mapM payloadFor itemSyms let mPayload = ofList <$> sequence itemPayloads (sym, declareNodes) <- case mPayload of Just payload -> declareKnownFreshWithPayload view payload Nothing -> declareKnownFresh view pure (sym, itemNodes ++ declareNodes, view, itemSyms) lowerApplicationArgument :: Maybe ViewExpr -> TricuAST -> LowerM (Integer, [String], Maybe ViewExpr) lowerApplicationArgument (Just fnView) arg = case viewExprFnParts fnView of Just (argView : _, _) | containsViewVar argView -> lowerExprKnown arg | otherwise -> lowerExprKnownAgainst arg argView _ -> lowerExprKnown arg lowerApplicationArgument _ arg = lowerExprKnown arg containsViewVar :: ViewExpr -> Bool containsViewVar view = case view of VEVar _ -> True VEVarId _ -> True VEList items -> any containsViewVar items VEApp f a -> containsViewVar f || containsViewVar a VEForall _ body -> containsViewVar body VEExists _ body -> containsViewVar body _ -> False applicationDebugLabel :: TricuAST -> String applicationDebugLabel func = case applicationHeadName func of Just name -> name ++ " application result" Nothing -> "application result" applicationHeadName :: TricuAST -> Maybe String applicationHeadName (SVar name _) = Just name applicationHeadName (SApp func _) = applicationHeadName func applicationHeadName _ = Nothing applicationResultView :: Maybe ViewExpr -> Maybe ViewExpr applicationResultView (Just fnView) = case viewExprFnParts fnView of Just (_ : restArgs, resultView) -> Just $ case restArgs of [] -> resultView _ -> viewExprFn restArgs resultView _ -> Nothing applicationResultView _ = Nothing listLiteralView :: [Maybe ViewExpr] -> ViewExpr listLiteralView [] = viewExprList viewAnyType listLiteralView (Just firstView : rest) | all (== Just firstView) rest = viewExprList firstView listLiteralView _ = viewExprList viewAnyType lowerUnannotatedLambda :: [String] -> TricuAST -> LowerM [String] lowerUnannotatedLambda [] body = do (_, nodes) <- lowerExpr body pure nodes lowerUnannotatedLambda (param : params) body = withLocalBinder param $ \paramSym -> do declareParam <- declareKnown paramSym viewAnyType restNodes <- lowerUnannotatedLambda params body pure (declareParam : restNodes) symbolForTop :: String -> LowerM Integer symbolForTop name = do st <- get case Map.lookup name (topSyms st) of Just sym -> pure sym Nothing -> liftEither (Left $ "internal check error: missing top-level symbol: " ++ name) symbolForLocal :: String -> LowerM Integer symbolForLocal name = do st <- get case lookupInScopes name (scopes st) of Just sym -> pure sym Nothing -> liftEither (Left $ "internal check error: missing local symbol: " ++ name) symbolForName :: String -> LowerM Integer symbolForName name = do st <- get case lookupInScopes name (scopes st) of Just sym -> pure sym Nothing -> case Map.lookup name (topSyms st) of Just sym -> pure sym Nothing -> symbolForExternal name symbolForExternal :: String -> LowerM Integer symbolForExternal name = do st <- get case Map.lookup name (externSyms st) of Just sym -> pure sym Nothing -> do sym <- freshSym recordDebugName sym ("external " ++ name) modify $ \st' -> st' { externSyms = Map.insert name sym (externSyms st') } pure sym nameIsUnbound :: String -> LowerM Bool nameIsUnbound name = do st <- get pure $ case lookupInScopes name (scopes st) of Just _ -> False Nothing -> Map.notMember name (topSyms st) lookupInScopes :: String -> [Map.Map String Integer] -> Maybe Integer lookupInScopes _ [] = Nothing lookupInScopes name (scope : rest) = case Map.lookup name scope of Just sym -> Just sym Nothing -> lookupInScopes name rest freshSym :: LowerM Integer freshSym = do st <- get let sym = nextSym st put st { nextSym = sym + 1 } pure sym isDefinition :: TricuAST -> Bool isDefinition SDef {} = True isDefinition SDefAnn {} = True isDefinition _ = False definitionName :: TricuAST -> String definitionName (SDef name _ _) = name definitionName (SDefAnn name _ _ _) = name definitionName _ = error "definitionName: expected top-level definition" liftEither :: Either String a -> LowerM a liftEither value = StateT $ \st -> case value of Left err -> Left err Right resultValue -> Right (resultValue, st) lowerArgView :: DefArg -> LowerM String lowerArgView (DefBinder _ Nothing) = pure "viewAny" lowerArgView (DefBinder _ (Just ty)) = liftEither (lowerViewExpr ty) lowerArgView (DefPhantom ty) = liftEither (lowerViewExpr ty) viewTypeToExpr :: ViewType -> ViewExpr viewTypeToExpr view = case view of VTName name -> VEName name VTVar varId -> VEVarId varId VTRef n -> VEApp (VEName "Ref") (VEInt n) VTRefText s -> VEApp (VEName "Ref") (VEString s) VTList item -> VEApp (VEName "List") (viewTypeToExpr item) VTMaybe item -> VEApp (VEName "Maybe") (viewTypeToExpr item) VTPair left right -> VEApp (VEApp (VEName "Pair") (viewTypeToExpr left)) (viewTypeToExpr right) VTResult err ok -> VEApp (VEApp (VEName "Result") (viewTypeToExpr err)) (viewTypeToExpr ok) VTGuarded base guard -> VEApp (VEApp (VEName "viewGuarded") (viewTypeToExpr base)) (VERaw (treeSource guard)) VTForall binders body -> VEForall binders (viewTypeToExpr body) VTExists binders body -> VEExists binders (viewTypeToExpr body) VTFn args resultView -> viewExprFn (map viewTypeToExpr args) (viewTypeToExpr resultView) viewExprFn :: [ViewExpr] -> ViewExpr -> ViewExpr viewExprFn args resultView = VEApp (VEApp (VEName "Fn") (VEList args)) resultView viewExprList :: ViewExpr -> ViewExpr viewExprList = VEApp (VEName "List") viewExprFnParts :: ViewExpr -> Maybe ([ViewExpr], ViewExpr) viewExprFnParts (VEForall _ body) = viewExprFnParts body viewExprFnParts (VEApp (VEApp (VEName "Fn") (VEList args)) resultView) = Just (args, resultView) viewExprFnParts _ = Nothing viewExprAsType :: ViewExpr -> Maybe ViewType viewExprAsType view = case view of VEName name -> Just (VTName name) VEVar _ -> Nothing VEVarId varId -> Just (VTVar varId) VEApp (VEName "Ref") (VEInt n) -> Just (VTRef n) VEApp (VEName "Ref") (VEString s) -> Just (VTRefText s) VEApp (VEName "List") item -> VTList <$> viewExprAsType item VEApp (VEName "Maybe") item -> VTMaybe <$> viewExprAsType item VEApp (VEApp (VEName "Pair") left) right -> VTPair <$> viewExprAsType left <*> viewExprAsType right VEApp (VEApp (VEName "Result") err) ok -> VTResult <$> viewExprAsType err <*> viewExprAsType ok VEApp (VEApp (VEName "Fn") (VEList args)) resultView -> VTFn <$> mapM viewExprAsType args <*> viewExprAsType resultView VEForall binders body -> VTForall binders <$> viewExprAsType body VEExists binders body -> VTExists binders <$> viewExprAsType body _ -> Nothing lowerViewExpr :: ViewExpr -> Either String String lowerViewExpr ty = case ty of VEName "Any" -> Right "viewAny" VEName "Bool" -> Right "viewBool" VEName "String" -> Right "viewString" VEName "Byte" -> Right "viewByte" VEName "Unit" -> Right "viewUnit" VEName name -> Right name VEVar name -> Right $ "viewVar " ++ show name VEVarId varId -> Right $ "viewVar " ++ show varId VEInt n -> Right (show n) VEString s -> Right (show s) VEList items -> do itemExprs <- mapM lowerViewExpr items Right $ "[" ++ unwords (map parens itemExprs) ++ "]" VEApp (VEName "Ref") (VEInt n) -> Right $ "viewRef " ++ show n VEApp (VEName "Ref") (VEString s) -> Right $ "viewRef " ++ show s VEApp (VEName "List") elemView -> do elemExpr <- lowerViewExpr elemView Right $ "viewList " ++ parens elemExpr VEApp (VEName "Maybe") elemView -> do elemExpr <- lowerViewExpr elemView Right $ "viewMaybe " ++ parens elemExpr VEApp (VEApp (VEName "Pair") left) right -> do l <- lowerViewExpr left r <- lowerViewExpr right Right $ "viewPair " ++ parens l ++ " " ++ parens r VEApp (VEApp (VEName "Result") err) ok -> do e <- lowerViewExpr err a <- lowerViewExpr ok Right $ "viewResult " ++ parens e ++ " " ++ parens a VEApp (VEApp (VEName "Fn") (VEList args)) resultView -> do as <- mapM lowerViewExpr args r <- lowerViewExpr resultView Right $ "viewFn [" ++ unwords (map parens as) ++ "] " ++ parens r VEApp func arg -> do f <- lowerViewExpr func a <- lowerViewExpr arg Right $ parens f ++ " " ++ parens a VEForall binders body -> do bodyExpr <- lowerViewExpr body Right $ "viewForall " ++ lowerStringList binders ++ " " ++ parens bodyExpr VEExists binders body -> do bodyExpr <- lowerViewExpr body Right $ "viewExists " ++ lowerStringList binders ++ " " ++ parens bodyExpr VERaw raw -> Right raw lowerStringList :: [Integer] -> String lowerStringList items = "[" ++ unwords (map (parens . show) items) ++ "]" quantifyFreeViewVars :: ViewExpr -> ViewExpr quantifyFreeViewVars view = let vars = Set.toList (freeViewVars view) binderIds = zip vars [0..] binderMap = Map.fromList binderIds body = rewriteViewVars binderMap view binders = map snd binderIds in if null vars then view else VEForall binders body rewriteViewVars :: Map.Map String Integer -> ViewExpr -> ViewExpr rewriteViewVars binderMap view = case view of VEVar name -> maybe (VEVar name) VEVarId (Map.lookup name binderMap) VEList items -> VEList (map (rewriteViewVars binderMap) items) VEApp f a -> VEApp (rewriteViewVars binderMap f) (rewriteViewVars binderMap a) VEForall binders body -> VEForall binders (rewriteViewVars binderMap body) VEExists binders body -> VEExists binders (rewriteViewVars binderMap body) _ -> view freeViewVars :: ViewExpr -> Set.Set String freeViewVars view = case view of VEVar name -> Set.singleton name VEVarId _ -> Set.empty VEList items -> Set.unions (map freeViewVars items) VEApp f a -> Set.union (freeViewVars f) (freeViewVars a) VEForall _ body -> freeViewVars body VEExists _ body -> freeViewVars body _ -> Set.empty treeSource :: T -> String treeSource Leaf = "t" treeSource (Stem x) = "(t " ++ treeSource x ++ ")" treeSource (Fork x y) = "(t " ++ treeSource x ++ " " ++ treeSource y ++ ")" parens :: String -> String parens s = "(" ++ s ++ ")"