1
1
mirror of https://github.com/github/semantic.git synced 2024-12-01 00:33:59 +03:00

Merge branch 'master' into analyses-provide-and-handle-effects

This commit is contained in:
Rob Rix 2018-04-25 16:43:00 -04:00
commit 2267f24ea8
26 changed files with 313 additions and 180 deletions

View File

@ -27,6 +27,7 @@ library
, Analysis.Abstract.Evaluating , Analysis.Abstract.Evaluating
, Analysis.Abstract.ImportGraph , Analysis.Abstract.ImportGraph
, Analysis.Abstract.Tracing , Analysis.Abstract.Tracing
, Analysis.Abstract.TypeChecking
, Analysis.CallGraph , Analysis.CallGraph
, Analysis.ConstructorName , Analysis.ConstructorName
, Analysis.CyclomaticComplexity , Analysis.CyclomaticComplexity
@ -166,6 +167,7 @@ library
, cmark-gfm , cmark-gfm
, containers , containers
, directory , directory
, directory-tree
, effects , effects
, filepath , filepath
, free , free

View File

@ -20,5 +20,5 @@ instance ( Interpreter effects result rest m
= interpret = interpret
. runBadModuleResolutions . runBadModuleResolutions
. raiseHandler (relay pure (\ (Resumable err) yield -> traceM ("ResolutionError:" <> show err) *> case err of . raiseHandler (relay pure (\ (Resumable err) yield -> traceM ("ResolutionError:" <> show err) *> case err of
RubyError nameToResolve -> yield nameToResolve NotFoundError nameToResolve _ _ -> yield nameToResolve
TypeScriptError nameToResolve -> yield nameToResolve)) GoImportError pathToResolve -> yield [pathToResolve]))

View File

@ -69,8 +69,7 @@ instance ( Effectful m
, term ~ Term (Union syntax) ann , term ~ Term (Union syntax) ann
) )
=> MonadAnalysis (Located location term) term value effects (ImportGraphing m) where => MonadAnalysis (Located location term) term value effects (ImportGraphing m) where
analyzeTerm eval term@(In ann syntax) = do analyzeTerm eval term@(In _ syntax) = do
traceShowM ann
case prj syntax of case prj syntax of
Just (Syntax.Identifier name) -> do Just (Syntax.Identifier name) -> do
moduleInclusion (Variable (unName name)) moduleInclusion (Variable (unName name))

View File

@ -0,0 +1,46 @@
{-# LANGUAGE GADTs, GeneralizedNewtypeDeriving, KindSignatures, ScopedTypeVariables, TypeOperators, UndecidableInstances #-}
module Analysis.Abstract.TypeChecking
( TypeChecking
) where
import Control.Abstract.Analysis
import Data.Abstract.Type
import Prologue hiding (TypeError)
newtype TypeChecking m (effects :: [* -> *]) a = TypeChecking { runTypeChecking :: m effects a }
deriving (Alternative, Applicative, Functor, Effectful, Monad)
deriving instance MonadEvaluator location term value effects m => MonadEvaluator location term value effects (TypeChecking m)
instance ( Effectful m
, Alternative (m effects)
, MonadAnalysis location term Type effects m
, Member (Resumable TypeError) effects
, Member NonDet effects
, MonadValue location Type effects (TypeChecking m)
)
=> MonadAnalysis location term Type effects (TypeChecking m) where
analyzeTerm eval term =
resume @TypeError (liftAnalyze analyzeTerm eval term) (
\yield err -> case err of
-- TODO: These should all yield both sides of the exception,
-- but something is mysteriously busted in the innards of typechecking,
-- so doing that just yields an empty list in the result type, which isn't
-- extraordinarily helpful. Better for now to just die with an error and
-- tackle this issue in a separate PR.
BitOpError{} -> throwResumable err
NumOpError{} -> throwResumable err
UnificationError{} -> throwResumable err
)
analyzeModule = liftAnalyze analyzeModule
instance ( Interpreter effects (Either (SomeExc TypeError) result) rest m
, MonadEvaluator location term value effects m
)
=> Interpreter (Resumable TypeError ': effects) result rest (TypeChecking m) where
interpret
= interpret
. runTypeChecking
. raiseHandler runError

View File

@ -37,6 +37,7 @@ module Control.Abstract.Evaluator
, putLoadStack , putLoadStack
, modifyLoadStack , modifyLoadStack
, currentModule , currentModule
, currentPackage
-- Control -- Control
, label , label
, goto , goto
@ -60,6 +61,7 @@ import Data.Abstract.FreeVariables
import Data.Abstract.Heap import Data.Abstract.Heap
import Data.Abstract.Module import Data.Abstract.Module
import Data.Abstract.ModuleTable import Data.Abstract.ModuleTable
import Data.Abstract.Package
import Data.Abstract.Origin import Data.Abstract.Origin
import Data.Empty import Data.Empty
import qualified Data.IntMap as IntMap import qualified Data.IntMap as IntMap
@ -308,6 +310,12 @@ currentModule = do
o <- raise ask o <- raise ask
maybeM (raise (fail "unable to get currentModule")) $ withSomeOrigin (originModule @term) o maybeM (raise (fail "unable to get currentModule")) $ withSomeOrigin (originModule @term) o
-- | Get the currently evaluating 'PackageInfo'.
currentPackage :: forall location term value effects m . MonadEvaluator location term value effects m => m effects PackageInfo
currentPackage = do
o <- raise ask
maybeM (raise (fail "unable to get currentPackage")) $ withSomeOrigin (originPackage @term) o
-- Control -- Control

View File

@ -38,6 +38,7 @@ import Data.Abstract.Module
import Data.Abstract.ModuleTable as ModuleTable import Data.Abstract.ModuleTable as ModuleTable
import Data.Abstract.Origin (SomeOrigin, packageOrigin) import Data.Abstract.Origin (SomeOrigin, packageOrigin)
import Data.Abstract.Package as Package import Data.Abstract.Package as Package
import Data.Language
import Data.Scientific (Scientific) import Data.Scientific (Scientific)
import Data.Semigroup.App import Data.Semigroup.App
import Data.Semigroup.Foldable import Data.Semigroup.Foldable
@ -54,7 +55,6 @@ type MonadEvaluatable location term value effects m =
, Member (Exc.Exc (LoopThrow value)) effects , Member (Exc.Exc (LoopThrow value)) effects
, Member Fail effects , Member Fail effects
, Member (Resumable (Unspecialized value)) effects , Member (Resumable (Unspecialized value)) effects
, Member (Resumable (ValueError location value)) effects
, Member (Resumable (LoadError term value)) effects , Member (Resumable (LoadError term value)) effects
, Member (Resumable (EvalError value)) effects , Member (Resumable (EvalError value)) effects
, Member (Resumable (ResolutionError value)) effects , Member (Resumable (ResolutionError value)) effects
@ -78,17 +78,20 @@ data LoopThrow value
-- | An error thrown when we can't resolve a module from a qualified name. -- | An error thrown when we can't resolve a module from a qualified name.
data ResolutionError value resume where data ResolutionError value resume where
RubyError :: String -> ResolutionError value ModulePath NotFoundError :: String -- ^ The path that was not found.
TypeScriptError :: String -> ResolutionError value ModulePath -> [String] -- ^ List of paths searched that shows where semantic looked for this module.
-> Language -- ^ Language.
-> ResolutionError value ModulePath
GoImportError :: FilePath -> ResolutionError value [ModulePath]
deriving instance Eq (ResolutionError a b) deriving instance Eq (ResolutionError a b)
deriving instance Show (ResolutionError a b) deriving instance Show (ResolutionError a b)
instance Show1 (ResolutionError value) where instance Show1 (ResolutionError value) where liftShowsPrec _ _ = showsPrec
liftShowsPrec _ _ = showsPrec
instance Eq1 (ResolutionError value) where instance Eq1 (ResolutionError value) where
liftEq _ (RubyError a) (RubyError b) = a == b liftEq _ (NotFoundError a _ l1) (NotFoundError b _ l2) = a == b && l1 == l2
liftEq _ (TypeScriptError a) (TypeScriptError b) = a == b liftEq _ (GoImportError a) (GoImportError b) = a == b
liftEq _ _ _ = False liftEq _ _ _ = False
-- | An error thrown when loading a module from the list of provided modules. Indicates we weren't able to find a module with the given name. -- | An error thrown when loading a module from the list of provided modules. Indicates we weren't able to find a module with the given name.
data LoadError term value resume where data LoadError term value resume where

View File

@ -1,8 +1,10 @@
{-# LANGUAGE TupleSections #-}
module Data.Abstract.Package where module Data.Abstract.Package where
import Data.Abstract.FreeVariables import Data.Abstract.FreeVariables
import Data.Abstract.Module import Data.Abstract.Module
import Data.Abstract.ModuleTable as ModuleTable import Data.Abstract.ModuleTable as ModuleTable
import qualified Data.Map as Map
type PackageName = Name type PackageName = Name
@ -31,12 +33,8 @@ data Package term = Package
} }
deriving (Eq, Functor, Ord, Show) deriving (Eq, Functor, Ord, Show)
fromModules :: PackageName -> Maybe Version -> Maybe (Module term) -> [Module term] -> Package term fromModules :: PackageName -> Maybe Version -> Maybe (Module term) -> Int -> [Module term] -> Package term
fromModules name version prelude = Package (PackageInfo name version) . go prelude fromModules name version prelude entryPoints modules =
Package (PackageInfo name version) (PackageBody (ModuleTable.fromModules modules) prelude entryPoints')
where where
go :: Maybe (Module term) -> [Module term] -> PackageBody term entryPoints' = ModuleTable . Map.fromList $ (,Nothing) . modulePath . moduleInfo <$> if entryPoints == 0 then modules else take entryPoints modules
go p [] = PackageBody mempty p mempty
go p (m:ms) = PackageBody (ModuleTable.fromModules (m : ms)) p entryPoints
where
entryPoints = ModuleTable.singleton path Nothing
path = modulePath (moduleInfo m)

View File

@ -1,14 +1,18 @@
{-# LANGUAGE TypeFamilies, UndecidableInstances #-} {-# LANGUAGE GADTs, TypeFamilies, UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-redundant-constraints #-} -- For the MonadValue instance, which requires MonadEvaluator to resolve its functional dependency. {-# OPTIONS_GHC -Wno-redundant-constraints #-} -- For the MonadValue instance, which requires MonadEvaluator to resolve its functional dependency.
module Data.Abstract.Type where module Data.Abstract.Type
( Type (..)
, TypeError (..)
, unify
) where
import Control.Abstract.Analysis import Control.Abstract.Analysis
import Data.Abstract.Address import Data.Abstract.Address
import Data.Abstract.Environment as Env import Data.Abstract.Environment as Env
import Data.Align (alignWith) import Data.Align (alignWith)
import Data.Semigroup.Reducer (Reducer) import Data.Semigroup.Reducer (Reducer)
import Prelude hiding (fail) import Prelude
import Prologue import Prologue hiding (TypeError)
type TName = Int type TName = Int
@ -33,9 +37,26 @@ data Type
-- TODO: À la carte representation of types. -- TODO: À la carte representation of types.
data TypeError resume where
NumOpError :: Type -> Type -> TypeError Type
BitOpError :: Type -> Type -> TypeError Type
UnificationError :: Type -> Type -> TypeError Type
deriving instance Show (TypeError resume)
instance Show1 TypeError where
liftShowsPrec _ _ _ (NumOpError l r) = showString "NumOpError " . shows [l, r]
liftShowsPrec _ _ _ (BitOpError l r) = showString "BitOpError " . shows [l, r]
liftShowsPrec _ _ _ (UnificationError l r) = showString "UnificationError " . shows [l, r]
instance Eq1 TypeError where
liftEq _ (BitOpError a b) (BitOpError c d) = a == c && b == d
liftEq _ (NumOpError a b) (NumOpError c d) = a == c && b == d
liftEq _ (UnificationError a b) (UnificationError c d) = a == c && b == d
liftEq _ _ _ = False
-- | Unify two 'Type's. -- | Unify two 'Type's.
unify :: (Effectful m, Applicative (m effects), Member Fail effects) => Type -> Type -> m effects Type unify :: (Effectful m, Applicative (m effects), Member (Resumable TypeError) effects) => Type -> Type -> m effects Type
unify (a1 :-> b1) (a2 :-> b2) = (:->) <$> unify a1 a2 <*> unify b1 b2 unify (a1 :-> b1) (a2 :-> b2) = (:->) <$> unify a1 a2 <*> unify b1 b2
unify a Null = pure a unify a Null = pure a
unify Null b = pure b unify Null b = pure b
@ -45,8 +66,7 @@ unify a (Var _) = pure a
unify (Product as) (Product bs) = Product <$> sequenceA (alignWith (these pure pure unify) as bs) unify (Product as) (Product bs) = Product <$> sequenceA (alignWith (these pure pure unify) as bs)
unify t1 t2 unify t1 t2
| t1 == t2 = pure t2 | t1 == t2 = pure t2
| otherwise = raise (fail ("cannot unify " ++ show t1 ++ " with " ++ show t2)) | otherwise = throwResumable (UnificationError t1 t2)
instance Ord location => ValueRoots location Type where instance Ord location => ValueRoots location Type where
valueRoots _ = mempty valueRoots _ = mempty
@ -57,8 +77,8 @@ instance AbstractHole Type where
-- | Discard the value arguments (if any), constructing a 'Type' instead. -- | Discard the value arguments (if any), constructing a 'Type' instead.
instance ( Alternative (m effects) instance ( Alternative (m effects)
, Member Fail effects
, Member Fresh effects , Member Fresh effects
, Member (Resumable TypeError) effects
, MonadAddressable location effects m , MonadAddressable location effects m
, MonadEvaluator location term Type effects m , MonadEvaluator location term Type effects m
, Reducer Type (Cell location Type) , Reducer Type (Cell location Type)
@ -93,28 +113,31 @@ instance ( Alternative (m effects)
scopedEnvironment _ = pure mempty scopedEnvironment _ = pure mempty
asString _ = raise (fail "Must evaluate to Value to use asString") asString t = unify t String $> ""
asPair _ = raise (fail "Must evaluate to Value to use asPair") asPair t = do
asBool _ = raise (fail "Must evaluate to Value to use asBool") t1 <- raise fresh
t2 <- raise fresh
unify t (Product [Var t1, Var t2]) $> (Var t1, Var t2)
asBool t = unify t Bool *> (pure True <|> pure False)
isHole ty = pure (ty == Hole) isHole ty = pure (ty == Hole)
ifthenelse cond if' else' = unify cond Bool *> (if' <|> else') ifthenelse cond if' else' = unify cond Bool *> (if' <|> else')
liftNumeric _ Float = pure Float liftNumeric _ Float = pure Float
liftNumeric _ Int = pure Int liftNumeric _ Int = pure Int
liftNumeric _ _ = raise (fail "Invalid type in unary numeric operation") liftNumeric _ t = throwResumable (NumOpError t Hole)
liftNumeric2 _ left right = case (left, right) of liftNumeric2 _ left right = case (left, right) of
(Float, Int) -> pure Float (Float, Int) -> pure Float
(Int, Float) -> pure Float (Int, Float) -> pure Float
_ -> unify left right _ -> unify left right
liftBitwise _ Int = pure Int liftBitwise _ Int = pure Int
liftBitwise _ t = raise (fail ("Invalid type passed to unary bitwise operation: " <> show t)) liftBitwise _ t = throwResumable (BitOpError t Hole)
liftBitwise2 _ Int Int = pure Int liftBitwise2 _ Int Int = pure Int
liftBitwise2 _ t1 t2 = raise (fail ("Invalid types passed to binary bitwise operation: " <> show (t1, t2))) liftBitwise2 _ t1 t2 = throwResumable (BitOpError t1 t2)
liftComparison (Concrete _) left right = case (left, right) of liftComparison (Concrete _) left right = case (left, right) of
(Float, Int) -> pure Bool (Float, Int) -> pure Bool
@ -128,9 +151,10 @@ instance ( Alternative (m effects)
call op params = do call op params = do
tvar <- raise fresh tvar <- raise fresh
paramTypes <- sequenceA params paramTypes <- sequenceA params
unified <- op `unify` (Product paramTypes :-> Var tvar) let needed = Product paramTypes :-> Var tvar
unified <- op `unify` needed
case unified of case unified of
_ :-> ret -> pure ret _ :-> ret -> pure ret
_ -> raise (fail "unification with a function produced something other than a function") gotten -> throwResumable (UnificationError needed gotten)
loop f = f empty loop f = f empty

View File

@ -202,7 +202,11 @@ instance AbstractHole (Value location) where
hole = injValue Hole hole = injValue Hole
-- | Construct a 'Value' wrapping the value arguments (if any). -- | Construct a 'Value' wrapping the value arguments (if any).
instance (Monad (m effects), MonadEvaluatable location term (Value location) effects m) => MonadValue location (Value location) effects m where instance ( Monad (m effects)
, Member (Resumable (ValueError location (Value location))) effects
, MonadEvaluatable location term (Value location) effects m
)
=> MonadValue location (Value location) effects m where
unit = pure . injValue $ Unit unit = pure . injValue $ Unit
integer = pure . injValue . Integer . Number.Integer integer = pure . injValue . Integer . Number.Integer
boolean = pure . injValue . Boolean boolean = pure . injValue . Boolean
@ -282,7 +286,7 @@ instance (Monad (m effects), MonadEvaluatable location term (Value location) eff
tentative x i j = attemptUnsafeArithmetic (x i j) tentative x i j = attemptUnsafeArithmetic (x i j)
-- Dispatch whatever's contained inside a 'Number.SomeNumber' to its appropriate 'MonadValue' ctor -- Dispatch whatever's contained inside a 'Number.SomeNumber' to its appropriate 'MonadValue' ctor
specialize :: MonadEvaluatable location term value effects m => Either ArithException Number.SomeNumber -> m effects value specialize :: (Member (Resumable (ValueError location value)) effects, MonadEvaluatable location term value effects m) => Either ArithException Number.SomeNumber -> m effects value
specialize (Left exc) = throwValueError (ArithmeticError exc) specialize (Left exc) = throwValueError (ArithmeticError exc)
specialize (Right (Number.SomeNumber (Number.Integer i))) = integer i specialize (Right (Number.SomeNumber (Number.Integer i))) = integer i
specialize (Right (Number.SomeNumber (Number.Ratio r))) = rational r specialize (Right (Number.SomeNumber (Number.Ratio r))) = rational r

View File

@ -2,7 +2,6 @@ module Data.File where
import Data.ByteString.Char8 as BC (pack) import Data.ByteString.Char8 as BC (pack)
import Data.Language import Data.Language
import qualified Data.List.NonEmpty as NonEmpty
import Prologue import Prologue
import System.FilePath.Posix import System.FilePath.Posix
@ -12,28 +11,20 @@ data File = File
} }
deriving (Eq, Ord, Show) deriving (Eq, Ord, Show)
data Project = Project
{ projectRootDir :: FilePath
, projectFiles :: [File]
, projectLanguage :: Language
, projectEntryPoints :: [File]
}
deriving (Eq, Ord, Show)
file :: FilePath -> File file :: FilePath -> File
file path = File path (languageForFilePath path) file path = File path (languageForFilePath path)
where languageForFilePath = languageForType . takeExtension where languageForFilePath = languageForType . takeExtension
data Project = Project
{ projectEntryPoints :: NonEmpty File
, projectRootDir :: FilePath
, projectFiles :: [File]
}
deriving (Eq, Ord, Show)
projectAllFiles :: Project -> [File]
projectAllFiles Project{..} = NonEmpty.toList projectEntryPoints <> projectFiles
projectName :: Project -> ByteString projectName :: Project -> ByteString
projectName = BC.pack . dropExtensions . takeFileName . projectRootDir projectName = BC.pack . dropExtensions . takeFileName . projectRootDir
projectLanguage :: Project -> Maybe Language
projectLanguage = fileLanguage. projectEntryPoint
projectEntryPoint :: Project -> File
projectEntryPoint = NonEmpty.head . projectEntryPoints
projectExtensions :: Project -> [String] projectExtensions :: Project -> [String]
projectExtensions = extensionsForLanguage . projectLanguage projectExtensions = extensionsForLanguage . projectLanguage

View File

@ -33,13 +33,12 @@ languageForType mediaType = case mediaType of
".phpt" -> Just PHP ".phpt" -> Just PHP
_ -> Nothing _ -> Nothing
extensionsForLanguage :: Maybe Language -> [String] extensionsForLanguage :: Language -> [String]
extensionsForLanguage Nothing = [] extensionsForLanguage language = case language of
extensionsForLanguage (Just language) = case language of Go -> [".go"]
Go -> ["go"] JavaScript -> [".js"]
JavaScript -> ["js"] PHP -> [".php"]
PHP -> ["php"] Python -> [".py"]
Python -> ["py"] Ruby -> [".rb"]
Ruby -> ["rb"] TypeScript -> [".ts", ".tsx", ".d.tsx"]
TypeScript -> ["ts", "tsx", "d.tsx"]
_ -> [] _ -> []

View File

@ -1,32 +1,49 @@
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass, ScopedTypeVariables #-}
module Language.Go.Syntax where module Language.Go.Syntax where
import Data.Abstract.Evaluatable hiding (Label) import Data.Abstract.Evaluatable hiding (Label)
import Data.Abstract.Module import Data.Abstract.FreeVariables (Name (..), name)
import Data.Abstract.Path import Data.Abstract.Module
import Data.Abstract.FreeVariables (name) import qualified Data.Abstract.Package as Package
import Diffing.Algorithm import Data.Abstract.Path
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString as B import qualified Data.ByteString as B
import System.FilePath.Posix import qualified Data.ByteString.Char8 as BC
import Prologue import Diffing.Algorithm
import Prologue
import System.FilePath.Posix
newtype ImportPath = ImportPath { unPath :: FilePath } data Relative = Relative | NonRelative
deriving (Eq, Ord, Show)
data ImportPath = ImportPath { unPath :: FilePath, pathIsRelative :: Relative }
deriving (Eq, Ord, Show) deriving (Eq, Ord, Show)
importPath :: ByteString -> ImportPath importPath :: ByteString -> ImportPath
importPath str = let path = stripQuotes str in ImportPath (BC.unpack path) importPath str = let path = stripQuotes str in ImportPath (BC.unpack path) (pathType path)
where stripQuotes = B.filter (`B.notElem` "\'\"") where
stripQuotes = B.filter (`B.notElem` "\'\"")
pathType xs | not (B.null xs), BC.head xs == '.' = Relative
| otherwise = NonRelative
defaultAlias :: ImportPath -> Name defaultAlias :: ImportPath -> Name
defaultAlias = name . BC.pack . takeFileName . unPath defaultAlias = name . BC.pack . takeFileName . unPath
-- TODO: need to delineate between relative and absolute Go imports resolveGoImport :: forall value term location effects m. MonadEvaluatable location term value effects m => ImportPath -> m effects [ModulePath]
resolveGoImport :: MonadEvaluatable location term value effects m => FilePath -> m effects [ModulePath] resolveGoImport (ImportPath path Relative) = do
resolveGoImport relImportPath = do
ModuleInfo{..} <- currentModule ModuleInfo{..} <- currentModule
let relRootDir = takeDirectory modulePath paths <- listModulesInDir (joinPaths (takeDirectory modulePath) path)
listModulesInDir (joinPaths relRootDir relImportPath) case paths of
[] -> throwResumable @(ResolutionError value) $ GoImportError path
_ -> pure paths
resolveGoImport (ImportPath path NonRelative) = do
package <- BC.unpack . unName . Package.packageName <$> currentPackage
traceM ("attempting to resolve " <> show path <> " for package " <> package)
case splitDirectories path of
-- Import an absolute path that's defined in this package being analyized.
-- First two are source, next is package name, remaining are path to package
-- (e.g. github.com/golang/<package>/path...).
(_ : _ : p : xs) | p == package -> listModulesInDir (joinPath xs)
_ -> throwResumable @(ResolutionError value) $ GoImportError path
-- | Import declarations (symbols are added directly to the calling environment). -- | Import declarations (symbols are added directly to the calling environment).
-- --
@ -39,10 +56,10 @@ instance Ord1 Import where liftCompare = genericLiftCompare
instance Show1 Import where liftShowsPrec = genericLiftShowsPrec instance Show1 Import where liftShowsPrec = genericLiftShowsPrec
instance Evaluatable Import where instance Evaluatable Import where
eval (Import (ImportPath name) _) = do eval (Import importPath _) = do
paths <- resolveGoImport name paths <- resolveGoImport importPath
for_ paths $ \path -> do for_ paths $ \path -> do
(importedEnv, _) <- traceResolve name path $ isolate (require path) (importedEnv, _) <- traceResolve (unPath importPath) path $ isolate (require path)
modifyEnv (mappend importedEnv) modifyEnv (mappend importedEnv)
unit unit
@ -58,12 +75,12 @@ instance Ord1 QualifiedImport where liftCompare = genericLiftCompare
instance Show1 QualifiedImport where liftShowsPrec = genericLiftShowsPrec instance Show1 QualifiedImport where liftShowsPrec = genericLiftShowsPrec
instance Evaluatable QualifiedImport where instance Evaluatable QualifiedImport where
eval (QualifiedImport (ImportPath name) aliasTerm) = do eval (QualifiedImport importPath aliasTerm) = do
paths <- resolveGoImport name paths <- resolveGoImport importPath
alias <- either (throwEvalError . FreeVariablesError) pure (freeVariable $ subterm aliasTerm) alias <- either (throwEvalError . FreeVariablesError) pure (freeVariable $ subterm aliasTerm)
void $ letrec' alias $ \addr -> do void $ letrec' alias $ \addr -> do
for_ paths $ \path -> do for_ paths $ \path -> do
(importedEnv, _) <- traceResolve name path $ isolate (require path) (importedEnv, _) <- traceResolve (unPath importPath) path $ isolate (require path)
modifyEnv (mappend importedEnv) modifyEnv (mappend importedEnv)
makeNamespace alias addr [] makeNamespace alias addr []
@ -78,9 +95,9 @@ instance Ord1 SideEffectImport where liftCompare = genericLiftCompare
instance Show1 SideEffectImport where liftShowsPrec = genericLiftShowsPrec instance Show1 SideEffectImport where liftShowsPrec = genericLiftShowsPrec
instance Evaluatable SideEffectImport where instance Evaluatable SideEffectImport where
eval (SideEffectImport (ImportPath name) _) = do eval (SideEffectImport importPath _) = do
paths <- resolveGoImport name paths <- resolveGoImport importPath
for_ paths $ \path -> traceResolve name path $ isolate (require path) for_ paths $ \path -> traceResolve (unPath importPath) path $ isolate (require path)
unit unit
-- A composite literal in Go -- A composite literal in Go

View File

@ -1,10 +1,11 @@
{-# LANGUAGE DeriveAnyClass, ViewPatterns #-} {-# LANGUAGE DeriveAnyClass, ViewPatterns, ScopedTypeVariables #-}
module Language.PHP.Syntax where module Language.PHP.Syntax where
import Data.Abstract.Evaluatable import Data.Abstract.Evaluatable
import Data.Abstract.Module import Data.Abstract.Module
import Data.Abstract.Path import Data.Abstract.Path
import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Char8 as BC
import qualified Data.Language as Language
import Diffing.Algorithm import Diffing.Algorithm
import Prelude hiding (fail) import Prelude hiding (fail)
import Prologue hiding (Text) import Prologue hiding (Text)
@ -34,25 +35,21 @@ instance Evaluatable VariableName
-- file, the complete contents of the included file are treated as though it -- file, the complete contents of the included file are treated as though it
-- were defined inside that function. -- were defined inside that function.
resolvePHPName :: MonadEvaluatable location term value effects m => ByteString -> m effects ModulePath resolvePHPName :: forall value location term effects m. MonadEvaluatable location term value effects m => ByteString -> m effects ModulePath
resolvePHPName n = resolve [name] >>= maybeM (raise (fail notFound)) resolvePHPName n = do
modulePath <- resolve [name]
maybe (throwResumable @(ResolutionError value) $ NotFoundError name [name] Language.PHP) pure modulePath
where name = toName n where name = toName n
notFound = "Unable to resolve: " <> name
toName = BC.unpack . dropRelativePrefix . stripQuotes toName = BC.unpack . dropRelativePrefix . stripQuotes
doInclude :: MonadEvaluatable location term value effects m => Subterm t (m effects value) -> m effects value include :: MonadEvaluatable location term value effects m
doInclude pathTerm = do => Subterm t (m effects value)
-> (ModulePath -> m effects (Environment location value, value))
-> m effects value
include pathTerm f = do
name <- subtermValue pathTerm >>= asString name <- subtermValue pathTerm >>= asString
path <- resolvePHPName name path <- resolvePHPName name
(importedEnv, v) <- traceResolve name path $ isolate (load path) (importedEnv, v) <- traceResolve name path $ isolate (f path)
modifyEnv (mappend importedEnv)
pure v
doIncludeOnce :: MonadEvaluatable location term value effects m => Subterm t (m effects value) -> m effects value
doIncludeOnce pathTerm = do
name <- subtermValue pathTerm >>= asString
path <- resolvePHPName name
(importedEnv, v) <- traceResolve name path $ isolate (require path)
modifyEnv (mappend importedEnv) modifyEnv (mappend importedEnv)
pure v pure v
@ -64,7 +61,7 @@ instance Ord1 Require where liftCompare = genericLiftCompare
instance Show1 Require where liftShowsPrec = genericLiftShowsPrec instance Show1 Require where liftShowsPrec = genericLiftShowsPrec
instance Evaluatable Require where instance Evaluatable Require where
eval (Require path) = doInclude path eval (Require path) = include path load
newtype RequireOnce a = RequireOnce a newtype RequireOnce a = RequireOnce a
@ -75,7 +72,7 @@ instance Ord1 RequireOnce where liftCompare = genericLiftCompare
instance Show1 RequireOnce where liftShowsPrec = genericLiftShowsPrec instance Show1 RequireOnce where liftShowsPrec = genericLiftShowsPrec
instance Evaluatable RequireOnce where instance Evaluatable RequireOnce where
eval (RequireOnce path) = doIncludeOnce path eval (RequireOnce path) = include path require
newtype Include a = Include a newtype Include a = Include a
@ -86,7 +83,7 @@ instance Ord1 Include where liftCompare = genericLiftCompare
instance Show1 Include where liftShowsPrec = genericLiftShowsPrec instance Show1 Include where liftShowsPrec = genericLiftShowsPrec
instance Evaluatable Include where instance Evaluatable Include where
eval (Include path) = doInclude path eval (Include path) = include path load
newtype IncludeOnce a = IncludeOnce a newtype IncludeOnce a = IncludeOnce a
@ -97,7 +94,7 @@ instance Ord1 IncludeOnce where liftCompare = genericLiftCompare
instance Show1 IncludeOnce where liftShowsPrec = genericLiftShowsPrec instance Show1 IncludeOnce where liftShowsPrec = genericLiftShowsPrec
instance Evaluatable IncludeOnce where instance Evaluatable IncludeOnce where
eval (IncludeOnce path) = doIncludeOnce path eval (IncludeOnce path) = include path require
newtype ArrayElement a = ArrayElement a newtype ArrayElement a = ArrayElement a

View File

@ -1,4 +1,4 @@
{-# LANGUAGE DeriveAnyClass, MultiParamTypeClasses #-} {-# LANGUAGE DeriveAnyClass, MultiParamTypeClasses, ScopedTypeVariables #-}
module Language.Python.Syntax where module Language.Python.Syntax where
import Data.Abstract.Environment as Env import Data.Abstract.Environment as Env
@ -8,7 +8,7 @@ import Data.Abstract.Module
import Data.Align.Generic import Data.Align.Generic
import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Char8 as BC
import Data.Functor.Classes.Generic import Data.Functor.Classes.Generic
import Data.List (intercalate) import qualified Data.Language as Language
import qualified Data.List.NonEmpty as NonEmpty import qualified Data.List.NonEmpty as NonEmpty
import Data.Mergeable import Data.Mergeable
import Diffing.Algorithm import Diffing.Algorithm
@ -51,14 +51,14 @@ relativeQualifiedName prefix paths = RelativeQualifiedName (BC.unpack prefix) (J
-- Subsequent imports of `parent.two` or `parent.three` will execute -- Subsequent imports of `parent.two` or `parent.three` will execute
-- `parent/two/__init__.py` and -- `parent/two/__init__.py` and
-- `parent/three/__init__.py` respectively. -- `parent/three/__init__.py` respectively.
resolvePythonModules :: MonadEvaluatable location term value effects m => QualifiedName -> m effects (NonEmpty ModulePath) resolvePythonModules :: forall value term location effects m. MonadEvaluatable location term value effects m => QualifiedName -> m effects (NonEmpty ModulePath)
resolvePythonModules q = do resolvePythonModules q = do
relRootDir <- rootDir q <$> currentModule relRootDir <- rootDir q <$> currentModule
for (moduleNames q) $ \name -> do for (moduleNames q) $ \name -> do
x <- search relRootDir name x <- search relRootDir name
traceResolve name x $ pure x traceResolve name x $ pure x
where where
rootDir (QualifiedName _) ModuleInfo{..} = takeDirectory modulePath rootDir (QualifiedName _) ModuleInfo{..} = mempty -- overall rootDir of the Package.
rootDir (RelativeQualifiedName n _) ModuleInfo{..} = upDir numDots (takeDirectory modulePath) rootDir (RelativeQualifiedName n _) ModuleInfo{..} = upDir numDots (takeDirectory modulePath)
where numDots = pred (length n) where numDots = pred (length n)
upDir n dir | n <= 0 = dir upDir n dir | n <= 0 = dir
@ -68,17 +68,14 @@ resolvePythonModules q = do
moduleNames (RelativeQualifiedName x Nothing) = error $ "importing from '" <> show x <> "' is not implemented" moduleNames (RelativeQualifiedName x Nothing) = error $ "importing from '" <> show x <> "' is not implemented"
moduleNames (RelativeQualifiedName _ (Just paths)) = moduleNames paths moduleNames (RelativeQualifiedName _ (Just paths)) = moduleNames paths
notFound xs = "Unable to resolve module import: " <> friendlyName q <> ", searched: " <> show xs
search rootDir x = do search rootDir x = do
traceM ("searching for " <> show x <> " in " <> show rootDir)
let path = normalise (rootDir </> normalise x) let path = normalise (rootDir </> normalise x)
let searchPaths = [ path </> "__init__.py" let searchPaths = [ path </> "__init__.py"
, path <.> ".py" , path <.> ".py"
] ]
resolve searchPaths >>= maybeM (raise (fail (notFound searchPaths))) modulePath <- resolve searchPaths
maybe (throwResumable @(ResolutionError value) $ NotFoundError path searchPaths Language.Python) pure modulePath
friendlyName :: QualifiedName -> String
friendlyName (QualifiedName xs) = intercalate "." (NonEmpty.toList xs)
friendlyName (RelativeQualifiedName prefix qn) = prefix <> maybe "" friendlyName qn
-- | Import declarations (symbols are added directly to the calling environment). -- | Import declarations (symbols are added directly to the calling environment).

View File

@ -7,6 +7,7 @@ import Data.Abstract.Module (ModulePath)
import Data.Abstract.ModuleTable as ModuleTable import Data.Abstract.ModuleTable as ModuleTable
import Data.Abstract.Path import Data.Abstract.Path
import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Char8 as BC
import qualified Data.Language as Language
import Diffing.Algorithm import Diffing.Algorithm
import Prelude hiding (fail) import Prelude hiding (fail)
import Prologue import Prologue
@ -19,15 +20,16 @@ import System.FilePath.Posix
resolveRubyName :: forall value term location effects m. MonadEvaluatable location term value effects m => ByteString -> m effects ModulePath resolveRubyName :: forall value term location effects m. MonadEvaluatable location term value effects m => ByteString -> m effects ModulePath
resolveRubyName name = do resolveRubyName name = do
let name' = cleanNameOrPath name let name' = cleanNameOrPath name
modulePath <- resolve [name' <.> "rb"] let paths = [name' <.> "rb"]
maybe (throwResumable @(ResolutionError value) $ RubyError name') pure modulePath modulePath <- resolve paths
maybe (throwResumable @(ResolutionError value) $ NotFoundError name' paths Language.Ruby) pure modulePath
-- load "/root/src/file.rb" -- load "/root/src/file.rb"
resolveRubyPath :: forall value term location effects m. MonadEvaluatable location term value effects m => ByteString -> m effects ModulePath resolveRubyPath :: forall value term location effects m. MonadEvaluatable location term value effects m => ByteString -> m effects ModulePath
resolveRubyPath path = do resolveRubyPath path = do
let name' = cleanNameOrPath path let name' = cleanNameOrPath path
modulePath <- resolve [name'] modulePath <- resolve [name']
maybe (throwResumable @(ResolutionError value) $ RubyError name') pure modulePath maybe (throwResumable @(ResolutionError value) $ NotFoundError name' [name'] Language.Ruby) pure modulePath
cleanNameOrPath :: ByteString -> String cleanNameOrPath :: ByteString -> String
cleanNameOrPath = BC.unpack . dropRelativePrefix . stripQuotes cleanNameOrPath = BC.unpack . dropRelativePrefix . stripQuotes

View File

@ -8,6 +8,7 @@ import Data.Abstract.Path
import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString as B import qualified Data.ByteString as B
import Data.Abstract.Module (ModulePath, ModuleInfo(..)) import Data.Abstract.Module (ModulePath, ModuleInfo(..))
import qualified Data.Language as Language
import Diffing.Algorithm import Diffing.Algorithm
import Prelude import Prelude
import Prologue import Prologue
@ -49,7 +50,7 @@ resolveRelativePath relImportPath exts = do
let path = joinPaths relRootDir relImportPath let path = joinPaths relRootDir relImportPath
resolveTSModule path exts >>= either notFound (\x -> traceResolve relImportPath x (pure x)) resolveTSModule path exts >>= either notFound (\x -> traceResolve relImportPath x (pure x))
where where
notFound _ = throwResumable @(ResolutionError value) $ TypeScriptError relImportPath notFound xs = throwResumable @(ResolutionError value) $ NotFoundError relImportPath xs Language.TypeScript
-- | Resolve a non-relative TypeScript import to a known 'ModuleName' or fail. -- | Resolve a non-relative TypeScript import to a known 'ModuleName' or fail.
-- --
@ -74,7 +75,7 @@ resolveNonRelativePath name exts = do
Left xs | parentDir <- takeDirectory path , root /= parentDir -> go root parentDir (searched <> xs) Left xs | parentDir <- takeDirectory path , root /= parentDir -> go root parentDir (searched <> xs)
| otherwise -> notFound (searched <> xs) | otherwise -> notFound (searched <> xs)
Right m -> traceResolve name m $ pure m Right m -> traceResolve name m $ pure m
notFound _ = throwResumable @(ResolutionError value) $ TypeScriptError name notFound xs = throwResumable @(ResolutionError value) $ NotFoundError name xs Language.TypeScript
resolveTSModule :: MonadEvaluatable location term value effects m => FilePath -> [String] -> m effects (Either [FilePath] ModulePath) resolveTSModule :: MonadEvaluatable location term value effects m => FilePath -> [String] -> m effects (Either [FilePath] ModulePath)
resolveTSModule path exts = maybe (Left searchPaths) Right <$> resolve searchPaths resolveTSModule path exts = maybe (Left searchPaths) Right <$> resolve searchPaths

View File

@ -7,8 +7,8 @@ module Semantic.CLI
) where ) where
import Data.File import Data.File
import Data.Language
import Data.List (intercalate) import Data.List (intercalate)
import qualified Data.List.NonEmpty as NonEmpty
import Data.List.Split (splitWhen) import Data.List.Split (splitWhen)
import Data.Version (showVersion) import Data.Version (showVersion)
import Development.GitRev import Development.GitRev
@ -25,7 +25,6 @@ import qualified Semantic.Task as Task
import System.IO (Handle, stdin, stdout) import System.IO (Handle, stdin, stdout)
import Text.Read import Text.Read
main :: IO () main :: IO ()
main = customExecParser (prefs showHelpOnEmpty) arguments >>= uncurry Task.runTaskWithOptions main = customExecParser (prefs showHelpOnEmpty) arguments >>= uncurry Task.runTaskWithOptions
@ -35,8 +34,8 @@ runDiff (SomeRenderer diffRenderer) = Semantic.diffBlobPairs diffRenderer <=< Ta
runParse :: SomeRenderer TermRenderer -> Either Handle [File] -> Task.TaskEff ByteString runParse :: SomeRenderer TermRenderer -> Either Handle [File] -> Task.TaskEff ByteString
runParse (SomeRenderer parseTreeRenderer) = Semantic.parseBlobs parseTreeRenderer <=< Task.readBlobs runParse (SomeRenderer parseTreeRenderer) = Semantic.parseBlobs parseTreeRenderer <=< Task.readBlobs
runGraph :: SomeRenderer GraphRenderer -> Maybe FilePath -> NonEmpty File -> Task.TaskEff ByteString runGraph :: SomeRenderer GraphRenderer -> Maybe FilePath -> FilePath -> Language -> [FilePath] -> Task.TaskEff ByteString
runGraph (SomeRenderer r) dir = Semantic.graph r <=< Task.readProject dir runGraph (SomeRenderer r) rootDir dir excludeDirs = Semantic.graph r <=< Task.readProject rootDir dir excludeDirs
-- | A parser for the application's command-line arguments. -- | A parser for the application's command-line arguments.
-- --
@ -85,20 +84,21 @@ arguments = info (version <*> helper <*> ((,) <$> optionsParser <*> argumentsPar
filesOrStdin <- Right <$> some (argument filePathReader (metavar "FILES...")) <|> pure (Left stdin) filesOrStdin <- Right <$> some (argument filePathReader (metavar "FILES...")) <|> pure (Left stdin)
pure $ runParse renderer filesOrStdin pure $ runParse renderer filesOrStdin
graphCommand = command "graph" (info graphArgumentsParser (progDesc "Compute import/call graph for an entry point")) graphCommand = command "graph" (info graphArgumentsParser (progDesc "Compute an import graph a directory or entry point"))
graphArgumentsParser = do graphArgumentsParser = do
renderer <- flag (SomeRenderer DOTGraphRenderer) (SomeRenderer DOTGraphRenderer) (long "dot" <> help "Output in DOT graph format (default)") renderer <- flag (SomeRenderer DOTGraphRenderer) (SomeRenderer DOTGraphRenderer) (long "dot" <> help "Output in DOT graph format (default)")
<|> flag' (SomeRenderer JSONGraphRenderer) (long "json" <> help "Output JSON graph") <|> flag' (SomeRenderer JSONGraphRenderer) (long "json" <> help "Output JSON graph")
rootDir <- optional (strOption (long "root" <> help "Root directory of project. Optional, defaults to entry file's directory." <> metavar "DIRECTORY")) rootDir <- optional (strOption (long "root" <> help "Root directory of project. Optional, defaults to entry file/directory." <> metavar "DIRECTORY"))
entryPoints <- NonEmpty.some1 (argument filePathReader (metavar "FILES..." <> help "Entry point(s)")) excludeDirs <- many (strOption (long "exclude-dir" <> help "Exclude a directory (e.g. vendor)"))
pure $ runGraph renderer rootDir entryPoints File{..} <- argument filePathReader (metavar "DIRECTORY:LANGUAGE")
pure $ runGraph renderer rootDir filePath (fromJust fileLanguage) excludeDirs
filePathReader = eitherReader parseFilePath filePathReader = eitherReader parseFilePath
parseFilePath arg = case splitWhen (== ':') arg of parseFilePath arg = case splitWhen (== ':') arg of
[a, b] | Just lang <- readMaybe a -> Right (File b (Just lang)) [a, b] | lang <- readMaybe b -> Right (File a lang)
| Just lang <- readMaybe b -> Right (File a (Just lang)) | lang <- readMaybe a -> Right (File b lang)
[path] -> Right (File path (languageForFilePath path)) [path] -> maybe (Left $ "Cannot identify language for path: " <> path) (Right . File path . Just) (languageForFilePath path)
_ -> Left ("cannot parse `" <> arg <> "`\nexpecting LANGUAGE:FILE or just FILE") args -> Left ("cannot parse `" <> join args <> "`\nexpecting FILE:LANGUAGE or just FILE")
optionsReader options = eitherReader $ \ str -> maybe (Left ("expected one of: " <> intercalate ", " (fmap fst options))) (Right . snd) (find ((== str) . fst) options) optionsReader options = eitherReader $ \ str -> maybe (Left ("expected one of: " <> intercalate ", " (fmap fst options))) (Right . snd) (find ((== str) . fst) options)
options options fields = option (optionsReader options) (fields <> showDefaultWith (findOption options) <> metavar (intercalate "|" (fmap fst options))) options options fields = option (optionsReader options) (fields <> showDefaultWith (findOption options) <> metavar (intercalate "|" (fmap fst options)))

View File

@ -23,7 +23,7 @@ import Data.Output
import Parsing.Parser import Parsing.Parser
import Prologue hiding (MonadError (..)) import Prologue hiding (MonadError (..))
import Rendering.Renderer import Rendering.Renderer
import Semantic.IO (Files, NoLanguageForBlob (..)) import Semantic.IO (Files)
import Semantic.Task import Semantic.Task
graph :: (Members '[Distribute WrappedTask, Files, Task, Exc SomeException, Telemetry] effs) graph :: (Members '[Distribute WrappedTask, Files, Task, Exc SomeException, Telemetry] effs)
@ -31,14 +31,12 @@ graph :: (Members '[Distribute WrappedTask, Files, Task, Exc SomeException, Tele
-> Project -> Project
-> Eff effs ByteString -> Eff effs ByteString
graph renderer project graph renderer project
| Just (SomeAnalysisParser parser prelude) <- someAnalysisParser | SomeAnalysisParser parser prelude <- someAnalysisParser
(Proxy :: Proxy '[ Analysis.Evaluatable, Analysis.Declarations1, FreeVariables1, Functor, Eq1, Ord1, Show1 ]) <$> projectLanguage project = do (Proxy :: Proxy '[ Analysis.Evaluatable, Analysis.Declarations1, FreeVariables1, Functor, Eq1, Ord1, Show1 ]) (projectLanguage project) = do
parsePackage parser prelude project >>= graphImports >>= case renderer of parsePackage parser prelude project >>= graphImports >>= case renderer of
JSONGraphRenderer -> pure . toOutput JSONGraphRenderer -> pure . toOutput
DOTGraphRenderer -> pure . Abstract.renderImportGraph DOTGraphRenderer -> pure . Abstract.renderImportGraph
| otherwise = throwError (SomeException (NoLanguageForBlob (filePath (projectEntryPoint project))))
-- | Parse a list of files into a 'Package'. -- | Parse a list of files into a 'Package'.
parsePackage :: Members '[Distribute WrappedTask, Files, Task] effs parsePackage :: Members '[Distribute WrappedTask, Files, Task] effs
=> Parser term -- ^ A parser. => Parser term -- ^ A parser.
@ -47,14 +45,14 @@ parsePackage :: Members '[Distribute WrappedTask, Files, Task] effs
-> Eff effs (Package term) -> Eff effs (Package term)
parsePackage parser preludeFile project@Project{..} = do parsePackage parser preludeFile project@Project{..} = do
prelude <- traverse (parseModule parser Nothing) preludeFile prelude <- traverse (parseModule parser Nothing) preludeFile
Package.fromModules n Nothing prelude <$> parseModules parser project p <- parseModules parser project
trace ("project: " <> show p) $ pure (Package.fromModules n Nothing prelude (length projectEntryPoints) p)
where where
n = name (projectName project) n = name (projectName project)
-- | Parse all files in a project into 'Module's. -- | Parse all files in a project into 'Module's.
parseModules :: Members '[Distribute WrappedTask, Files, Task] effs => Parser term -> Project -> Eff effs [Module term] parseModules :: Members '[Distribute WrappedTask, Files, Task] effs => Parser term -> Project -> Eff effs [Module term]
parseModules parser project@Project{..} = distributeFor allFiles (WrapTask . parseModule parser (Just projectRootDir)) parseModules parser Project{..} = distributeFor (projectEntryPoints <> projectFiles) (WrapTask . parseModule parser (Just projectRootDir))
where allFiles = projectAllFiles project
-- | Parse a file into a 'Module'. -- | Parse a file into a 'Module'.
parseModule :: Members '[Files, Task] effs => Parser term -> Maybe FilePath -> File -> Eff effs (Module term) parseModule :: Members '[Files, Task] effs => Parser term -> Maybe FilePath -> File -> Eff effs (Module term)

View File

@ -6,7 +6,9 @@ module Semantic.IO
, readBlobPairsFromHandle , readBlobPairsFromHandle
, readBlobsFromHandle , readBlobsFromHandle
, readBlobsFromPaths , readBlobsFromPaths
, readProjectFromPaths
, readBlobsFromDir , readBlobsFromDir
, findFiles
, languageForFilePath , languageForFilePath
, NoLanguageForBlob(..) , NoLanguageForBlob(..)
, readBlob , readBlob
@ -32,10 +34,11 @@ import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import Data.Language import Data.Language
import Data.Source import Data.Source
import qualified Data.List.NonEmpty as NonEmpty
import Prelude hiding (readFile) import Prelude hiding (readFile)
import Prologue hiding (MonadError (..), fail) import Prologue hiding (MonadError (..), fail)
import System.Directory (doesDirectoryExist) import System.Directory (doesDirectoryExist)
import qualified System.Directory.Tree as Tree
import System.Directory.Tree (AnchoredDirTree(..))
import System.Exit import System.Exit
import System.FilePath import System.FilePath
import System.FilePath.Glob import System.FilePath.Glob
@ -87,16 +90,42 @@ readBlobFromPath file = do
readBlobsFromPaths :: MonadIO m => [File] -> m [Blob.Blob] readBlobsFromPaths :: MonadIO m => [File] -> m [Blob.Blob]
readBlobsFromPaths files = catMaybes <$> traverse readFile files readBlobsFromPaths files = catMaybes <$> traverse readFile files
readProjectFromPaths :: MonadIO m => Maybe FilePath -> NonEmpty File -> m Project readProjectFromPaths :: MonadIO m => Maybe FilePath -> FilePath -> Language -> [FilePath] -> m Project
readProjectFromPaths root files = do readProjectFromPaths maybeRoot path lang excludeDirs = do
paths <- liftIO $ filter (/= entryPointPath) <$> fmap fold (globDir (compile . mappend "**/*." <$> exts) rootDir) isDir <- isDirectory path
pure $ Project files rootDir (toFile <$> paths) let (filterFun, entryPoints, rootDir) = if isDir
then (id, [], fromMaybe path maybeRoot)
else (filter (/= path), [toFile path], fromMaybe (takeDirectory path) maybeRoot)
paths <- liftIO $ filterFun <$> findFiles rootDir exts excludeDirs
pure $ Project rootDir (toFile <$> paths) lang entryPoints
where where
toFile path = File path (languageForFilePath path) toFile path = File path (Just lang)
exts = extensionsForLanguage (fileLanguage entryPoint) exts = extensionsForLanguage lang
entryPoint = NonEmpty.head files
entryPointPath = filePath entryPoint -- Recursively find files in a directory.
rootDir = fromMaybe (takeDirectory entryPointPath) root findFiles :: forall m. MonadIO m => FilePath -> [String] -> [FilePath] -> m [FilePath]
findFiles path exts excludeDirs = do
_:/dir <- liftIO $ Tree.build path
pure $ (onlyFiles . Tree.filterDir (withExtensions exts) . Tree.filterDir (notIn excludeDirs)) dir
where
-- Build a list of only FilePath's (remove directories and failures)
onlyFiles (Tree.Dir _ fs) = concatMap onlyFiles fs
onlyFiles (Tree.Failed _ _) = []
onlyFiles (Tree.File _ f) = [f]
-- Predicate for Files with one of the extensions in 'exts'.
withExtensions exts (Tree.File n _)
| takeExtension n `elem` exts = True
| otherwise = False
withExtensions _ _ = True
-- Predicate for contents NOT in a directory
notIn dirs (Tree.Dir n _)
| (x:_) <- n, x == '.' = False -- Don't include directories that start with '.'.
| n `elem` dirs = False
| otherwise = True
notIn _ _ = True
readBlobsFromDir :: MonadIO m => FilePath -> m [Blob.Blob] readBlobsFromDir :: MonadIO m => FilePath -> m [Blob.Blob]
readBlobsFromDir path = do readBlobsFromDir path = do
@ -160,8 +189,8 @@ readBlobs = send . ReadBlobs
readBlobPairs :: Member Files effs => Either Handle [Both File] -> Eff effs [Blob.BlobPair] readBlobPairs :: Member Files effs => Either Handle [Both File] -> Eff effs [Blob.BlobPair]
readBlobPairs = send . ReadBlobPairs readBlobPairs = send . ReadBlobPairs
readProject :: Member Files effs => Maybe FilePath -> NonEmpty File -> Eff effs Project readProject :: Member Files effs => Maybe FilePath -> FilePath -> Language -> [FilePath] -> Eff effs Project
readProject dir files = send (ReadProject dir files) readProject rootDir dir excludeDirs = send . ReadProject rootDir dir excludeDirs
-- | A task which writes a 'B.ByteString' to a 'Handle' or a 'FilePath'. -- | A task which writes a 'B.ByteString' to a 'Handle' or a 'FilePath'.
writeToOutput :: Member Files effs => Either Handle FilePath -> B.ByteString -> Eff effs () writeToOutput :: Member Files effs => Either Handle FilePath -> B.ByteString -> Eff effs ()
@ -173,7 +202,7 @@ data Files out where
ReadBlob :: File -> Files Blob.Blob ReadBlob :: File -> Files Blob.Blob
ReadBlobs :: Either Handle [File] -> Files [Blob.Blob] ReadBlobs :: Either Handle [File] -> Files [Blob.Blob]
ReadBlobPairs :: Either Handle [Both File] -> Files [Blob.BlobPair] ReadBlobPairs :: Either Handle [Both File] -> Files [Blob.BlobPair]
ReadProject :: Maybe FilePath -> NonEmpty File -> Files Project ReadProject :: Maybe FilePath -> FilePath -> Language -> [FilePath] -> Files Project
WriteToOutput :: Either Handle FilePath -> B.ByteString -> Files () WriteToOutput :: Either Handle FilePath -> B.ByteString -> Files ()
-- | Run a 'Files' effect in 'IO'. -- | Run a 'Files' effect in 'IO'.
@ -181,10 +210,10 @@ runFiles :: Members '[Exc SomeException, IO] effs => Eff (Files ': effs) a -> Ef
runFiles = interpret $ \ files -> case files of runFiles = interpret $ \ files -> case files of
ReadBlob path -> rethrowing (readBlobFromPath path) ReadBlob path -> rethrowing (readBlobFromPath path)
ReadBlobs (Left handle) -> rethrowing (readBlobsFromHandle handle) ReadBlobs (Left handle) -> rethrowing (readBlobsFromHandle handle)
ReadBlobs (Right paths@[File path Nothing]) -> rethrowing (isDirectory path >>= bool (readBlobsFromPaths paths) (readBlobsFromDir path)) ReadBlobs (Right paths@[File path _]) -> rethrowing (isDirectory path >>= bool (readBlobsFromPaths paths) (readBlobsFromDir path))
ReadBlobs (Right paths) -> rethrowing (readBlobsFromPaths paths) ReadBlobs (Right paths) -> rethrowing (readBlobsFromPaths paths)
ReadBlobPairs source -> rethrowing (either readBlobPairsFromHandle (traverse (runBothWith readFilePair)) source) ReadBlobPairs source -> rethrowing (either readBlobPairsFromHandle (traverse (runBothWith readFilePair)) source)
ReadProject dir files -> rethrowing (readProjectFromPaths dir files) ReadProject rootDir dir language excludeDirs -> rethrowing (readProjectFromPaths rootDir dir language excludeDirs)
WriteToOutput destination contents -> liftIO (either B.hPutStr B.writeFile destination contents) WriteToOutput destination contents -> liftIO (either B.hPutStr B.writeFile destination contents)

View File

@ -8,14 +8,17 @@ import Analysis.Abstract.BadModuleResolutions
import Analysis.Abstract.BadSyntax import Analysis.Abstract.BadSyntax
import Analysis.Abstract.BadValues import Analysis.Abstract.BadValues
import Analysis.Abstract.BadVariables import Analysis.Abstract.BadVariables
import Analysis.Abstract.Caching
import Analysis.Abstract.Erroring import Analysis.Abstract.Erroring
import Analysis.Abstract.Evaluating import Analysis.Abstract.Evaluating as X
import Analysis.Abstract.ImportGraph import Analysis.Abstract.ImportGraph
import Analysis.Abstract.TypeChecking
import Analysis.Declaration import Analysis.Declaration
import Control.Abstract.Analysis import Control.Abstract.Analysis
import Data.Abstract.Address import Data.Abstract.Address
import Data.Abstract.Evaluatable import Data.Abstract.Evaluatable
import Data.Abstract.Located import Data.Abstract.Located
import Data.Abstract.Type
import Data.Abstract.Value import Data.Abstract.Value
import Data.Blob import Data.Blob
import Data.Diff import Data.Diff
@ -52,25 +55,35 @@ type JustEvaluating term
( Evaluating (Located Precise term) term (Value (Located Precise term))))))) ( Evaluating (Located Precise term) term (Value (Located Precise term)))))))
type EvaluatingWithHoles term = BadAddresses (BadModuleResolutions (BadVariables (BadValues (BadSyntax (Evaluating (Located Precise term) term (Value (Located Precise term))))))) type EvaluatingWithHoles term = BadAddresses (BadModuleResolutions (BadVariables (BadValues (BadSyntax (Evaluating (Located Precise term) term (Value (Located Precise term)))))))
type ImportGraphingWithHoles term = ImportGraphing (EvaluatingWithHoles term) type ImportGraphingWithHoles term = ImportGraphing (EvaluatingWithHoles term)
-- The order is significant here: Caching has to come on the outside, or the RunEffect instance for NonDet
-- will expect the TypeError exception type to have an Ord instance, which is wrong.
type Checking term
= Caching
( TypeChecking
( Erroring (AddressError Monovariant Type)
( Erroring (EvalError Type)
( Erroring (ResolutionError Type)
( Erroring (Unspecialized Type)
( Evaluating Monovariant term Type))))))
evalGoProject path = runAnalysis @(JustEvaluating Go.Term) <$> evaluateProject goParser Nothing path evalGoProject path = runAnalysis @(JustEvaluating Go.Term) <$> evaluateProject goParser Language.Go Nothing path
evalRubyProject path = runAnalysis @(JustEvaluating Ruby.Term) <$> evaluateProject rubyParser rubyPrelude path evalRubyProject path = runAnalysis @(JustEvaluating Ruby.Term) <$> evaluateProject rubyParser Language.Ruby rubyPrelude path
evalPHPProject path = runAnalysis @(JustEvaluating PHP.Term) <$> evaluateProject phpParser Nothing path evalPHPProject path = runAnalysis @(JustEvaluating PHP.Term) <$> evaluateProject phpParser Language.PHP Nothing path
evalPythonProject path = runAnalysis @(JustEvaluating Python.Term) <$> evaluateProject pythonParser pythonPrelude path evalPythonProject path = runAnalysis @(JustEvaluating Python.Term) <$> evaluateProject pythonParser Language.Python pythonPrelude path
evalTypeScriptProject path = runAnalysis @(EvaluatingWithHoles TypeScript.Term) <$> evaluateProject typescriptParser Nothing path evalTypeScriptProjectQuietly path = runAnalysis @(EvaluatingWithHoles TypeScript.Term) <$> evaluateProject typescriptParser Language.TypeScript Nothing path
evalTypeScriptProject path = runAnalysis @(JustEvaluating TypeScript.Term) <$> evaluateProject typescriptParser Language.TypeScript Nothing path
typecheckGoFile path = runAnalysis @(Checking Go.Term) <$> evaluateProject goParser Language.Go Nothing path
rubyPrelude = Just $ File (TypeLevel.symbolVal (Proxy :: Proxy (PreludePath Ruby.Term))) (Just Language.Ruby) rubyPrelude = Just $ File (TypeLevel.symbolVal (Proxy :: Proxy (PreludePath Ruby.Term))) (Just Language.Ruby)
pythonPrelude = Just $ File (TypeLevel.symbolVal (Proxy :: Proxy (PreludePath Python.Term))) (Just Language.Python) pythonPrelude = Just $ File (TypeLevel.symbolVal (Proxy :: Proxy (PreludePath Python.Term))) (Just Language.Python)
-- Evaluate a project, starting at a single entrypoint. -- Evaluate a project, starting at a single entrypoint.
evaluateProject parser prelude path = evaluatePackage <$> runTask (readProject Nothing (file path :| []) >>= parsePackage parser prelude) evaluateProject parser lang prelude path = evaluatePackage <$> runTask (readProject Nothing path lang [] >>= parsePackage parser prelude)
-- Read and parse a file.
parseFile :: Parser term -> FilePath -> IO term parseFile :: Parser term -> FilePath -> IO term
parseFile parser = runTask . (parse parser <=< readBlob . file) parseFile parser = runTask . (parse parser <=< readBlob . file)
-- Read a file from the filesystem into a Blob.
blob :: FilePath -> IO Blob blob :: FilePath -> IO Blob
blob = runTask . readBlob . file blob = runTask . readBlob . file

View File

@ -3,6 +3,7 @@ module Analysis.Go.Spec (spec) where
import Data.Abstract.Evaluatable (EvalError(..), runAnalysis) import Data.Abstract.Evaluatable (EvalError(..), runAnalysis)
import qualified Language.Go.Assignment as Go import qualified Language.Go.Assignment as Go
import qualified Data.Language as Language
import SpecHelpers import SpecHelpers
@ -31,4 +32,4 @@ spec = parallel $ do
where where
fixtures = "test/fixtures/go/analysis/" fixtures = "test/fixtures/go/analysis/"
evaluate entry = evalGoProject (fixtures <> entry) evaluate entry = evalGoProject (fixtures <> entry)
evalGoProject path = runAnalysis @(TestEvaluating Go.Term) <$> evaluateProject goParser Nothing path evalGoProject path = runAnalysis @(TestEvaluating Go.Term) <$> evaluateProject goParser Language.Go Nothing path

View File

@ -3,6 +3,7 @@ module Analysis.PHP.Spec (spec) where
import Data.Abstract.Evaluatable (EvalError(..), runAnalysis) import Data.Abstract.Evaluatable (EvalError(..), runAnalysis)
import qualified Language.PHP.Assignment as PHP import qualified Language.PHP.Assignment as PHP
import qualified Data.Language as Language
import SpecHelpers import SpecHelpers
@ -35,4 +36,4 @@ spec = parallel $ do
where where
fixtures = "test/fixtures/php/analysis/" fixtures = "test/fixtures/php/analysis/"
evaluate entry = evalPHPProject (fixtures <> entry) evaluate entry = evalPHPProject (fixtures <> entry)
evalPHPProject path = runAnalysis @(TestEvaluating PHP.Term) <$> evaluateProject phpParser Nothing path evalPHPProject path = runAnalysis @(TestEvaluating PHP.Term) <$> evaluateProject phpParser Language.PHP Nothing path

View File

@ -5,6 +5,7 @@ import Data.Abstract.Evaluatable (EvalError(..), runAnalysis)
import Data.Abstract.Value import Data.Abstract.Value
import Data.Map import Data.Map
import qualified Language.Python.Assignment as Python import qualified Language.Python.Assignment as Python
import qualified Data.Language as Language
import SpecHelpers import SpecHelpers
@ -50,4 +51,4 @@ spec = parallel $ do
addr = Address . Precise addr = Address . Precise
fixtures = "test/fixtures/python/analysis/" fixtures = "test/fixtures/python/analysis/"
evaluate entry = evalPythonProject (fixtures <> entry) evaluate entry = evalPythonProject (fixtures <> entry)
evalPythonProject path = runAnalysis @(TestEvaluating Python.Term) <$> evaluateProject pythonParser pythonPrelude path evalPythonProject path = runAnalysis @(TestEvaluating Python.Term) <$> evaluateProject pythonParser Language.Python pythonPrelude path

View File

@ -10,6 +10,7 @@ import Data.List.NonEmpty (NonEmpty(..))
import Data.Map import Data.Map
import Data.Map.Monoidal as Map import Data.Map.Monoidal as Map
import qualified Language.Ruby.Assignment as Ruby import qualified Language.Ruby.Assignment as Ruby
import qualified Data.Language as Language
import SpecHelpers import SpecHelpers
@ -71,4 +72,4 @@ spec = parallel $ do
addr = Address . Precise addr = Address . Precise
fixtures = "test/fixtures/ruby/analysis/" fixtures = "test/fixtures/ruby/analysis/"
evaluate entry = evalRubyProject (fixtures <> entry) evaluate entry = evalRubyProject (fixtures <> entry)
evalRubyProject path = runAnalysis @(TestEvaluating Ruby.Term) <$> evaluateProject rubyParser rubyPrelude path evalRubyProject path = runAnalysis @(TestEvaluating Ruby.Term) <$> evaluateProject rubyParser Language.Ruby rubyPrelude path

View File

@ -5,6 +5,7 @@ import Data.Abstract.Evaluatable
import qualified Language.TypeScript.Assignment as TypeScript import qualified Language.TypeScript.Assignment as TypeScript
import Data.Abstract.Value as Value import Data.Abstract.Value as Value
import Data.Abstract.Number as Number import Data.Abstract.Number as Number
import qualified Data.Language as Language
import SpecHelpers import SpecHelpers
@ -42,4 +43,4 @@ spec = parallel $ do
where where
fixtures = "test/fixtures/typescript/analysis/" fixtures = "test/fixtures/typescript/analysis/"
evaluate entry = evalTypeScriptProject (fixtures <> entry) evaluate entry = evalTypeScriptProject (fixtures <> entry)
evalTypeScriptProject path = runAnalysis @(TestEvaluating TypeScript.Term) <$> evaluateProject typescriptParser Nothing path evalTypeScriptProject path = runAnalysis @(TestEvaluating TypeScript.Term) <$> evaluateProject typescriptParser Language.TypeScript Nothing path

View File

@ -5,10 +5,10 @@
{+(Identifier)+})+} {+(Identifier)+})+}
{+(Import {+(Import
{+(TextElement)+})+} {+(TextElement)+})+}
{ (QualifiedImport {+(QualifiedImport
{-(Identifier)-}) {+(Identifier)+})+}
->(QualifiedImport {-(QualifiedImport
{+(Identifier)+}) } {-(Identifier)-})-}
{-(Import {-(Import
{-(TextElement)-})-} {-(TextElement)-})-}
{-(QualifiedImport {-(QualifiedImport