{-# language DataKinds #-} {-# language GeneralizedNewtypeDeriving #-} {-# language FlexibleContexts #-} {-# language PolyKinds #-} {-# language TypeApplications #-} {-# language TypeOperators #-} {-# language TypeSynonymInstances, FlexibleInstances #-} {-# language TemplateHaskell, TypeFamilies, MultiParamTypeClasses #-} {-# language RankNTypes #-} {-# language LambdaCase #-} {-# language ScopedTypeVariables #-} {-| Module : Language.Python.Validate.Syntax Copyright : (C) CSIRO 2017-2019 License : BSD3 Maintainer : Isaac Elliott Stability : experimental Portability : non-portable -} module Language.Python.Validate.Syntax ( module Data.Validation , module Language.Python.Validate.Syntax.Error -- * Main validation functions , Syntax, ValidateSyntax, runValidateSyntax , validateModuleSyntax , validateStatementSyntax , validateExprSyntax -- * Miscellany -- ** Extra types , SyntaxContext(..), FunctionInfo(..), inLoop, inFunction, inGenerator, inParens , runValidateSyntax' , initialSyntaxContext -- ** Extra functions , reservedWords , canAssignTo , deleteBy' , deleteFirstsBy' , localNonlocals -- ** Validation functions , validateArgsSyntax , validateBlockSyntax , validateCompoundStatementSyntax , validateComprehensionSyntax , validateDecoratorSyntax , validateDictItemSyntax , validateExceptAsSyntax , validateIdentSyntax , validateImportAsSyntax , validateImportTargetsSyntax , validateListItemSyntax , validateParamsSyntax , validateSetItemSyntax , validateSimpleStatementSyntax , validateStringLiteralSyntax , validateSubscriptSyntax , validateSuiteSyntax , validateTupleItemSyntax , validateWhitespace ) where import Data.Validation import Control.Applicative ((<|>), liftA2) import Control.Lens.Cons (snoc, _init) import Control.Lens.Fold ((^..), (^?), (^?!), folded, allOf, toListOf, anyOf, lengthOf, has) import Control.Lens.Getter ((^.), getting, view) import Control.Lens.Prism (_Right, _Just) import Control.Lens.Review ((#)) import Control.Lens.Setter ((.~), (%~)) import Control.Lens.TH (makeLenses) import Control.Lens.Tuple (_1, _2, _3) import Control.Lens.Traversal (traverseOf) import Control.Monad (when) import Control.Monad.State (State, put, modify, get, evalState) import Control.Monad.Reader (ReaderT, local, ask, runReaderT) import Data.Char (isAscii, ord) import Data.Foldable (toList, traverse_) import Data.Bitraversable (bitraverse) import Data.Functor.Compose (Compose(..)) import Data.List (intersect, union) import Data.List.NonEmpty (NonEmpty(..), (<|)) import Data.Maybe (isJust, isNothing, fromMaybe) import Data.Semigroup (Semigroup(..)) import Data.Type.Set (Nub, Member) import Data.Validate.Monadic (ValidateM(..), bindVM, liftVM0, liftVM1, errorVM, errorVM1) import Unsafe.Coerce (unsafeCoerce) import qualified Data.List.NonEmpty as NonEmpty import Language.Python.Optics import Language.Python.Optics.Validated (unvalidated) import Language.Python.Syntax.Ann import Language.Python.Syntax.CommaSep import Language.Python.Syntax.Expr import Language.Python.Syntax.Ident import Language.Python.Syntax.Import import Language.Python.Syntax.Module import Language.Python.Syntax.ModuleNames import Language.Python.Syntax.Punctuation import Language.Python.Syntax.Statement import Language.Python.Syntax.Strings import Language.Python.Syntax.Whitespace import Language.Python.Validate.Indentation import Language.Python.Validate.Syntax.Error deleteBy' :: (a -> b -> Bool) -> a -> [b] -> [b] deleteBy' _ _ [] = [] deleteBy' eq a (b:bs) = if a `eq` b then bs else b : deleteBy' eq a bs deleteFirstsBy' :: (a -> b -> Bool) -> [a] -> [b] -> [a] deleteFirstsBy' eq = foldl (flip (deleteBy' (flip eq))) reservedWords :: [String] reservedWords = [ "False" , "class" , "finally" , "is" , "return" , "None" , "continue" , "for" , "lambda" , "try" , "True" , "def" , "from" , "nonlocal" , "while" , "and" , "del" , "global" , "not" , "with" , "as" , "elif" , "if" , "or" , "yield" , "assert" , "else" , "import" , "pass" , "break" , "except" , "in" , "raise" ] data Syntax data FunctionInfo = FunctionInfo { _functionParams :: [String] , _asyncFunction :: Bool } makeLenses ''FunctionInfo data SyntaxContext = SyntaxContext { _inLoop :: Bool , _inFinally :: Bool , _inFunction :: Maybe FunctionInfo , _inGenerator :: Bool , _inClass :: Bool , _inParens :: Bool } makeLenses ''SyntaxContext type ValidateSyntax e = ValidateM (NonEmpty e) (ReaderT SyntaxContext (State [String])) runValidateSyntax :: ValidateSyntax e a -> Validation (NonEmpty e) a runValidateSyntax = runValidateSyntax' initialSyntaxContext [] runValidateSyntax' :: SyntaxContext -> [String] -> ValidateSyntax e a -> Validation (NonEmpty e) a runValidateSyntax' ctxt nlscope = flip evalState nlscope . flip runReaderT ctxt . getCompose . unValidateM localNonlocals :: ([String] -> [String]) -> ValidateSyntax e a -> ValidateSyntax e a localNonlocals f v = ValidateM . Compose $ do before <- get modify f res <- getCompose $ unValidateM v put before pure res initialSyntaxContext :: SyntaxContext initialSyntaxContext = SyntaxContext { _inLoop = False , _inFinally = False , _inFunction = Nothing , _inGenerator = False , _inClass = False , _inParens = False } validateIdentSyntax :: ( AsSyntaxError e a , Member Indentation v ) => Ident v a -> ValidateSyntax e (Ident (Nub (Syntax ': v)) a) validateIdentSyntax (MkIdent a name ws) | not (all isAscii name) = errorVM1 (_BadCharacter # (getAnn a, name)) | null name = errorVM1 (_EmptyIdentifier # getAnn a) | otherwise = bindVM (liftVM0 $ view inFunction) $ \fi -> let reserved = reservedWords <> if fromMaybe False (fi ^? _Just.asyncFunction) then ["async", "await"] else [] in if (name `elem` reserved) then errorVM1 (_IdentifierReservedWord # (getAnn a, name)) else pure $ MkIdent a name ws validateWhitespace :: (AsSyntaxError e a, Foldable f) => a -> f Whitespace -> ValidateSyntax e (f Whitespace) validateWhitespace ann ws = liftVM0 ask `bindVM` \ctxt -> if _inParens ctxt then pure ws else if any (\case Newline{} -> True Comment{} -> False Continued{} -> False Tab -> False Space -> False) ws then errorVM1 (_UnexpectedNewline # ann) else if any (\case Newline{} -> False Comment{} -> True Continued{} -> False Tab -> False Space -> False) ws then errorVM1 (_UnexpectedComment # ann) else pure ws validateAt :: (AsSyntaxError e a) => a -> At -> ValidateSyntax e At validateAt a (MkAt ws) = MkAt <$> validateWhitespace a ws validateComma :: (AsSyntaxError e a) => a -> Comma -> ValidateSyntax e Comma validateComma a (MkComma ws) = MkComma <$> validateWhitespace a ws validateDot :: (AsSyntaxError e a) => a -> Dot -> ValidateSyntax e Dot validateDot a (MkDot ws) = MkDot <$> validateWhitespace a ws validateColon :: (AsSyntaxError e a) => a -> Colon -> ValidateSyntax e Colon validateColon a (MkColon ws) = MkColon <$> validateWhitespace a ws validateSemicolon :: AsSyntaxError e a => Semicolon a -> ValidateSyntax e (Semicolon a) validateSemicolon (MkSemicolon a ws) = MkSemicolon a <$> validateWhitespace (getAnn a) ws validateEquals :: AsSyntaxError e a => a -> Equals -> ValidateSyntax e Equals validateEquals a (MkEquals ws) = MkEquals <$> validateWhitespace a ws validateAssignmentSyntax :: ( AsSyntaxError e a , Member Indentation v ) => a -> Expr v a -> ValidateSyntax e (Expr (Nub (Syntax ': v)) a) validateAssignmentSyntax a ex = (if lengthOf (getting $ _Tuple.tupleItems._TupleUnpack) ex > 1 || lengthOf (getting $ _List.listItems._ListUnpack) ex > 1 then errorVM1 $ _ManyStarredTargets # a else pure ()) *> (if canAssignTo ex then validateExprSyntax ex else errorVM1 $ _CannotAssignTo # (a, ex ^. unvalidated)) validateCompForSyntax :: ( AsSyntaxError e a , Member Indentation v ) => CompFor v a -> ValidateSyntax e (CompFor (Nub (Syntax ': v)) a) validateCompForSyntax (CompFor a b c d e) = (\c' -> CompFor a b c' d) <$> liftVM1 (local $ inGenerator .~ True) (validateAssignmentSyntax (getAnn a) c) <*> validateExprSyntax e validateCompIfSyntax :: ( AsSyntaxError e a , Member Indentation v ) => CompIf v a -> ValidateSyntax e (CompIf (Nub (Syntax ': v)) a) validateCompIfSyntax (CompIf a b c) = CompIf a b <$> liftVM1 (local $ inGenerator .~ True) (validateExprSyntax c) validateComprehensionSyntax :: ( AsSyntaxError e a , Member Indentation v ) => (ex v a -> ValidateSyntax e (ex (Nub (Syntax ': v)) a)) -> Comprehension ex v a -> ValidateSyntax e (Comprehension ex (Nub (Syntax ': v)) a) validateComprehensionSyntax f (Comprehension a b c d) = Comprehension a <$> liftVM1 (local $ inGenerator .~ True) (f b) <*> validateCompForSyntax c <*> liftVM1 (local $ inGenerator .~ True) (traverse (bitraverse validateCompForSyntax validateCompIfSyntax) d) validateStringPyChar :: ( AsSyntaxError e a ) => a -> PyChar -> ValidateSyntax e PyChar validateStringPyChar a (Char_lit '\0') = errorVM1 $ _NullByte # a validateStringPyChar _ a = pure a validateBytesPyChar :: ( AsSyntaxError e a ) => a -> PyChar -> ValidateSyntax e PyChar validateBytesPyChar a (Char_lit '\0') = errorVM1 $ _NullByte # a validateBytesPyChar a (Char_lit c) | ord c >= 128 = errorVM1 $ _NonAsciiInBytes # (a, c) validateBytesPyChar _ a = pure a validateStringLiteralSyntax :: AsSyntaxError e a => StringLiteral a -> ValidateSyntax e (StringLiteral a) validateStringLiteralSyntax (StringLiteral a b c d e f) = StringLiteral a b c d <$> traverse (validateStringPyChar $ getAnn a) e <*> validateWhitespace (getAnn a) f validateStringLiteralSyntax (BytesLiteral a b c d e f) = BytesLiteral a b c d <$> traverse (validateBytesPyChar $ getAnn a) e <*> validateWhitespace (getAnn a) f validateStringLiteralSyntax (RawStringLiteral a b c d e f) = RawStringLiteral a b c d e <$> validateWhitespace (getAnn a) f validateStringLiteralSyntax (RawBytesLiteral a b c d e f) = RawBytesLiteral a b c d e <$> validateWhitespace (getAnn a) f validateDictItemSyntax :: ( AsSyntaxError e a , Member Indentation v ) => DictItem v a -> ValidateSyntax e (DictItem (Nub (Syntax ': v)) a) validateDictItemSyntax (DictItem a b c d) = (\b' -> DictItem a b' c) <$> validateExprSyntax b <*> validateExprSyntax d validateDictItemSyntax (DictUnpack a b c) = DictUnpack a <$> validateWhitespace (getAnn a) b <*> validateExprSyntax c validateSubscriptSyntax :: ( AsSyntaxError e a , Member Indentation v ) => Subscript v a -> ValidateSyntax e (Subscript (Nub (Syntax ': v)) a) validateSubscriptSyntax (SubscriptExpr e) = SubscriptExpr <$> validateExprSyntax e validateSubscriptSyntax (SubscriptSlice a b c d) = (\a' -> SubscriptSlice a' b) <$> traverse validateExprSyntax a <*> traverse validateExprSyntax c <*> traverseOf (traverse._2.traverse) validateExprSyntax d validateListItemSyntax :: ( AsSyntaxError e a , Member Indentation v ) => ListItem v a -> ValidateSyntax e (ListItem (Nub (Syntax ': v)) a) validateListItemSyntax (ListItem a b) = ListItem a <$> validateExprSyntax b validateListItemSyntax (ListUnpack a b c d) = ListUnpack a <$> traverseOf (traverse._2) (validateWhitespace $ getAnn a) b <*> validateWhitespace (getAnn a) c <*> validateExprSyntax d validateSetItemSyntax :: ( AsSyntaxError e a , Member Indentation v ) => SetItem v a -> ValidateSyntax e (SetItem (Nub (Syntax ': v)) a) validateSetItemSyntax (SetItem a b) = SetItem a <$> validateExprSyntax b validateSetItemSyntax (SetUnpack a b c d) = SetUnpack a <$> traverseOf (traverse._2) (validateWhitespace $ getAnn a) b <*> validateWhitespace (getAnn a) c <*> validateExprSyntax d validateTupleItemSyntax :: ( AsSyntaxError e a , Member Indentation v ) => TupleItem v a -> ValidateSyntax e (TupleItem (Nub (Syntax ': v)) a) validateTupleItemSyntax (TupleItem a b) = TupleItem a <$> validateExprSyntax b validateTupleItemSyntax (TupleUnpack a b c d) = TupleUnpack a <$> traverseOf (traverse._2) (validateWhitespace $ getAnn a) b <*> validateWhitespace (getAnn a) c <*> validateExprSyntax d validateExprSyntax :: ( AsSyntaxError e a , Member Indentation v ) => Expr v a -> ValidateSyntax e (Expr (Nub (Syntax ': v)) a) validateExprSyntax (Unit a b c) = Unit a <$> liftVM1 (local $ inParens .~ True) (validateWhitespace (getAnn a) b) <*> validateWhitespace (getAnn a) c validateExprSyntax (Lambda a b c d e) = let paramIdents = c ^.. folded.unvalidated.paramName.identValue in Lambda a <$> validateWhitespace (getAnn a) b <*> validateParamsSyntax True c <*> validateColon (getAnn a) d <*> liftVM1 (local $ \ctxt -> ctxt { _inLoop = False , _inFunction = fmap ((functionParams %~ (`union` paramIdents)) . (asyncFunction .~ False)) (_inFunction ctxt) <|> Just (FunctionInfo paramIdents False) }) (validateExprSyntax e) validateExprSyntax (Yield a b c) = Yield a <$> validateWhitespace (getAnn a) b <* (liftVM0 ask `bindVM` \ctxt -> case _inFunction ctxt of Nothing | _inGenerator ctxt -> pure () | otherwise -> errorVM1 (_YieldOutsideGenerator # getAnn a) Just info -> if info^.asyncFunction then errorVM1 $ _YieldInsideCoroutine # getAnn a else pure ()) <*> traverse validateExprSyntax c validateExprSyntax (YieldFrom a b c d) = YieldFrom a <$> validateWhitespace (getAnn a) b <*> validateWhitespace (getAnn a) c <* (liftVM0 ask `bindVM` \ctxt -> case _inFunction ctxt of Nothing | _inGenerator ctxt -> pure () | otherwise -> errorVM1 (_YieldOutsideGenerator # getAnn a) Just fi -> if fi ^. asyncFunction then errorVM1 (_YieldFromInsideCoroutine # getAnn a) else pure ()) <*> validateExprSyntax d validateExprSyntax (Ternary a b c d e f) = (\b' d' f' -> Ternary a b' c d' e f') <$> validateExprSyntax b <*> validateExprSyntax d <*> validateExprSyntax f validateExprSyntax (Subscript a b c d e) = (\b' d' -> Subscript a b' c d' e) <$> validateExprSyntax b <*> traverse validateSubscriptSyntax d validateExprSyntax (Not a ws e) = Not a <$> validateWhitespace (getAnn a) ws <*> validateExprSyntax e validateExprSyntax (Parens a ws1 e ws2) = Parens a ws1 <$> liftVM1 (local $ inParens .~ True) (validateExprSyntax e) <*> validateWhitespace (getAnn a) ws2 validateExprSyntax (Bool a b ws) = pure $ Bool a b ws validateExprSyntax (UnOp a op expr) = UnOp a op <$> validateExprSyntax expr validateExprSyntax (String a strLits) = if all (\case StringLiteral{} -> True RawStringLiteral{} -> True _ -> False) strLits || all (\case BytesLiteral{} -> True RawBytesLiteral{} -> True _ -> False) strLits then String a <$> traverse validateStringLiteralSyntax strLits else errorVM1 (_Can'tJoinStringAndBytes # getAnn a) validateExprSyntax (Int a n ws) = pure $ Int a n ws validateExprSyntax (Float a n ws) = pure $ Float a n ws validateExprSyntax (Imag a n ws) = pure $ Imag a n ws validateExprSyntax (Ident a name) = Ident a <$> validateIdentSyntax name validateExprSyntax (List a ws1 exprs ws2) = List a ws1 <$> liftVM1 (local $ inParens .~ True) (traverseOf (traverse.traverse) validateListItemSyntax exprs) <*> validateWhitespace (getAnn a) ws2 validateExprSyntax (ListComp a ws1 comp ws2) = liftVM1 (local $ inParens .~ True) (ListComp a ws1 <$> validateComprehensionSyntax validateExprSyntax comp) <*> validateWhitespace (getAnn a) ws2 validateExprSyntax (Generator a comp) = Generator a <$> validateComprehensionSyntax validateExprSyntax comp validateExprSyntax (Await a ws expr) = bindVM (liftVM0 ask) $ \ctxt -> Await a <$> validateWhitespace (getAnn a) ws <* (if not $ fromMaybe False (ctxt ^? inFunction._Just.asyncFunction) then errorVM1 $ _AwaitOutsideCoroutine # getAnn a else pure () *> if ctxt^.inGenerator then errorVM1 $ _AwaitInsideComprehension # getAnn a else pure ()) <*> validateExprSyntax expr validateExprSyntax (Deref a expr ws1 name) = Deref a <$> validateExprSyntax expr <*> validateWhitespace (getAnn a) ws1 <*> validateIdentSyntax name validateExprSyntax (Call a expr ws args ws2) = Call a <$> validateExprSyntax expr <*> liftVM1 (local $ inParens .~ True) (validateWhitespace (getAnn a) ws) <*> liftVM1 (local $ inParens .~ True) (traverse validateArgsSyntax args) <*> validateWhitespace (getAnn a) ws2 validateExprSyntax (None a ws) = None a <$> validateWhitespace (getAnn a) ws validateExprSyntax (Ellipsis a ws) = Ellipsis a <$> validateWhitespace (getAnn a) ws validateExprSyntax (BinOp a e1 op e2) = BinOp a <$> validateExprSyntax e1 <*> pure op <*> validateExprSyntax e2 validateExprSyntax (Tuple a b comma d) = Tuple a <$> validateTupleItemSyntax b <*> validateComma (getAnn a) comma <*> traverseOf (traverse.traverse) validateTupleItemSyntax d validateExprSyntax (DictComp a ws1 comp ws2) = liftVM1 (local $ inParens .~ True) (DictComp a ws1 <$> validateComprehensionSyntax dictItem comp) <*> validateWhitespace (getAnn a) ws2 where dictItem (DictUnpack a _ _) = errorVM1 (_InvalidDictUnpacking # getAnn a) dictItem a = validateDictItemSyntax a validateExprSyntax (Dict a b c d) = Dict a b <$> liftVM1 (local $ inParens .~ True) (traverseOf (traverse.traverse) validateDictItemSyntax c) <*> validateWhitespace (getAnn a) d validateExprSyntax (SetComp a ws1 comp ws2) = liftVM1 (local $ inParens .~ True) (SetComp a ws1 <$> validateComprehensionSyntax setItem comp) <*> validateWhitespace (getAnn a) ws2 where setItem (SetUnpack a _ _ _) = errorVM1 (_InvalidSetUnpacking # getAnn a) setItem a = validateSetItemSyntax a validateExprSyntax (Set a b c d) = Set a b <$> liftVM1 (local $ inParens .~ True) (traverse validateSetItemSyntax c) <*> validateWhitespace (getAnn a) d validateBlockSyntax :: ( AsSyntaxError e a , Member Indentation v ) => Block v a -> ValidateSyntax e (Block (Nub (Syntax ': v)) a) validateBlockSyntax (Block x b bs) = Block x <$> validateStatementSyntax b <*> traverseOf (traverse._Right) validateStatementSyntax bs validateSuiteSyntax :: ( AsSyntaxError e a , Member Indentation v ) => Suite v a -> ValidateSyntax e (Suite (Nub (Syntax ': v)) a) validateSuiteSyntax (SuiteMany a b c d e) = (\b' -> SuiteMany a b' c d) <$> validateColon (getAnn a) b <*> validateBlockSyntax e validateSuiteSyntax (SuiteOne a b c) = SuiteOne a <$> validateColon (getAnn a) b <*> validateSmallStatementSyntax c validateDecoratorSyntax :: ( AsSyntaxError e a , Member Indentation v ) => Decorator v a -> ValidateSyntax e (Decorator (Nub (Syntax ': v)) a) validateDecoratorSyntax (Decorator a b c d e f g) = (\c' d' -> Decorator a b c' d' e f) <$> validateAt (getAnn a) c <*> isDecoratorValue d <*> traverseOf (traverse._1) validateBlankSyntax g where someDerefs Ident{} = True someDerefs (Deref _ a _ _) = someDerefs a someDerefs _ = False isDecoratorValue e@(Call _ a _ _ _) | someDerefs a = pure $ unsafeCoerce e isDecoratorValue e | someDerefs e = pure $ unsafeCoerce e isDecoratorValue _ = errorVM1 (_MalformedDecorator # getAnn a) validateBlankSyntax :: AsSyntaxError e a => Blank a -> ValidateSyntax e (Blank a) validateBlankSyntax (Blank a ws cmt) = (\ws' -> Blank a ws' cmt) <$> validateWhitespace (getAnn a) ws validateCompoundStatementSyntax :: forall e v a . ( AsSyntaxError e a , Member Indentation v ) => CompoundStatement v a -> ValidateSyntax e (CompoundStatement (Nub (Syntax ': v)) a) validateCompoundStatementSyntax (Fundef a decos idnts asyncWs ws1 name ws2 params ws3 mty body) = let paramIdents = params ^.. folded.unvalidated.paramName.identValue in (\decos' -> Fundef a decos' idnts) <$> traverse validateDecoratorSyntax decos <*> traverse (validateWhitespace $ getAnn a) asyncWs <*> validateWhitespace (getAnn a) ws1 <*> validateIdentSyntax name <*> pure ws2 <*> liftVM1 (local $ inParens .~ True) (validateParamsSyntax False params) <*> pure ws3 <*> traverse (bitraverse (validateWhitespace $ getAnn a) validateExprSyntax) mty <*> localNonlocals id (liftVM1 (local $ \ctxt -> ctxt { _inLoop = False , _inFunction = fmap ((functionParams %~ (`union` paramIdents)) . (asyncFunction %~ (|| isJust asyncWs))) (_inFunction ctxt) <|> Just (FunctionInfo paramIdents $ isJust asyncWs) }) (validateSuiteSyntax body)) validateCompoundStatementSyntax (If a idnts ws1 expr body elifs body') = If a idnts <$> validateWhitespace (getAnn a) ws1 <*> validateExprSyntax expr <*> validateSuiteSyntax body <*> traverse (\(a, b, c, d) -> (\c' -> (,,,) a b c') <$> validateExprSyntax c <*> validateSuiteSyntax d) elifs <*> traverseOf (traverse._3) validateSuiteSyntax body' validateCompoundStatementSyntax (While a idnts ws1 expr body els) = While a idnts <$> validateWhitespace (getAnn a) ws1 <*> validateExprSyntax expr <*> liftVM1 (local $ (inFinally .~ False) . (inLoop .~ True)) (validateSuiteSyntax body) <*> traverseOf (traverse._3) validateSuiteSyntax els validateCompoundStatementSyntax (TryExcept a idnts b e f k l) = TryExcept a idnts <$> validateWhitespace (getAnn a) b <*> validateSuiteSyntax e <*> traverse (\(idnts, f, g, j) -> (,,,) idnts <$> validateWhitespace (getAnn a) f <*> traverse validateExceptAsSyntax g <*> validateSuiteSyntax j) f <* (if anyOf (_init.folded._3) isNothing $ NonEmpty.toList f then errorVM1 $ _DefaultExceptMustBeLast # getAnn a else pure ()) <*> traverse (\(idnts, x, w) -> (,,) idnts <$> validateWhitespace (getAnn a) x <*> validateSuiteSyntax w) k <*> traverse (\(idnts, x, w) -> (,,) idnts <$> validateWhitespace (getAnn a) x <*> liftVM1 (local $ inFinally .~ True) (validateSuiteSyntax w)) l validateCompoundStatementSyntax (TryFinally a idnts b e idnts2 f i) = TryFinally a idnts <$> validateWhitespace (getAnn a) b <*> validateSuiteSyntax e <*> pure idnts2 <*> validateWhitespace (getAnn a) f <*> liftVM1 (local $ inFinally .~ True) (validateSuiteSyntax i) validateCompoundStatementSyntax (ClassDef a decos idnts b c d g) = liftVM1 (local $ inLoop .~ False) $ (\decos' -> ClassDef a decos' idnts) <$> traverse validateDecoratorSyntax decos <*> validateWhitespace (getAnn a) b <*> validateIdentSyntax c <*> traverse (\(x, y, z) -> (,,) <$> validateWhitespace (getAnn a) x <*> traverse (liftVM1 (local $ inParens .~ True) . validateArgsSyntax) y <*> validateWhitespace (getAnn a) z) d <*> liftVM1 (local $ (inClass .~ True) . (inFunction .~ Nothing)) (validateSuiteSyntax g) validateCompoundStatementSyntax (For a idnts asyncWs b c d e h i) = bindVM (liftVM0 ask) $ \ctxt -> For a idnts <$ (if isJust asyncWs && not (fromMaybe False $ ctxt ^? inFunction._Just.asyncFunction) then errorVM1 (_AsyncForOutsideCoroutine # getAnn a) else pure ()) <*> traverse (validateWhitespace $ getAnn a) asyncWs <*> validateWhitespace (getAnn a) b <*> validateAssignmentSyntax (getAnn a) c <*> validateWhitespace (getAnn a) d <*> traverse validateExprSyntax e <*> liftVM1 (local $ (inFinally .~ False) . (inLoop .~ True)) (validateSuiteSyntax h) <*> traverse (\(idnts, x, w) -> (,,) idnts <$> validateWhitespace (getAnn a) x <*> validateSuiteSyntax w) i validateCompoundStatementSyntax (With a b asyncWs c d e) = bindVM (liftVM0 ask) $ \ctxt -> With a b <$ (if isJust asyncWs && not (fromMaybe False $ ctxt ^? inFunction._Just.asyncFunction) then errorVM1 (_AsyncWithOutsideCoroutine # getAnn a) else pure ()) <*> traverse (validateWhitespace $ getAnn a) asyncWs <*> validateWhitespace (getAnn a) c <*> traverse (\(WithItem a b c) -> WithItem a <$> validateExprSyntax b <*> traverse (\(ws, b) -> (,) <$> validateWhitespace (getAnn a) ws <*> validateAssignmentSyntax (getAnn a) b) c) d <*> validateSuiteSyntax e validateExceptAsSyntax :: ( AsSyntaxError e a , Member Indentation v ) => ExceptAs v a -> ValidateSyntax e (ExceptAs (Nub (Syntax ': v)) a) validateExceptAsSyntax (ExceptAs ann e f) = ExceptAs ann <$> validateExprSyntax e <*> traverse (\(a, b) -> (,) <$> validateWhitespace (getAnn ann) a <*> validateIdentSyntax b) f validateImportAsSyntax :: ( AsSyntaxError e a , Member Indentation v ) => (t v a -> ValidateSyntax e (t' (Nub (Syntax ': v)) a)) -> ImportAs t v a -> ValidateSyntax e (ImportAs t' (Nub (Syntax ': v)) a) validateImportAsSyntax v (ImportAs x a b) = ImportAs x <$> v a <*> traverse (\(c, d) -> (,) <$> (c <$ validateWhitespace (getAnn x) (NonEmpty.toList c)) <*> validateIdentSyntax d) b validateImportTargetsSyntax :: ( AsSyntaxError e a , Member Indentation v ) => ImportTargets v a -> ValidateSyntax e (ImportTargets (Nub (Syntax ': v)) a) validateImportTargetsSyntax (ImportAll a ws) = bindVM (liftVM0 ask) $ \ctxt -> if ctxt ^. inClass || has (inFunction._Just) ctxt then errorVM1 $ _WildcardImportInDefinition # getAnn a else ImportAll a <$> validateWhitespace (getAnn a) ws validateImportTargetsSyntax (ImportSome a cs) = ImportSome a <$> traverse (validateImportAsSyntax validateIdentSyntax) cs validateImportTargetsSyntax (ImportSomeParens a ws1 cs ws2) = liftVM1 (local $ inParens .~ True) (ImportSomeParens a <$> validateWhitespace (getAnn a) ws1 <*> traverse (validateImportAsSyntax validateIdentSyntax) cs) <*> validateWhitespace (getAnn a) ws2 validateModuleNameSyntax :: forall e a v . ( AsSyntaxError e a , Member Indentation v ) => ModuleName v a -> ValidateSyntax e (ModuleName (Nub (Syntax ': v)) a) validateModuleNameSyntax (ModuleNameOne a b) = ModuleNameOne a <$> validateIdentSyntax b validateModuleNameSyntax (ModuleNameMany a b c d) = ModuleNameMany a <$> validateIdentSyntax b <*> validateDot (getAnn a) c <*> validateModuleNameSyntax d validateRelativeModuleNameSyntax :: forall e a v . ( AsSyntaxError e a , Member Indentation v ) => RelativeModuleName v a -> ValidateSyntax e (RelativeModuleName (Nub (Syntax ': v)) a) validateRelativeModuleNameSyntax (RelativeWithName ann dots mn) = RelativeWithName ann <$> traverse (validateDot $ getAnn ann) dots <*> validateModuleNameSyntax mn validateRelativeModuleNameSyntax (Relative ann dots) = Relative ann <$> traverse (validateDot $ getAnn ann) dots validateSimpleStatementSyntax :: forall e a v . ( AsSyntaxError e a , Member Indentation v ) => SimpleStatement v a -> ValidateSyntax e (SimpleStatement (Nub (Syntax ': v)) a) validateSimpleStatementSyntax (Assert a b c d) = Assert a <$> validateWhitespace (getAnn a) b <*> validateExprSyntax c <*> traverseOf (traverse._2) validateExprSyntax d validateSimpleStatementSyntax (Raise a ws f) = Raise a <$> validateWhitespace (getAnn a) ws <*> traverse (\(b, c) -> (,) <$> validateExprSyntax b <*> traverse (\(d, e) -> (,) <$> validateWhitespace (getAnn a) d <*> validateExprSyntax e) c) f validateSimpleStatementSyntax (Return a ws expr) = liftVM0 ask `bindVM` \sctxt -> case _inFunction sctxt of Just{} -> Return a <$> validateWhitespace (getAnn a) ws <*> traverse validateExprSyntax expr _ -> errorVM1 (_ReturnOutsideFunction # getAnn a) validateSimpleStatementSyntax (Expr a expr) = Expr a <$> validateExprSyntax expr validateSimpleStatementSyntax (Assign a lvalue rs) = liftVM0 ask `bindVM` \sctxt -> let assigns = if isJust (_inFunction sctxt) then (lvalue : (snd <$> NonEmpty.init rs)) ^.. folded.unvalidated.assignTargets.identValue else [] in Assign a <$> validateAssignmentSyntax (getAnn a) lvalue <*> ((\a b -> case a of; [] -> pure b; a : as -> a :| (snoc as b)) <$> traverse (\(ws, b) -> (,) <$> validateEquals (getAnn a) ws <*> validateAssignmentSyntax (getAnn a) b) (NonEmpty.init rs) <*> (\(ws, b) -> (,) <$> validateEquals (getAnn a) ws <*> validateExprSyntax b) (NonEmpty.last rs)) <* liftVM0 (modify (assigns ++)) validateSimpleStatementSyntax (AugAssign a lvalue aa rvalue) = AugAssign a <$> (if canAssignTo lvalue then case lvalue of Ident{} -> validateExprSyntax lvalue Deref{} -> validateExprSyntax lvalue Subscript{} -> validateExprSyntax lvalue _ -> errorVM1 (_CannotAugAssignTo # (getAnn a, lvalue ^. unvalidated)) else errorVM1 (_CannotAssignTo # (getAnn a, lvalue ^. unvalidated))) <*> pure aa <*> validateExprSyntax rvalue validateSimpleStatementSyntax (Pass a ws) = Pass a <$> validateWhitespace (getAnn a) ws validateSimpleStatementSyntax (Break a ws) = Break a <$ (liftVM0 ask `bindVM` \sctxt -> if _inLoop sctxt then pure () else errorVM1 (_BreakOutsideLoop # getAnn a)) <*> validateWhitespace (getAnn a) ws validateSimpleStatementSyntax (Continue a ws) = Continue a <$ (liftVM0 ask `bindVM` \sctxt -> (if _inLoop sctxt then pure () else errorVM1 (_ContinueOutsideLoop # getAnn a)) *> (if _inFinally sctxt then errorVM1 (_ContinueInsideFinally # getAnn a) else pure ())) <*> validateWhitespace (getAnn a) ws validateSimpleStatementSyntax (Global a ws ids) = liftVM0 ask `bindVM` \ctx -> let params = ctx ^.. inFunction.folded.functionParams.folded in Global a ws <$> traverse (\i -> let ival = i ^. getting identValue in (if ival `elem` params then errorVM1 $ _ParameterMarkedGlobal # (getAnn a, ival) else pure ()) *> validateIdentSyntax i) ids validateSimpleStatementSyntax (Nonlocal a ws ids) = liftVM0 ask `bindVM` \sctxt -> liftVM0 get `bindVM` \nls -> (case deleteFirstsBy' (\a -> (==) (a ^. unvalidated.identValue)) (ids ^.. folded.unvalidated) nls of [] -> pure () ids -> traverse_ (\e -> errorVM1 (_NoBindingNonlocal # e)) ids) *> case sctxt ^? inFunction._Just.functionParams of Nothing -> errorVM1 (_NonlocalOutsideFunction # getAnn a) Just params -> case intersect params (ids ^.. folded.unvalidated.identValue) of [] -> Nonlocal a ws <$> traverse validateIdentSyntax ids bad -> errorVM1 (_ParametersNonlocal # (getAnn a, bad)) validateSimpleStatementSyntax (Del a ws ids) = Del a ws <$> traverse (\x -> validateExprSyntax x <* if canDelete x then pure () else errorVM1 $ _CannotDelete # (getAnn a, x ^. unvalidated)) ids validateSimpleStatementSyntax (Import a ws mns) = Import @(Nub (Syntax ': v)) a ws <$> traverse (validateImportAsSyntax validateModuleNameSyntax) mns validateSimpleStatementSyntax (From a ws1 mn ws2 ts) = From a ws1 <$> validateRelativeModuleNameSyntax mn <*> validateWhitespace (getAnn a) ws2 <*> validateImportTargetsSyntax ts canDelete :: Expr v a -> Bool canDelete None{} = False canDelete Ellipsis{} = False canDelete UnOp{} = False canDelete Int{} = False canDelete Call{} = False canDelete BinOp{} = False canDelete Bool{} = False canDelete Unit{} = False canDelete Yield{} = False canDelete YieldFrom{} = False canDelete Ternary{} = False canDelete ListComp{} = False canDelete DictComp{} = False canDelete Dict{} = False canDelete SetComp{} = False canDelete Set{} = False canDelete Lambda{} = False canDelete Float{} = False canDelete Imag{} = False canDelete Not{} = False canDelete Generator{} = False canDelete Await{} = False canDelete String{} = False canDelete (Parens _ _ a _) = canDelete a canDelete (List _ _ a _) = all (allOf (folded.getting _Exprs) canDelete) a && not (any (\case; ListUnpack{} -> True; _ -> False) $ a ^.. folded.folded) canDelete (Tuple _ a _ b) = all canDelete ((a ^?! getting _Exprs) : toListOf (folded.folded.getting _Exprs) b) && not (any (\case; TupleUnpack{} -> True; _ -> False) $ a : toListOf (folded.folded) b) canDelete Deref{} = True canDelete Subscript{} = True canDelete Ident{} = True validateSmallStatementSyntax :: ( AsSyntaxError e a , Member Indentation v ) => SmallStatement v a -> ValidateSyntax e (SmallStatement (Nub (Syntax ': v)) a) validateSmallStatementSyntax (MkSmallStatement s ss sc cmt nl) = (\s' ss' sc' -> MkSmallStatement s' ss' sc' cmt nl) <$> validateSimpleStatementSyntax s <*> traverse (bitraverse validateSemicolon validateSimpleStatementSyntax) ss <*> traverse validateSemicolon sc validateStatementSyntax :: ( AsSyntaxError e a , Member Indentation v ) => Statement v a -> ValidateSyntax e (Statement (Nub (Syntax ': v)) a) validateStatementSyntax (CompoundStatement c) = CompoundStatement <$> validateCompoundStatementSyntax c validateStatementSyntax (SmallStatement idnts a) = SmallStatement idnts <$> validateSmallStatementSyntax a canAssignTo :: Expr v a -> Bool canAssignTo None{} = False canAssignTo Ellipsis{} = False canAssignTo UnOp{} = False canAssignTo Int{} = False canAssignTo Call{} = False canAssignTo BinOp{} = False canAssignTo Bool{} = False canAssignTo Unit{} = False canAssignTo Yield{} = False canAssignTo YieldFrom{} = False canAssignTo Ternary{} = False canAssignTo ListComp{} = False canAssignTo DictComp{} = False canAssignTo Dict{} = False canAssignTo SetComp{} = False canAssignTo Set{} = False canAssignTo Lambda{} = False canAssignTo Float{} = False canAssignTo Imag{} = False canAssignTo Not{} = False canAssignTo Generator{} = False canAssignTo Await{} = False canAssignTo String{} = False canAssignTo (Parens _ _ a _) = canAssignTo a canAssignTo (List _ _ a _) = all (allOf (folded.getting _Exprs) canAssignTo) a canAssignTo (Tuple _ a _ b) = all canAssignTo ((a ^?! getting _Exprs) : toListOf (folded.folded.getting _Exprs) b) canAssignTo Deref{} = True canAssignTo Subscript{} = True canAssignTo Ident{} = True validateArgsSyntax :: ( AsSyntaxError e a , Member Indentation v ) => CommaSep1' (Arg v a) -> ValidateSyntax e (CommaSep1' (Arg (Nub (Syntax ': v)) a)) validateArgsSyntax e = unsafeCoerce e <$ go [] False False (toList e) where go :: (AsSyntaxError e a, Member Indentation v) => [String] -- ^ Have we seen a keyword argument? -> Bool -- ^ Have we seen a **argument? -> Bool -> [Arg v a] -> ValidateSyntax e [Arg (Nub (Syntax ': v)) a] go _ _ _ [] = pure [] go names False False (PositionalArg a expr : args) = liftA2 (:) (PositionalArg a <$> validateExprSyntax expr) (go names False False args) go names seenKeyword seenUnpack (PositionalArg a expr : args) = when seenKeyword (errorVM1 (_PositionalAfterKeywordArg # (getAnn a, expr ^. unvalidated))) *> when seenUnpack (errorVM1 (_PositionalAfterKeywordUnpacking # (getAnn a, expr ^. unvalidated))) *> go names seenKeyword seenUnpack args go names seenKeyword False (StarArg a ws expr : args) = liftA2 (:) (StarArg a <$> validateWhitespace (getAnn a) ws <*> validateExprSyntax expr) (go names seenKeyword False args) go names seenKeyword seenUnpack (StarArg a _ expr : args) = when seenKeyword (errorVM1 (_PositionalAfterKeywordArg # (getAnn a, expr ^. unvalidated))) *> when seenUnpack (errorVM1 (_PositionalAfterKeywordUnpacking # (getAnn a, expr ^. unvalidated))) *> go names seenKeyword seenUnpack args go names _ seenUnpack (KeywordArg a name ws2 expr : args) | _identValue name `elem` names = errorVM1 (_DuplicateArgument # (getAnn a, _identValue name)) <*> validateIdentSyntax name <*> go names True seenUnpack args | otherwise = liftA2 (:) (KeywordArg a <$> validateIdentSyntax name <*> pure ws2 <*> validateExprSyntax expr) (go (_identValue name:names) True seenUnpack args) go names seenKeyword _ (DoubleStarArg a ws expr : args) = liftA2 (:) (DoubleStarArg a <$> validateWhitespace (getAnn a) ws <*> validateExprSyntax expr) (go names seenKeyword True args) newtype HaveSeenStarArg = HaveSeenStarArg Bool newtype HaveSeenKeywordArg = HaveSeenKeywordArg Bool newtype HaveSeenEmptyStarArg a = HaveSeenEmptyStarArg (Maybe a) validateParamsSyntax :: forall e v a . ( AsSyntaxError e a , Member Indentation v ) => Bool -- ^ These are the parameters to a lambda -> CommaSep (Param v a) -> ValidateSyntax e (CommaSep (Param (Nub (Syntax ': v)) a)) validateParamsSyntax isLambda e = unsafeCoerce e <$ go [] (HaveSeenStarArg False) (HaveSeenEmptyStarArg Nothing) (HaveSeenKeywordArg False) (toList e) where checkTy :: a -> Maybe (Colon, Expr v a) -> ValidateSyntax e (Maybe (Colon, Expr (Nub (Syntax ': v)) a)) checkTy a mty = if isLambda then traverse (\_ -> errorVM1 (_TypedParamInLambda # a)) mty else traverseOf (traverse._2) validateExprSyntax mty go :: [String] -- identifiers that we've seen -> HaveSeenStarArg -- have we seen a star argument? -> HaveSeenEmptyStarArg a -- have we seen an empty star argument? -> HaveSeenKeywordArg -- have we seen a keyword parameter? -> [Param v a] -> ValidateSyntax e [Param (Nub (Syntax ': v)) a] go _ _ (HaveSeenEmptyStarArg b) _ [] = case b of Nothing -> pure [] Just b' -> errorVM1 $ _NoKeywordsAfterEmptyStarArg # b' go names bsa besa bkw@(HaveSeenKeywordArg False) (PositionalParam a name mty : params) | _identValue name `elem` names = errorVM1 (_DuplicateArgument # (getAnn a, _identValue name)) <*> validateIdentSyntax name <*> checkTy (getAnn a) mty <*> go (_identValue name:names) bsa besa bkw params | otherwise = liftA2 (:) (PositionalParam a <$> validateIdentSyntax name <*> checkTy (getAnn a) mty) (go (_identValue name:names) bsa besa bkw params) go names (HaveSeenStarArg b) besa bkw (StarParam a _ name mty : params) | _identValue name `elem` names = if b then errorVM1 (_ManyStarredParams # getAnn a) <*> errorVM1 (_DuplicateArgument # (getAnn a, _identValue name)) <*> validateIdentSyntax name <*> checkTy (getAnn a) mty <*> go (_identValue name:names) (HaveSeenStarArg True) besa bkw params else errorVM1 (_DuplicateArgument # (getAnn a, _identValue name)) <*> validateIdentSyntax name <*> checkTy (getAnn a) mty <*> go (_identValue name:names) (HaveSeenStarArg True) besa bkw params | otherwise = if b then errorVM1 (_ManyStarredParams # getAnn a) <*> validateIdentSyntax name *> checkTy (getAnn a) mty *> go (_identValue name:names) (HaveSeenStarArg True) besa bkw params else validateIdentSyntax name *> checkTy (getAnn a) mty *> go (_identValue name:names) (HaveSeenStarArg True) besa bkw params go names (HaveSeenStarArg b) _ bkw (UnnamedStarParam a _ : params) = if b then errorVM1 (_ManyStarredParams # getAnn a) <*> go names (HaveSeenStarArg True) (HaveSeenEmptyStarArg $ Just $ getAnn a) bkw params else go names (HaveSeenStarArg True) (HaveSeenEmptyStarArg $ Just $ getAnn a) bkw params go names bsa besa bkw@(HaveSeenKeywordArg True) (PositionalParam a name mty : params) = let name' = _identValue name errs = foldr (<|) (_PositionalAfterKeywordParam # (getAnn a, name') :| []) [_DuplicateArgument # (getAnn a, name') | name' `elem` names] in errorVM errs <*> checkTy (getAnn a) mty <*> go (name':names) bsa besa bkw params go names bsa _ _ (KeywordParam a name mty ws2 expr : params) | _identValue name `elem` names = errorVM1 (_DuplicateArgument # (getAnn a, _identValue name)) <*> checkTy (getAnn a) mty <*> go names bsa (HaveSeenEmptyStarArg Nothing) (HaveSeenKeywordArg True) params | otherwise = liftA2 (:) (KeywordParam a <$> validateIdentSyntax name <*> checkTy (getAnn a) mty <*> pure ws2 <*> validateExprSyntax expr) (go (_identValue name:names) bsa (HaveSeenEmptyStarArg Nothing) (HaveSeenKeywordArg True) params) go names bsa besa bkw [DoubleStarParam a ws name mty] | _identValue name `elem` names = errorVM1 (_DuplicateArgument # (getAnn a, _identValue name)) <*> checkTy (getAnn a) mty <* go names bsa besa bkw [] | otherwise = fmap pure $ DoubleStarParam a ws <$> validateIdentSyntax name <*> checkTy (getAnn a) mty <* go names bsa besa bkw [] go names bsa besa bkw (DoubleStarParam a _ name mty : _) = (if _identValue name `elem` names then errorVM1 (_DuplicateArgument # (getAnn a, _identValue name)) else pure ()) *> errorVM1 (_UnexpectedDoubleStarParam # (getAnn a, _identValue name)) <*> checkTy (getAnn a) mty <* go names bsa besa bkw [] validateModuleSyntax :: ( AsSyntaxError e a , Member Indentation v ) => Module v a -> ValidateSyntax e (Module (Nub (Syntax ': v)) a) validateModuleSyntax m = case m of ModuleEmpty -> pure ModuleEmpty ModuleBlankFinal a -> ModuleBlankFinal <$> validateBlankSyntax a ModuleBlank a b c -> (\a' -> ModuleBlank a' b) <$> validateBlankSyntax a <*> validateModuleSyntax c ModuleStatement a b -> ModuleStatement <$> validateStatementSyntax a <*> validateModuleSyntax b