Skip to content
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

Add a modules() method to the cryptol python api for listing modules and their documentation #1755

Merged
merged 6 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cryptol-remote-api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## next -- TBA

* Add `visible modules` command to return the modules, submodules and associated
documentation for them.

## 3.2.0 -- 2024-08-20

Expand Down
5 changes: 5 additions & 0 deletions cryptol-remote-api/cryptol-eval-server/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import CryptolServer.ExtendSearchPath
import CryptolServer.FocusedModule
( focusedModuleDescr, focusedModule )
import CryptolServer.Names ( visibleNamesDescr, visibleNames )
import CryptolServer.Modules ( visibleModulesDescr, visibleModules )
import CryptolServer.TypeCheck ( checkType )
import CryptolServer.Sat ( proveSatDescr, proveSat )
import Cryptol.REPL.Command (CommandResult, DocstringResult)
Expand Down Expand Up @@ -156,6 +157,10 @@ cryptolEvalMethods =
"visible names"
visibleNamesDescr
visibleNames
, command
"visible modules"
visibleModulesDescr
visibleModules
, command
"check type"
(Doc.Paragraph [Doc.Text "Check and return the type of the given expression."])
Expand Down
1 change: 1 addition & 0 deletions cryptol-remote-api/cryptol-remote-api.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ library
CryptolServer.FocusedModule
CryptolServer.Interrupt
CryptolServer.LoadModule
CryptolServer.Modules
CryptolServer.Names
CryptolServer.Options
CryptolServer.Sat
Expand Down
5 changes: 5 additions & 0 deletions cryptol-remote-api/cryptol-remote-api/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import CryptolServer.Interrupt
import CryptolServer.LoadModule
( loadFile, loadFileDescr, loadModule, loadModuleDescr )
import CryptolServer.Names ( visibleNames, visibleNamesDescr )
import CryptolServer.Modules ( visibleModules, visibleModulesDescr )
import CryptolServer.Sat ( proveSat, proveSatDescr )
import CryptolServer.TypeCheck ( checkType, checkTypeDescr )
import CryptolServer.Version ( version, versionDescr )
Expand Down Expand Up @@ -121,6 +122,10 @@ cryptolMethods =
"visible names"
visibleNamesDescr
visibleNames
, command
"visible modules"
visibleModulesDescr
visibleModules
, command
"check type"
checkTypeDescr
Expand Down
31 changes: 31 additions & 0 deletions cryptol-remote-api/docs/Cryptol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,37 @@ Return fields



visible modules (command)
~~~~~~~~~~~~~~~~~~~~~~~~~

List the currently visible (i.e., in scope) module names.

Parameter fields
++++++++++++++++

No parameters


Return fields
+++++++++++++


``module``
A human-readable representation of the module's name



``documentation``
An optional field containing documentation strings for the module, if it is documented



``parameterized``
A Boolean value indicating whether the focused module is parameterized




check type (command)
~~~~~~~~~~~~~~~~~~~~

Expand Down
5 changes: 5 additions & 0 deletions cryptol-remote-api/python/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Revision history for `cryptol` Python package

## next

* Add `modules` command to return the modules, submodules and associated
documentation for them.
Comment on lines +5 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a similar changelog entry to the cryptol-remote-api changelog?


## 3.2.1 -- 2024-08-18

* Require building with `argo-client-0.0.13` or later. `argo-client-0.0.13` uses
Expand Down
6 changes: 6 additions & 0 deletions cryptol-remote-api/python/cryptol/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,13 @@ def __init__(self, connection : HasProtocolState, expr : Any, solver : Solver, t
class CryptolSafe(CryptolProveSat):
def __init__(self, connection : HasProtocolState, expr : Any, solver : Solver, timeout: Optional[float]) -> None:
super(CryptolSafe, self).__init__(connection, SmtQueryType.SAFE, expr, solver, 1, timeout=timeout)

class CryptolModules(argo.Command):
def __init__(self, connection : HasProtocolState, timeout: Optional[float]) -> None:
super(CryptolModules, self).__init__('visible modules', {}, connection, timeout=timeout)

def process_result(self, res : Any) -> Any:
return res

class CryptolNames(argo.Command):
def __init__(self, connection : HasProtocolState, timeout: Optional[float]) -> None:
Expand Down
16 changes: 12 additions & 4 deletions cryptol-remote-api/python/cryptol/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,11 @@ def __init__(self,
def get_default_timeout(self) -> Optional[float]:
"""Get the value of the optional default timeout for methods (in seconds)."""
return self.timeout

def set_default_timeout(self, timeout : Optional[float]) -> None:
"""Set the value of the optional default timeout for methods (in seconds)."""
self.timeout = timeout

# Currently disabled in (overly?) conservative effort to not accidentally
# duplicate and share mutable state.

Expand Down Expand Up @@ -203,10 +203,10 @@ def load_file(self, filename : str, *, timeout:Optional[float] = None) -> argo.C
timeout = timeout if timeout is not None else self.timeout
self.most_recent_result = CryptolLoadFile(self, filename, timeout)
return self.most_recent_result

def check_docstrings(self, timeout: Optional[float] = None) -> argo.Command:
"""Check docstrings

:param timeout: Optional timeout for this request (in seconds).
"""
timeout = timeout if timeout is not None else self.timeout
Expand Down Expand Up @@ -396,6 +396,14 @@ def safe(self, expr : Any, solver : solver.Solver = solver.Z3, *, timeout:Option
self.most_recent_result = CryptolSafe(self, expr, solver, timeout)
return self.most_recent_result

def modules(self, *, timeout:Optional[float] = None) -> argo.Command:
"""Discover the list of modules and submodules currently in scope in the current context.

:param timeout: Optional timeout for this request (in seconds)."""
timeout = timeout if timeout is not None else self.timeout
self.most_recent_result = CryptolModules(self, timeout)
return self.most_recent_result
glguy marked this conversation as resolved.
Show resolved Hide resolved

def names(self, *, timeout:Optional[float] = None) -> argo.Command:
"""Discover the list of term names currently in scope in the current context.

Expand Down
16 changes: 16 additions & 0 deletions cryptol-remote-api/python/cryptol/cryptoltypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,23 @@ def argument_types(obj : Union[CryptolTypeSchema, CryptolType]) -> List[CryptolT
return [arg1] + args
else:
return []

# -----------------------------------------------------------
# Cryptol Module Info
# -----------------------------------------------------------

CryptolVisibleModuleInfo = TypedDict("CryptolVisibleModuleInfo",
{ "module": str
, "parameterized": bool
, "documentation": Optional[List[str]]
})

def to_cryptol_visible_module_info(d : Any) -> CryptolVisibleModuleInfo:
if check_dict(d, required_keys={"module": str, "parameterized": bool}, optional_keys={"documentation": List}):
# the call to check_dict ensures this cast is OK
return cast(CryptolVisibleModuleInfo, d)
else:
raise ValueError("Cryptol visible module info is malformed: " + str(d))

# -----------------------------------------------------------
# Cryptol Name Info
Expand Down
4 changes: 4 additions & 0 deletions cryptol-remote-api/python/cryptol/single_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ def safe(expr : Any, solver : Solver = Z3, *, timeout:Optional[float] = None) ->
"""
return __get_designated_connection().safe(expr, solver, timeout=timeout)

def modules(*, timeout:Optional[float] = None) -> List[cryptoltypes.CryptolVisibleModuleInfo]:
"""Discover the list of modules currently in scope in the current context."""
return __get_designated_connection().modules(timeout=timeout)

def names(*, timeout:Optional[float] = None) -> List[cryptoltypes.CryptolNameInfo]:
"""Discover the list of term names currently in scope in the current context."""
return __get_designated_connection().names(timeout=timeout)
Expand Down
8 changes: 8 additions & 0 deletions cryptol-remote-api/python/cryptol/synchronous.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,14 @@ def safe(self, expr : Any, solver : Solver = Z3, *, timeout:Optional[float] = No
else:
raise ValueError("Unknown solver type: " + str(solver))

def modules(self, *, timeout:Optional[float] = None) -> List[cryptoltypes.CryptolVisibleModuleInfo]:
"""Discover the list of term names currently in scope in the current context."""
res = self.connection.modules(timeout=timeout).result()
if isinstance(res, list):
return [ cryptoltypes.to_cryptol_visible_module_info(entry) for entry in res ]
else:
raise ValueError("Result of `modules()` is not a list: " + str(res))

def names(self, *, timeout:Optional[float] = None) -> List[cryptoltypes.CryptolNameInfo]:
"""Discover the list of term names currently in scope in the current context."""
res = self.connection.names(timeout=timeout).result()
Expand Down
18 changes: 18 additions & 0 deletions cryptol-remote-api/python/tests/cryptol/test-files/Modules.cry
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* A top-level
* docstring
*/
module Modules where

/** A functor docstring */
submodule F where
parameter
type N : #

/** A submodule in a functor docstring */
submodule Q where
q = ()

/** A submodule docstring */
submodule M = submodule F where
type N = 0
26 changes: 26 additions & 0 deletions cryptol-remote-api/python/tests/cryptol/test_modules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import unittest
from pathlib import Path
import cryptol
from cryptol.single_connection import *

class TestModules(unittest.TestCase):
def test_modules(self):
connect(verify=False)
load_file(str(Path('tests','cryptol','test-files', 'Modules.cry')))

expected = [
{'module': 'Cryptol', 'parameterized': False},
{'module': 'Modules', 'parameterized': False, 'documentation': ['A top-level\ndocstring\n']},
{'module': 'Modules::`where` argument of M', 'parameterized': False},
{'module': 'Modules::M', 'parameterized': False, 'documentation': ['A submodule docstring\n', 'A functor docstring\n']},
{'module': 'Modules::M::Q', 'parameterized': False, 'documentation': ['A submodule in a functor docstring\n']},
{'module': 'Modules::F', 'parameterized': True, 'documentation': ['A functor docstring\n']},
{'module': 'Modules::F::Q', 'parameterized': True, 'documentation': ['A submodule in a functor docstring\n']},
]

names_to_check = modules()

self.assertCountEqual(expected, names_to_check)

if __name__ == "__main__":
unittest.main()
83 changes: 83 additions & 0 deletions cryptol-remote-api/src/CryptolServer/Modules.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
module CryptolServer.Modules
( visibleModules
, visibleModulesDescr
) where

import qualified Argo.Doc as Doc
import qualified Data.Aeson as JSON
import Data.Aeson ((.=))
import qualified Data.Map as Map
import Data.Text (unpack)

import Cryptol.ModuleSystem.Env (loadedModules)
import Cryptol.ModuleSystem.Interface (ifsDoc)
import Cryptol.TypeCheck.AST as T
import Cryptol.Utils.PP (PP, pp)

import CryptolServer (CryptolCommand, getModuleEnv)


data VisibleModulesParams = VisibleNamesParams

instance JSON.FromJSON VisibleModulesParams where
parseJSON _ = pure VisibleNamesParams

instance Doc.DescribedMethod VisibleModulesParams [VisibleModuleInfo] where
parameterFieldDescription = []

resultFieldDescription =
[ ("module",
Doc.Paragraph [ Doc.Text "A human-readable representation of the module's name"])
, ("documentation",
Doc.Paragraph [ Doc.Text "An optional field containing documentation strings for the module, "
, Doc.Text "if it is documented"])
, ("parameterized",
Doc.Paragraph [ Doc.Text "A Boolean value indicating whether the focused module is "
, Doc.Text "parameterized"
])
]

visibleModulesDescr :: Doc.Block
visibleModulesDescr =
Doc.Paragraph [Doc.Text "List the currently visible (i.e., in scope) module names."]

visibleModules :: VisibleModulesParams -> CryptolCommand [VisibleModuleInfo]
visibleModules _ = concatMap (moduleToInfos False) . loadedModules <$> getModuleEnv


moduleToInfos :: PP a => Bool -> T.ModuleG a -> [VisibleModuleInfo]
moduleToInfos p m =
ModuleInfo
{ name = show (pp (T.mName m))
, parameterized = p
, docs = map unpack (T.mDoc m)
} :
[ ModuleInfo
{ name = show (pp k)
, parameterized = p
, docs = map unpack (ifsDoc (T.smIface v))
}
| (k,v) <- Map.assocs (T.mSubmodules m)
] ++
[ x
| v <- Map.elems (T.mFunctors m)
, x <- moduleToInfos True v
]

data VisibleModuleInfo =
ModuleInfo
{ name :: String
, parameterized :: Bool
, docs :: [String]
}

instance JSON.ToJSON VisibleModuleInfo where
toJSON ModuleInfo{..} = JSON.object (
[ "module" .= name
, "parameterized" .= parameterized] ++
["documentation" .= docs | not (null docs)])
Loading