thrift-compiler-0.1.0.1: Thrift/Compiler/Typechecker/Monad.hs
{-
Copyright (c) Meta Platforms, Inc. and affiliates.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree.
-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# OPTIONS_GHC -Wno-star-is-type #-}
module Thrift.Compiler.Typechecker.Monad
( TC, Typechecked
, typeError, runTypechecker, ask, asks, traverseWeird
, Alternative(..)
, lookupType, lookupSchema, lookupUnion, lookupEnum, lookupConst
, lookupService, lookupEnumInt
, TypeError(..), ErrorMsg(..), AnnotationPlacement(..)
, orderError
) where
import Prelude hiding (Enum)
import Control.Applicative
import Data.Either ( rights )
import Data.Int (Int32)
import Data.Some
import Data.Text (Text)
import Control.Monad.Reader
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import Thrift.Compiler.Parser
import Thrift.Compiler.Types
-- Typechecking Monad ----------------------------------------------------------
-- The typechecking monad is a reader transformed either monad. This allows us
-- to keep track of state and errors
newtype TC l a = TC (ReaderT (Env l) (Either [TypeError l]) a)
deriving (Functor,Monad, MonadReader (Env l))
instance Applicative (TC l) where
pure a = TC (pure a)
-- | Special Applicative instance which allows us to collect
-- type errors from *both* parts of the computation in the
-- event that they both fail.
TC (ReaderT rf) <*> TC (ReaderT rx) =
TC $ ReaderT $ \env -> rf env `collect` rx env
where
collect (Left e1) (Left e2) = Left $ e1 <> e2
collect (Left e) (Right _) = Left e
collect (Right _) (Left e) = Left e
collect (Right f) (Right x) = Right $ f x
type Typechecked (l :: *) (t :: Status -> * -> * -> *) =
TC l (t 'Resolved l Loc)
typeError :: Loc -> ErrorMsg l -> TC l a
typeError loc msg = TC $ lift $ Left [TypeError loc msg]
runTypechecker :: Env l -> TC l a -> Either [TypeError l] a
runTypechecker env (TC t) = runReaderT t env
instance Alternative (TC l) where
-- This produces an empty list of errors, so should never be used as the
-- first parater of '<|>'.
empty = TC $ lift (Left [])
t1 <|> t2 = TC $ do
env <- ask
lift $ case runTypechecker env t1 of
Left err1 -> case runTypechecker env t2 of
Left err2 -> Left (err1 <> err2)
x2@Right{} -> x2
x1@Right{} -> x1
-- | See T45688659 for the weird tale of how badly-typed keys are getting
-- ignored. Try to be similar here, in weird mode, by dropping errors.
traverseWeird :: Bool -> (a -> TC l b) -> [a] -> TC l [b]
traverseWeird optsLenient f xs = do
if not optsLenient then
traverse f xs
else
TC $ ReaderT $ \env -> Right (mapEWeird (runTypechecker env . f) xs)
where
mapEWeird :: (a -> Either [e] b) -> [a] -> [b]
mapEWeird ff = rights . map ff
-- Lookup Functions ------------------------------------------------------------
lookupType :: ThriftName -> Loc -> TC l (Some (Type l))
lookupType = envCtxLookup typeMap UnknownType
lookupSchema :: ThriftName -> Loc -> TC l (Some (Schema l))
lookupSchema = envLookup schemaMap UnknownType
lookupUnion :: ThriftName -> Loc -> TC l (Some (USchema l))
lookupUnion = envLookup unionMap UnknownType
lookupEnum :: ThriftName -> Loc -> TC l EnumValues
lookupEnum = envLookup enumMap UnknownType
-- | Allow enum values as default values for int fields.
-- This is a type violation. This is added to allow parsing these old files
-- when @--lenient@ mode is active (see 'Options.optsLenient').
lookupEnumInt :: ThriftName -> Loc -> TC l Int32
lookupEnumInt name loc = do
valueOrCollision <- envLookup enumInt UnknownEnumValue name loc
case valueOrCollision of
Just value -> pure value
Nothing -> typeError loc $ MultipleEnumValues name
-- | Loc parameter of usage of ThriftName is for error messages.
-- Loc in result is where ThriftName was defined.
lookupConst :: ThriftName -> Loc -> TC l (Some (Type l), Name, Loc)
lookupConst = envCtxLookup constMap UnknownConst
-- | Loc parameter of usage of ThriftName is for error messages.
-- Loc in result is where ThriftName was defined.
lookupService :: ThriftName -> Loc -> TC l (Name, Set.Set Text, Loc)
lookupService = envLookup serviceMap UnknownService
envLookup
:: (Env l -> Map.Map Text u)
-> (ThriftName -> ErrorMsg l)
-> ThriftName
-> Loc
-> TC l u
envLookup getMap mkError name loc = do
env <- TC ask
case doLookup env name of
Nothing -> typeError loc $ mkError name
Just u -> pure u
where
doLookup env (UName n) = Map.lookup n (getMap env)
doLookup env (QName m n) =
Map.lookup n . getMap =<< Map.lookup m (importMap env)
envCtxLookup
:: (Env l -> Context u)
-> (ThriftName -> ErrorMsg l)
-> ThriftName
-> Loc
-> TC l u
envCtxLookup getMap mkError name loc = do
env <- TC ask
case doLookup env name of
Nothing -> typeError loc $ mkError name
Just u -> pure u
where
doLookup env (UName n) = Map.lookup n (cMap $ getMap env)
doLookup env (QName m n) =
Map.lookup n . cMap . getMap =<< Map.lookup m (importMap env)
-- Type Errors -----------------------------------------------------------------
data TypeError l
-- Intra-Modular Type Errors
= TypeError Loc (ErrorMsg l)
-- Inter-Modular Errors
| EmptyInput
| CyclicModules [ThriftFile SpliceFile Loc]
data ErrorMsg l
= CyclicTypes [Parsed Decl]
| CyclicServices [Parsed Service]
| UnknownType ThriftName
| UnknownConst ThriftName
| UnknownService ThriftName
| UnknownEnumValue ThriftName
| MultipleEnumValues ThriftName
| forall t. LiteralMismatch (Type l t) (UntypedConst Loc)
| forall u v. IdentMismatch (Type l u) (Type l v) ThriftName
| UnknownField Text
| MissingField Text
| InvalidFieldId Text Int
| InvalidField (UntypedConst Loc)
| InvalidUnion Name Int
| EmptyUnion Text
| forall t. InvalidThrows (Type l t) Text
| AnnotationMismatch (AnnotationPlacement l) (Annotation Loc)
| DuplicateName Text
| DuplicateEnumVal Text [Text] Int
| forall t1 t2. TypeMismatch (Type l t1) (Type l t2)
| forall t. NotDefinedBeforeUsed (Type l t)
data AnnotationPlacement l
= forall t. AnnType (Type l t)
| AnnField | AnnStruct | AnnUnion | AnnTypedef | AnnEnum | AnnPriority
orderError :: TypeError l -> TypeError l -> Ordering
-- EmptyInput always comes first
orderError EmptyInput EmptyInput = EQ
orderError EmptyInput _ = LT
orderError _ EmptyInput = GT
-- CyclicModules comes next
orderError CyclicModules{} CyclicModules{} = EQ
orderError CyclicModules{} _ = LT
orderError _ CyclicModules{} = GT
-- Type Errors are ordered by Loc
orderError (TypeError l1 _) (TypeError l2 _) = compare l1 l2