-
Notifications
You must be signed in to change notification settings - Fork 452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Modular abstract interpretation #668
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I intend to fix the export problem in a follow-up PR; this is, therefore, ready for review.
-- 9.2+ | ||
:seti -Wno-missing-kind-signatures | ||
:seti -Wno-missing-signatures |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now support 9.2, but only for semantic-analysis
(for now).
(identifier) @this | ||
{ | ||
node @id.node | ||
node @this.node | ||
attr (@this.node) type = "identifier" | ||
var @this.text = (source-text @this) | ||
attr (@this.node) text = (source-text @this) | ||
} | ||
|
||
(import_statement) @this | ||
{ | ||
node @this.node | ||
attr (@this.node) type = "import" | ||
} | ||
|
||
(import_statement name: (dotted_name (identifier) @id)) @this | ||
{ | ||
edge @this.node -> @id.node | ||
attr (@id.node) role = "module-name-fragment" | ||
attr (@this.node -> @id.node) index = (named-child-index @id) | ||
attr (@this.node -> @id.node) text = @id.text |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This lets us import imports 😊
=> (term -> m ExcSet) | ||
-> (term -> m ExcSet) ) | ||
-> File term | ||
-> m (File (Module ExcSet)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this is the first of our analyses to return a Module
(and thus to support modular analysis). Unfortunately, because we don't yet track exports, we can't do linking properly.
runStatement :: ([Message] -> a -> m r) -> StatementC m a -> m r | ||
runStatement k (StatementC m) = runState (k . reverse) [] m |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Statement
effects just track sequential, imperative directives, currently limited to imports. This collects them into Message
s which are then returned via this handler.
runFiles | ||
:: (forall sig m . Has (State (MStore value)) sig m => File term -> m (File result)) | ||
-> [File term] | ||
-> (MStore value, [File result]) | ||
runFiles runFile | ||
= run | ||
. runStoreState | ||
. traverse runFile |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This convenience wraps up a pattern common among the (0-CFA) analyses into a convenient bundle. It's also a great example of exposing effects while hiding carriers, the essence of abstraction.
module Analysis.Module | ||
( Module(..) | ||
, ModuleSet(..) | ||
, link | ||
) where | ||
|
||
import Analysis.Name | ||
import Data.Foldable (foldl') | ||
import qualified Data.Map as Map | ||
import qualified Data.Set as Set | ||
|
||
data Module a = Module | ||
{ body :: Map.Map Name a -> a | ||
, imports :: Set.Set Name | ||
, exports :: Map.Map Name a | ||
, unknown :: Set.Set Name | ||
} | ||
|
||
newtype ModuleSet a = ModuleSet { getModuleSet :: Map.Map Name (Module a) } | ||
|
||
instance Semigroup (ModuleSet a) where | ||
m1 <> m2 = ModuleSet ((link m2 <$> getModuleSet m1) <> (link m1 <$> getModuleSet m2)) | ||
|
||
link :: ModuleSet a -> Module a -> Module a | ||
link (ModuleSet ms) m = Module b' (imports m Set.\\ Map.keysSet ms) (exports m) u' where | ||
(u', b') = foldl' (\ (u, b) -> resolve u b . exports) (unknown m, body m) (Map.restrictKeys ms (imports m)) | ||
resolve u b e = (u Set.\\ Map.keysSet e, b . mappend (Map.restrictKeys e u)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Brought in verbatim from membrane
. I intend to tidy this up in a future PR, as it's somewhat unreadable as-is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it’s a bit dense. Would you mind taking a run at it now? Even pulling out and naming the fold function would help a bit.
data Term | ||
= Var Name | ||
| Noop | ||
| Iff Term Term Term | ||
| Bool Bool | ||
| String Text | ||
| Throw Term | ||
| Let Name Term Term | ||
| Import (NonEmpty Text) | ||
deriving (Eq, Ord, Show) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tagless final class, and the Print
and Interpret
instantiations of it are gone, because defining an Ord
instance for Interpret
would have been agonizing. Instead, we parse into a first-order, initial encoding: plain old data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plain old data is (usually) my favorite kind of data!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just, it was so nice! But too clever by half.
@@ -134,48 +106,66 @@ let' n v m = do | |||
|
|||
-- Parsing | |||
|
|||
parseFile :: Syntax rep => FilePath -> IO (Either (A.JSONPath, String) (Maybe rep)) | |||
parseFile :: (Has (Throw String) sig m, MonadIO m) => FilePath -> m (File Term) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also parse into a File
, which is convenient since we use those for analysis (so that we can service file/line/etc directives).
pure (index, node, node <$ guard (ty == "module"))) | ||
|
||
parseType :: A.Object -> [A.Value] -> String -> A.Parser (IntMap.IntMap Term -> Term) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pulled this and the rest of parseNode
's where clause to the top level, mostly to make it tidier and give them type signatures. The only real change otherwise is using Term
constructors in parseType
here instead of Syntax
methods.
# 0.1.0.2 | ||
|
||
- Support ghc 9.2. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I bumped semantic-source
, which turned out to be rather confusing, but eh, it's out there now.
|
||
-- Messages | ||
|
||
data Message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
newtype here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expectation is that there'll be other message types, but in the meantime, yeah, good call.
import Data.List.NonEmpty (NonEmpty) | ||
import Data.Text | ||
|
||
-- Statement effect |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you expand a little on this? Where is this intended to be used, etc?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏻
module Analysis.Module | ||
( Module(..) | ||
, ModuleSet(..) | ||
, link | ||
) where | ||
|
||
import Analysis.Name | ||
import Data.Foldable (foldl') | ||
import qualified Data.Map as Map | ||
import qualified Data.Set as Set | ||
|
||
data Module a = Module | ||
{ body :: Map.Map Name a -> a | ||
, imports :: Set.Set Name | ||
, exports :: Map.Map Name a | ||
, unknown :: Set.Set Name | ||
} | ||
|
||
newtype ModuleSet a = ModuleSet { getModuleSet :: Map.Map Name (Module a) } | ||
|
||
instance Semigroup (ModuleSet a) where | ||
m1 <> m2 = ModuleSet ((link m2 <$> getModuleSet m1) <> (link m1 <$> getModuleSet m2)) | ||
|
||
link :: ModuleSet a -> Module a -> Module a | ||
link (ModuleSet ms) m = Module b' (imports m Set.\\ Map.keysSet ms) (exports m) u' where | ||
(u', b') = foldl' (\ (u, b) -> resolve u b . exports) (unknown m, body m) (Map.restrictKeys ms (imports m)) | ||
resolve u b e = (u Set.\\ Map.keysSet e, b . mappend (Map.restrictKeys e u)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it’s a bit dense. Would you mind taking a run at it now? Even pulling out and naming the fold function would help a bit.
data Term | ||
= Var Name | ||
| Noop | ||
| Iff Term Term Term | ||
| Bool Bool | ||
| String Text | ||
| Throw Term | ||
| Let Name Term Term | ||
| Import (NonEmpty Text) | ||
deriving (Eq, Ord, Show) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plain old data is (usually) my favorite kind of data!
let' n v' (eval (b (Interpret (pure (pure v')))))) | ||
evalModule :: (Has (Env addr) sig m, HasLabelled Store (Store addr val) sig m, Has (Dom val) sig m) => (Term -> S.StatementC m val) -> (Term -> m (Module val)) | ||
evalModule f i = S.runStatement mk (eval f i) where | ||
mk msgs b = pure (Module (const b) (Set.fromList (map (\ (S.Import cs) -> name (Text.intercalate (pack ".") (toList cs))) msgs)) mempty mempty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some let-bindings here would go a long way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm going to 🔥 them instead, as there's no way to provide the body, unknowns, and exports independent of the domain type anyway.
-- FIXME: this should get the path to the source file, not the path to the JSON. | ||
-- FIXME: this should use the span of the source file, not an empty span. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to fix these now or later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Later, definitely.
This PR:
Exception
domain to support modular abstract interpretation.