1
1
mirror of https://github.com/github/semantic.git synced 2024-11-25 11:04:00 +03:00

Merge branch 'master' into analysis-composition

This commit is contained in:
Rob Rix 2018-03-07 17:49:30 -05:00 committed by GitHub
commit 48819ad6ef
43 changed files with 416 additions and 158 deletions

230
.stylish-haskell.yaml Normal file
View File

@ -0,0 +1,230 @@
# stylish-haskell configuration file
# ==================================
# The stylish-haskell tool is mainly configured by specifying steps. These steps
# are a list, so they have an order, and one specific step may appear more than
# once (if needed). Each file is processed by these steps in the given order.
steps:
# Convert some ASCII sequences to their Unicode equivalents. This is disabled
# by default.
# - unicode_syntax:
# # In order to make this work, we also need to insert the UnicodeSyntax
# # language pragma. If this flag is set to true, we insert it when it's
# # not already present. You may want to disable it if you configure
# # language extensions using some other method than pragmas. Default:
# # true.
# add_language_pragma: true
# Align the right hand side of some elements. This is quite conservative
# and only applies to statements where each element occupies a single
# line.
- simple_align:
cases: true
top_level_patterns: true
records: true
# Import cleanup
- imports:
# There are different ways we can align names and lists.
#
# - global: Align the import names and import list throughout the entire
# file.
#
# - file: Like global, but don't add padding when there are no qualified
# imports in the file.
#
# - group: Only align the imports per group (a group is formed by adjacent
# import lines).
#
# - none: Do not perform any alignment.
#
# Default: global.
align: group
# The following options affect only import list alignment.
#
# List align has following options:
#
# - after_alias: Import list is aligned with end of import including
# 'as' and 'hiding' keywords.
#
# > import qualified Data.List as List (concat, foldl, foldr, head,
# > init, last, length)
#
# - with_alias: Import list is aligned with start of alias or hiding.
#
# > import qualified Data.List as List (concat, foldl, foldr, head,
# > init, last, length)
#
# - new_line: Import list starts always on new line.
#
# > import qualified Data.List as List
# > (concat, foldl, foldr, head, init, last, length)
#
# Default: after_alias
list_align: after_alias
# Right-pad the module names to align imports in a group:
#
# - true: a little more readable
#
# > import qualified Data.List as List (concat, foldl, foldr,
# > init, last, length)
# > import qualified Data.List.Extra as List (concat, foldl, foldr,
# > init, last, length)
#
# - false: diff-safe
#
# > import qualified Data.List as List (concat, foldl, foldr, init,
# > last, length)
# > import qualified Data.List.Extra as List (concat, foldl, foldr,
# > init, last, length)
#
# Default: true
pad_module_names: false
# Long list align style takes effect when import is too long. This is
# determined by 'columns' setting.
#
# - inline: This option will put as much specs on same line as possible.
#
# - new_line: Import list will start on new line.
#
# - new_line_multiline: Import list will start on new line when it's
# short enough to fit to single line. Otherwise it'll be multiline.
#
# - multiline: One line per import list entry.
# Type with constructor list acts like single import.
#
# > import qualified Data.Map as M
# > ( empty
# > , singleton
# > , ...
# > , delete
# > )
#
# Default: inline
long_list_align: new_line_multiline
# Align empty list (importing instances)
#
# Empty list align has following options
#
# - inherit: inherit list_align setting
#
# - right_after: () is right after the module name:
#
# > import Vector.Instances ()
#
# Default: inherit
empty_list_align: inherit
# List padding determines indentation of import list on lines after import.
# This option affects 'long_list_align'.
#
# - <integer>: constant value
#
# - module_name: align under start of module name.
# Useful for 'file' and 'group' align settings.
list_padding: 4
# Separate lists option affects formatting of import list for type
# or class. The only difference is single space between type and list
# of constructors, selectors and class functions.
#
# - true: There is single space between Foldable type and list of it's
# functions.
#
# > import Data.Foldable (Foldable (fold, foldl, foldMap))
#
# - false: There is no space between Foldable type and list of it's
# functions.
#
# > import Data.Foldable (Foldable(fold, foldl, foldMap))
#
# Default: true
separate_lists: true
# Space surround option affects formatting of import lists on a single
# line. The only difference is single space after the initial
# parenthesis and a single space before the terminal parenthesis.
#
# - true: There is single space associated with the enclosing
# parenthesis.
#
# > import Data.Foo ( foo )
#
# - false: There is no space associated with the enclosing parenthesis
#
# > import Data.Foo (foo)
#
# Default: false
space_surround: false
# Language pragmas
- language_pragmas:
# We can generate different styles of language pragma lists.
#
# - vertical: Vertical-spaced language pragmas, one per line.
#
# - compact: A more compact style.
#
# - compact_line: Similar to compact, but wrap each line with
# `{-#LANGUAGE #-}'.
#
# Default: vertical.
style: compact
# Align affects alignment of closing pragma brackets.
#
# - true: Brackets are aligned in same column.
#
# - false: Brackets are not aligned together. There is only one space
# between actual import and closing bracket.
#
# Default: true
align: true
# stylish-haskell can detect redundancy of some language pragmas. If this
# is set to true, it will remove those redundant pragmas. Default: true.
remove_redundant: true
# Replace tabs by spaces. This is disabled by default.
# - tabs:
# # Number of spaces to use for each tab. Default: 8, as specified by the
# # Haskell report.
# spaces: 8
# Remove trailing whitespace
- trailing_whitespace: {}
# A common setting is the number of columns (parts of) code will be wrapped
# to. Different steps take this into account. Default: 80.
columns: 120
# By default, line endings are converted according to the OS. You can override
# preferred format here.
#
# - native: Native newline format. CRLF on Windows, LF on other OSes.
#
# - lf: Convert to LF ("\n").
#
# - crlf: Convert to CRLF ("\r\n").
#
# Default: native.
newline: native
# Sometimes, language extensions are specified in a cabal file or from the
# command line instead of using language pragmas in the file. stylish-haskell
# needs to be aware of these, so it can parse the file correctly.
language_extensions:
- DeriveFoldable
- DeriveFunctor
- DeriveGeneric
- DeriveTraversable
- FlexibleContexts
- FlexibleInstances
- MultiParamTypeClasses
- OverloadedStrings
- RecordWildCards
- StrictData

View File

@ -48,9 +48,9 @@ library
, Data.Abstract.Configuration
, Data.Abstract.Environment
, Data.Abstract.Evaluatable
, Data.Abstract.Linker
, Data.Abstract.FreeVariables
, Data.Abstract.Live
, Data.Abstract.ModuleTable
, Data.Abstract.Store
, Data.Abstract.Type
, Data.Abstract.Value

View File

@ -17,7 +17,7 @@ import Data.Abstract.Cache
import Data.Abstract.Configuration
import Data.Abstract.Environment
import Data.Abstract.Evaluatable
import Data.Abstract.Linker
import Data.Abstract.ModuleTable
import Data.Abstract.Live
import Data.Abstract.Store
import Data.Abstract.Value
@ -34,8 +34,8 @@ type CachingEffects t v
, State (Store (LocationFor v) v) -- For 'MonadStore'.
, Reader (Cache (LocationFor v) t v) -- For 'MonadCacheIn'.
, State (Cache (LocationFor v) t v) -- For 'MonadCacheOut'.
, Reader (Linker t) -- Cache of unevaluated modules
, State (Linker v) -- Cache of evaluated modules
, Reader (ModuleTable t) -- Cache of unevaluated modules
, State (ModuleTable v) -- Cache of evaluated modules
]
newtype CachingAnalysis term value a = CachingAnalysis { runCachingAnalysis :: Evaluator (CachingEffects term value) term value a }
@ -170,21 +170,3 @@ memoizeEval e = do
store' <- getStore
modifyCache (cacheInsert c (v, store'))
pure v
{-
-- | Evaluation of a single iteration of an analysis, given a 'MonadCacheIn' instance as an oracle for results and a 'MonadCacheOut' instance to record computed results in.
evCache :: forall t v m
. ( Ord (LocationFor v)
, Ord t
, Ord v
, Ord (Cell (LocationFor v) v)
, MonadCaching t v m
)
=> (((v -> m v) -> t -> m v) -> (v -> m v) -> t -> m v)
-> ((v -> m v) -> t -> m v)
-> (v -> m v) -> t -> m v
-}

View File

@ -9,7 +9,7 @@ import Control.Monad.Effect.Reader
import Control.Monad.Effect.State
import Data.Abstract.Address
import Data.Abstract.Evaluatable
import Data.Abstract.Linker
import Data.Abstract.ModuleTable
import Data.Abstract.Store
import Data.Abstract.Value
import Data.Set (delete)
@ -22,8 +22,8 @@ type DeadCodeEffects t v
, State (Store (LocationFor v) v) -- The heap
, State (EnvironmentFor v) -- Global (imperative) environment
, Reader (EnvironmentFor v) -- Local environment (e.g. binding over a closure)
, Reader (Linker t) -- Cache of unevaluated modules
, State (Linker v) -- Cache of evaluated modules
, Reader (ModuleTable t) -- Cache of unevaluated modules
, State (ModuleTable v) -- Cache of evaluated modules
]

View File

@ -9,7 +9,7 @@ import Control.Monad.Effect.Reader
import Control.Monad.Effect.State
import Data.Abstract.Address
import Data.Abstract.Evaluatable
import Data.Abstract.Linker
import Data.Abstract.ModuleTable
import Data.Abstract.Store
import Data.Abstract.Value
import Data.Blob
@ -23,8 +23,8 @@ type EvaluationEffects t v
, State (Store (LocationFor v) v) -- The heap
, State (EnvironmentFor v) -- Global (imperative) environment
, Reader (EnvironmentFor v) -- Local environment (e.g. binding over a closure)
, Reader (Linker t) -- Cache of unevaluated modules
, State (Linker v) -- Cache of evaluated modules
, Reader (ModuleTable t) -- Cache of unevaluated modules
, State (ModuleTable v) -- Cache of evaluated modules
]
@ -60,7 +60,7 @@ evaluates pairs (_, t) = run @(EvaluationEffects term v) (runEvaluator (runEvalu
-- | Run an action with the passed ('Blob', @term@) pairs available for imports.
withModules :: (MonadAnalysis term value m, MonadEvaluator term value m) => [(Blob, term)] -> m a -> m a
withModules pairs = localModuleTable (const moduleTable)
where moduleTable = Linker (Map.fromList (map (first (dropExtensions . blobPath)) pairs))
where moduleTable = ModuleTable (Map.fromList (map (first (dropExtensions . blobPath)) pairs))
-- | An analysis performing concrete evaluation of @term@s to @value@s.
newtype Evaluation term value a = Evaluation { runEvaluation :: Evaluator (EvaluationEffects term value) term value a }

View File

@ -8,7 +8,7 @@ import Control.Monad.Effect.Fresh
import Control.Monad.Effect.NonDet
import Control.Monad.Effect.Reader
import Control.Monad.Effect.State
import Data.Abstract.Linker
import Data.Abstract.ModuleTable
import Data.Abstract.Value
import Prelude hiding (fail)
@ -35,21 +35,21 @@ class MonadFail m => MonadEvaluator term value m | m -> term, m -> value where
modifyStore :: (StoreFor value -> StoreFor value) -> m ()
-- | Retrieve the table of evaluated modules.
getModuleTable :: m (Linker value)
getModuleTable :: m (ModuleTable value)
-- | Update the table of evaluated modules.
modifyModuleTable :: (Linker value -> Linker value) -> m ()
modifyModuleTable :: (ModuleTable value -> ModuleTable value) -> m ()
-- | Retrieve the table of unevaluated modules.
askModuleTable :: m (Linker term)
askModuleTable :: m (ModuleTable term)
-- | Run an action with a locally-modified table of unevaluated modules.
localModuleTable :: (Linker term -> Linker term) -> m a -> m a
localModuleTable :: (ModuleTable term -> ModuleTable term) -> m a -> m a
instance Members '[ Fail
, Reader (EnvironmentFor value)
, State (EnvironmentFor value)
, State (StoreFor value)
, Reader (Linker term)
, State (Linker value)
, Reader (ModuleTable term)
, State (ModuleTable value)
] effects
=> MonadEvaluator term value (Evaluator effects term value) where
getGlobalEnv = Evaluator get

View File

@ -18,7 +18,7 @@ import Control.Monad.Effect.Fail
import Data.Abstract.Address
import Data.Abstract.Environment
import Data.Abstract.FreeVariables as FreeVariables
import Data.Abstract.Linker
import Data.Abstract.ModuleTable
import Data.Abstract.Value
import Data.Algebra
import qualified Data.ByteString.Char8 as BC
@ -84,7 +84,7 @@ require :: ( FreeVariables term
)
=> term
-> m v
require term = getModuleTable >>= maybe (load term) pure . linkerLookup name
require term = getModuleTable >>= maybe (load term) pure . moduleTableLookup name
where name = moduleName term
-- | Load another term/file and return an Effect.
@ -96,12 +96,12 @@ load :: ( FreeVariables term
)
=> term
-> m v
load term = askModuleTable >>= maybe notFound evalAndCache . linkerLookup name
load term = askModuleTable >>= maybe notFound evalAndCache . moduleTableLookup name
where name = moduleName term
notFound = fail ("cannot find " <> show name)
evalAndCache e = do
v <- evaluateTerm e
modifyModuleTable (linkerInsert name v)
modifyModuleTable (moduleTableInsert name v)
pure v
-- | Get a module name from a term (expects single free variables).

View File

@ -1,16 +0,0 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.Abstract.Linker where
import Data.Semigroup
import GHC.Generics
import qualified Data.Map as Map
newtype Linker a = Linker { unLinker :: Map.Map FilePath a }
deriving (Eq, Foldable, Functor, Generic1, Monoid, Ord, Semigroup, Show, Traversable)
linkerLookup :: FilePath -> Linker a -> Maybe a
linkerLookup k = Map.lookup k . unLinker
linkerInsert :: FilePath -> a -> Linker a -> Linker a
linkerInsert k v Linker{..} = Linker (Map.insert k v unLinker)

View File

@ -0,0 +1,19 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.Abstract.ModuleTable
( ModuleTable (..)
, moduleTableLookup
, moduleTableInsert
)where
import Data.Semigroup
import GHC.Generics
import qualified Data.Map as Map
newtype ModuleTable a = ModuleTable { unModuleTable :: Map.Map FilePath a }
deriving (Eq, Foldable, Functor, Generic1, Monoid, Ord, Semigroup, Show, Traversable)
moduleTableLookup :: FilePath -> ModuleTable a -> Maybe a
moduleTableLookup k = Map.lookup k . unModuleTable
moduleTableInsert :: FilePath -> a -> ModuleTable a -> ModuleTable a
moduleTableInsert k v ModuleTable{..} = ModuleTable (Map.insert k v unModuleTable)

View File

@ -382,10 +382,14 @@ importDeclaration :: Assignment
importDeclaration = makeTerm'' <$> symbol ImportDeclaration <*> children (manyTerm (importSpec <|> importSpecList))
where
importSpec = makeTerm <$> symbol ImportSpec <*> children (namedImport <|> plainImport)
namedImport = flip Declaration.Import <$> expression <*> expression <*> pure []
plainImport = Declaration.Import <$> expression <*> emptyTerm <*> pure []
namedImport = flip Declaration.Import <$> expression' <*> expression' <*> pure []
plainImport = Declaration.Import <$> expression' <*> emptyTerm <*> pure []
importSpecList = makeTerm <$> symbol ImportSpecList <*> children (manyTerm (importSpec <|> comment))
expression' = expression <|> dotImport <|> blankImport
dotImport = makeTerm <$> symbol Dot <*> (Syntax.Identifier <$> source)
blankImport = makeTerm <$> symbol BlankIdentifier <*> (Syntax.Identifier <$> source)
indexExpression :: Assignment
indexExpression = makeTerm <$> symbol IndexExpression <*> children (Expression.Subscript <$> expression <*> manyTerm expression)

View File

@ -20,9 +20,9 @@
(
(Identifier)
(Identifier))
(Identifier)
(Identifier)
(
(Identifier)
(Identifier)
(Identifier)
(Identifier)))
([]))
@ -32,8 +32,10 @@
->(Identifier) }
([])
(
(Identifier)
(Identifier))
(
(Identifier))
(
(Identifier)))
([]))
(Function
(Empty)

View File

@ -20,9 +20,9 @@
(
(Identifier)
(Identifier))
(Identifier)
(Identifier)
(
(Identifier)
(Identifier)
(Identifier)
(Identifier)))
([]))
@ -32,8 +32,10 @@
->(Identifier) }
([])
(
(Identifier)
(Identifier))
(
(Identifier))
(
(Identifier)))
([]))
(Function
(Empty)

View File

@ -18,9 +18,9 @@
(
(Identifier)
(Identifier))
(Identifier)
(Identifier)
(
(Identifier)
(Identifier)
(Identifier)
(Identifier)))
([]))
@ -29,8 +29,10 @@
(Identifier)
([])
(
(Identifier)
(Identifier))
(
(Identifier))
(
(Identifier)))
([]))
(Function
(Empty)

View File

@ -18,9 +18,9 @@
(
(Identifier)
(Identifier))
(Identifier)
(Identifier)
(
(Identifier)
(Identifier)
(Identifier)
(Identifier)))
([]))
@ -29,8 +29,10 @@
(Identifier)
([])
(
(Identifier)
(Identifier))
(
(Identifier))
(
(Identifier)))
([]))
(Function
(Empty)

View File

@ -16,10 +16,12 @@
{ (Identifier)
->(Identifier) })
(
{ (Identifier)
->(Identifier) }
{ (Identifier)
->(Identifier) })
(
{ (Identifier)
->(Identifier) })
(
{ (Identifier)
->(Identifier) }))
(Return
(
(Integer)

View File

@ -16,10 +16,12 @@
{ (Identifier)
->(Identifier) })
(
{ (Identifier)
->(Identifier) }
{ (Identifier)
->(Identifier) })
(
{ (Identifier)
->(Identifier) })
(
{ (Identifier)
->(Identifier) }))
(Return
(
(Integer)

View File

@ -14,8 +14,10 @@
(Identifier)
(Identifier))
(
(Identifier)
(Identifier))
(
(Identifier))
(
(Identifier)))
(Return
(
(Integer)

View File

@ -14,8 +14,10 @@
(Identifier)
(Identifier))
(
(Identifier)
(Identifier))
(
(Identifier))
(
(Identifier)))
(Return
(
(Integer)

View File

@ -10,8 +10,9 @@
{ (Identifier)
->(Identifier) }
(Function
{ (Identifier)
->(Identifier) }
(
{ (Identifier)
->(Identifier) })
{ (Identifier)
->(Identifier) }))
(Type
@ -19,13 +20,17 @@
->(Identifier) }
(Function
(
{-(Identifier)-}
(Identifier)
{+(Identifier)+})
(
{-(
{-(Identifier)-})-}
(
(Identifier))
{+(
{+(Identifier)+})+})
(
(
{+(BidirectionalChannel
{+(Identifier)+})+})+}
{-(Identifier)-}
(Identifier))
{+(Identifier)+})+}
{-(Identifier)-})
(
(Identifier)))
(Empty))))))

View File

@ -10,8 +10,9 @@
{ (Identifier)
->(Identifier) }
(Function
{ (Identifier)
->(Identifier) }
(
{ (Identifier)
->(Identifier) })
{ (Identifier)
->(Identifier) }))
(Type
@ -19,13 +20,17 @@
->(Identifier) }
(Function
(
{-(Identifier)-}
(Identifier)
{+(Identifier)+})
(
{+(Identifier)+}
{-(
{-(Identifier)-})-}
(
(Identifier))
{+(
{+(Identifier)+})+})
(
(
{+(Identifier)+}
{-(BidirectionalChannel
{-(Identifier)-})-})-}
(Identifier))
{-(Identifier)-})-})
(
(Identifier)))
(Empty))))))

View File

@ -9,15 +9,20 @@
(Type
(Identifier)
(Function
(Identifier)
(
(Identifier))
(Identifier)))
(Type
(Identifier)
(Function
(
(Identifier)
(Identifier))
(
(Identifier))
(
(Identifier)))
(
(Identifier)
(Identifier))
(
(Identifier))
(
(Identifier)))
(Empty))))))

View File

@ -9,17 +9,21 @@
(Type
(Identifier)
(Function
(Identifier)
(
(Identifier))
(Identifier)))
(Type
(Identifier)
(Function
(
(Identifier)
(Identifier))
(
(Identifier))
(
(Identifier)))
(
(
(BidirectionalChannel
(Identifier)))
(Identifier))
(
(Identifier)))
(Empty))))))

View File

@ -9,7 +9,7 @@
(Import
{ (TextElement)
->(TextElement) }
(Empty))
(Identifier))
(Import
{ (TextElement)
->(TextElement) }

View File

@ -9,7 +9,7 @@
(Import
{ (TextElement)
->(TextElement) }
(Empty))
(Identifier))
(Import
{ (TextElement)
->(TextElement) }

View File

@ -7,7 +7,7 @@
(Empty))
(Import
(TextElement)
(Empty))
(Identifier))
(Import
(TextElement)
(Identifier)))

View File

@ -7,7 +7,7 @@
(Empty))
(Import
(TextElement)
(Empty))
(Identifier))
(Import
(TextElement)
(Identifier)))

View File

@ -127,8 +127,7 @@
(
{ (Identifier)
->(Identifier) }
(
{ (Identifier)
->(Identifier) }
(Identifier)))
{ (Identifier)
->(Identifier) }
(Identifier))
([])))

View File

@ -127,8 +127,7 @@
(
{ (Identifier)
->(Identifier) }
(
{ (Identifier)
->(Identifier) }
(Identifier)))
{ (Identifier)
->(Identifier) }
(Identifier))
([])))

View File

@ -92,7 +92,6 @@
(Identifier)
(
(Identifier)
(
(Identifier)
(Identifier)))
(Identifier)
(Identifier))
([])))

View File

@ -106,7 +106,6 @@
(Identifier)
(
(Identifier)
(
(Identifier)
(Identifier)))
(Identifier)
(Identifier))
([])))

View File

@ -8,7 +8,7 @@
(Import
{ (TextElement)
->(TextElement) }
(Empty))
(Identifier))
(Import
{ (TextElement)
->(TextElement) }

View File

@ -8,7 +8,7 @@
(Import
{ (TextElement)
->(TextElement) }
(Empty))
(Identifier))
(Import
{ (TextElement)
->(TextElement) }

View File

@ -6,7 +6,7 @@
(Empty))
(Import
(TextElement)
(Empty))
(Identifier))
(Import
(TextElement)
(Identifier))

View File

@ -6,7 +6,7 @@
(Empty))
(Import
(TextElement)
(Empty))
(Identifier))
(Import
(TextElement)
(Identifier))

View File

@ -6,11 +6,12 @@
(Identifier)
([])
(
(TypeConversion
(Pointer
(Identifier))
{ (Identifier)
->(Identifier) })
(Pointer
(Call
(Identifier)
{ (Identifier)
->(Identifier) }
(Empty)))
(Call
(Pointer
(Identifier))

View File

@ -6,11 +6,12 @@
(Identifier)
([])
(
(TypeConversion
(Pointer
(Identifier))
{ (Identifier)
->(Identifier) })
(Pointer
(Call
(Identifier)
{ (Identifier)
->(Identifier) }
(Empty)))
(Call
(Pointer
(Identifier))

View File

@ -6,10 +6,11 @@
(Identifier)
([])
(
(TypeConversion
(Pointer
(Identifier))
(Identifier))
(Pointer
(Call
(Identifier)
(Identifier)
(Empty)))
(Call
(Pointer
(Identifier))

View File

@ -6,10 +6,11 @@
(Identifier)
([])
(
(TypeConversion
(Pointer
(Identifier))
(Identifier))
(Pointer
(Call
(Identifier)
(Identifier)
(Empty)))
(Call
(Pointer
(Identifier))

View File

@ -28,7 +28,8 @@
{ (Identifier)
->(Identifier) }
(
(Identifier)
(
(Identifier))
(Variadic
(Identifier)
(Empty)))

View File

@ -28,7 +28,8 @@
{ (Identifier)
->(Identifier) }
(
(Identifier)
(
(Identifier))
(Variadic
(Identifier)
(Empty)))

View File

@ -25,7 +25,8 @@
(Empty)
(Identifier)
(
(Identifier)
(
(Identifier))
(Variadic
(Identifier)
(Empty)))

View File

@ -25,7 +25,8 @@
(Empty)
(Identifier)
(
(Identifier)
(
(Identifier))
(Variadic
(Identifier)
(Empty)))

@ -1 +1 @@
Subproject commit a7c77ef5459e4f610bd82ce203984f408bc106c2
Subproject commit 19fd8594796a26b26c0545897fd46f547f316be8