-
Notifications
You must be signed in to change notification settings - Fork 232
Editor support for F*
You can edit F★ code using your favourite text editor, but Emacs, Visual Studio Code, Atom, and Vim have extensions that add special support for F★, including syntax highlighting and interactive development.
fstar-mode supports F★'s IDE protocol described below on this page, and includes support for interactive proofs, syntax highlighting, completion, type hints, documentation queries, and quick navigation.
fstar-vscode-assistant is a Visual Studio Code extension for F*. It uses language-server protocol between a VS Code client and language server, combined with F*'s IDE protocol. It supports interactive proofs, syntax highlighting, completion, type hints, quick navigation, document formatting, etc.
VimFStar is a Vim plugin for F★ that supports interactive development and syntax highlighting.
The Atom package for F★ supports syntax highlighting via atom-fstar and interactive development via fstar-interactive.
Status: On Jan 11, 2017 it was possible to verify an F★ file through atom-script (this is not as sophisticated as the emacs mode which can verify files piece-by-piece). There was also a fork of atom-fstar here. None of these currently supports the new JSON protocol below.
F★ can be started in IDE mode by passing it the --ide
flag, which supersedes the legacy --in
flag. Clients of the former API are encouraged to migrate (see migration notes below).
>>>
indicates client messages. <<<
indicates F★'s response. ###
are comments. For convenience responses were reformatted, but in the actual protocol each message occupies a single line.
$ fstar.exe test.fst --ide ### Start F★
### F★ immediately sends information about the protocol upon starting
<<< { "features": [ "autocomplete", "compute", "describe-protocol", "describe-repl",
"exit", "lookup", "lookup/documentation", "lookup/definition",
"pop", "peek", "push", "search" ],
"version": 2,
"kind": "protocol-info" }
### Let's send some code for processing:
>>> {"query-id":"1","query":"push","args":{"kind":"full","code":"module Test\n","line":1,"column":0}}
### Looks like that worked — no errors
<<< { "response": [],
"status": "success",
"query-id": "1",
"kind": "response" }
### Another push
>>> {"query-id":"2","query":"push","args":{"kind":"full","code":"\nlet rec fib n =\n if n <= 1 then 1 else fib (n - 1) + fib (n - 2)\n","line":2,"column":0}}
<<< { "kind": "response",
"query-id": "2",
"status": "success",
"response": [] }
### Use starts typing, so we try a completion
>>> {"query-id":"3","query":"autocomplete","args":{"partial-symbol":"fi"}}
### Response includes the length of the match, the namespace, and the completion candidate
<<< { "response": [[2, "Test", "fib"]],
"status": "success",
"query-id": "3",
"kind": "response" }
### It's easy to get more info about a symbol, too:
>>> {"query-id":"4","query":"lookup","args":{"symbol":"Test.fib","requested-info":["type","defined-at","documentation"]}}
<<< { "response": {
"definition": null,
"documentation": null,
"type": "(n:int -> Tot int)",
"defined-at": { "end": [3, 11],
"beg": [3, 8],
"fname": "<input>" },
"name": "Test.fib" },
"status": "success",
"query-id": "4",
"kind": "response" }
### If a push fails, we get errors:
>>> {"query-id":"5","query":"push","args":{"kind":"full","code":"let a = assert false\n","line":5,"column":0}}
<<< { "response": [{ "ranges": [{ "end": [5, 20],
"beg": [5, 0],
"fname": "<input>" }],
"message": "assertion failed",
"level": "error" }],
"status": "failure",
"query-id": "5",
"kind": "response" }
One way to experiment with F*'s IDE protocol is to collect a sample trace of events from emacs when running fstar-mode.el. You can do this by adding this to your .emacs (setq fstar-subp-debug t)
and then launching fstar-mode.el on a sample F* file. This will result in the trace of interactions between emacs and fstar being recorded in the *fstar-debug*
buffer. For example, you will see something like this.
;;; Started F* interactive: (" path/to/fstar.exe" " ... arguments to F* ...")
{"kind":"protocol-info","version":2,"features":["autocomplete","autocomplete/context","compute","compute/reify","compute/pure-subterms","describe-protocol","describe-repl","exit","lookup","lookup/context","lookup/documentation","lookup/definition","peek","pop","push","search","segment","vfs-add","tactic-ranges","interrupt","progress"]}
;;; Complete message received: (status: nil; message: ((kind . "protocol-info") (version . 2) (features "autocomplete" "autocomplete/context" "compute" "compute/reify" "compute/pure-subterms" "describe-protocol" "describe-repl" "exit" "lookup" "lookup/context" "lookup/documentation" "lookup/definition" "peek" "pop" "push" "search" "segment" "vfs-add" "tactic-ranges" "interrupt" "progress")))
;;; [441.68ms] Feature list received
;;; Processing queue
>>> {"query-id":"1","query":"vfs-add","args":{"filename":null,"contents":"(*\n Copyright 2021 Microsoft Research\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n ... ""}}
>>> {"query-id":"2","query":"push","args":{"kind":"full","code":"...","line":1,"column":0}}
{"kind":"response","query-id":"1","status":"success","response":null}
;;; Complete message received: (status: "success"; message: ((kind . "response") (query-id . "1") (status . "success") (response . :json-null)))
;;; Query "1" completed in 0.2522s
{"kind":"message","query-id":"2","level":"progress","contents":{"stage":"loading-dependency","modname":"prims"}}
The lines that begin with ">>>" are the messages sent from emacs to F*. If you save those lines only to a file (without the ">>>" prefix) and pipe it to F* launched with the arguments described on the first line of the debug output, then you can replay an interaction with F* independently of the editor. e.g, cat trace | fstar.exe --ide A.fst
F★'s IDE mode (introduced in version 0.9.4.3) is a simple JSON-based client-server protocol over text pipes. Clients send newline-terminated queries, and the F★ server responds with a mix of newline-terminated responses (one per query) and additional newline-terminated messages providing status or progress information. The current server is ordered and synchronous (at most one query can run at any time and responses are sent in order), but clients should not depend on this.
Each query is identified by a client-selected ID which the server echoes back in exactly one response and zero or more messages pertaining to that query. Certain server messages are not directly related to a query and do not include a query ID (in fact, the current implementation of the server never includes query IDs in messages).
F★ documents are sequences of individual phrases. Clients are expected to use simple heuristics or user interaction to segment the document into individual definitions, and send these sentences one by one to the F★ server using sequences of push
and pop
queries. A push
indicates to F★ that one more sections of the buffer are ready be parsed and typechecked, and a pop
undoes the effect of the last push
. F★ responds to each push with a success code indicating whether the corresponding segment was correctly parsed and typechecked. If so, the client may send the next segment (clients should wait for each segment to be fully processed before sending the next one); if not, the client should indicate errors to the user and re-send an updated segment when appropriate.
The first query must be a
(version 2 of the protocol) There are no restrictions on the first query (nonetheless, since F★ reads the whole file to load dependencies upon the first push, it is recommended to precede it with a push
.vfs-add
). Some queries (but not push
) may fail with Current module unset
until a module
declaration is pushed.
On recent OCaml builds, clients can send F★ an interrupt signal (SIGINT) to interrupt long-running queries (currently compute
and push
). F★ may ignore the interrupt, or cause the currently-running query to fail early.
Client messages are all in the one form.
-
Client queries contain three mandatory fields
query-id
,query
, andargs
; no other fields are recognized:{ "query-id": "…", "query": "…", "args": { … } }
-
query-id
is a client chosen, session-unique string (a perpetually increasing counter is a good choice). -
query
is a string identifying the query. -
args
is query-specific. Send{}
, notnull
, to indicate an empty collection ofargs
.
-
Server messages can take multiple formats. In all cases, future versions of the protocol may include more data; this is not considered a breaking change. All server messages contain a ""kind"
field.
-
Protocol information messages can be sent at any time; one is sent right before F★ starts to listen for client queries:
{ "kind": "protocol-info", "features": [ … ], "version": … }
-
features
is a list of strings indicating which features this build of F★ supports. Clients should use this list instead of F★'s version number to determine whether a feature is available. -
version
is the protocol's version number. The current version number is 1. This number is incremented every time the API is changed in backwards-incompatible ways.
-
-
Status messages can be sent at any time; they provide clients with feedback while F★ is busy preparing a full-fledged
response
. Most messages should be displayed to the user, and even anerror
-level message doesn't indicate that e.g. apush
failed.{ "kind": "message", "level": "…", "contents": "…" }
-
level
is one of"error"
,"warning"
,"info"
and"proof-state"
(other values oflevel
may be added in the future — clients should ignore unknown values). -
contents
is message-specific: see List of status messages below. -
query-id
is optional and indicates the query (if any) whose execution caused this message to be sent.
-
-
Response messages are sent in response to client queries (exactly one response per query). They indicate whether a query completed successfully, and which results it produced if any. Each response contains the ID of the corresponding query.
{"kind": "response", "query-id": "…", "status": "…", "response": …}
-
query-id
is a string (the one sent by the client). Ifstatus
is"protocol-violation"
, however, thequery-id
field may be set to"?"
. -
status
is one of"success"
,"failure"
, or"protocol-violation"
. -
response
is query-specific.
-
These are part of the list returned in "protocol-info" messages:
- Queries (these just indicate that a query is available):
autocomplete
;compute
;describe-protocol
;describe-repl
;exit
;lookup
;pop
;peek
;push
;search
;segment
;vfs-add
-
lookup/documentation
;lookup/definition
: indicate thatlookup
understand requests for documentation and definitions. -
lookup/context
;autocomplete/context
: indicate thatlookup
andautocomplete
understand thecontext
argument. -
tactic-ranges
: indicate that proof states may contain ranges -
interrupt
: indicate that F★ will gracefully handle sigint (C-c C-c) signals
Exit the current F★ subprocess. This is equivalent to sending a C-d
(U+004 END OF TRANSMISSION
) character. It yields a clean exit, though usually just killing the F★ subprocess is fine too (except for saving hints).
- Query
args
:{}
-
status
: always"success"
-
response
:null
- Example:
>>> { "query-id": "0", "query": "exit", "args": {} } <<< {"kind":"response","query-id":"0","status":"success","response":null}
Replay the welcome message.
- Query
args
:{}
-
status
: always"success"
-
response
: Same as inprotocol-info
messages. - Example:
>>> { "query-id": "0", "query": "describe-protocol", "args": {} } <<< { "response": { "features": [ "autocomplete", "compute", "describe-protocol", "describe-repl", "exit", "lookup", "lookup/documentation", "lookup/definition", "pop", "peek", "push", "search" ], "version": 2 }, "status": "success", "query-id": "0", "kind": "response" }
Give information on the current state of the subprocess. Currently only loaded dependencies.
- Query
args
:{}
-
status
: always"success"
-
response
(dictionary): Information about the current subprocess state:-
loaded-dependencies
(list of strings): Currently loaded dependencies. -
options
(list of dictionaries): All F★ options-
name
(string): Name of the option -
value
(null, bool, string, or list of the same): Value of the option;null
if unset -
default
(null, bool, string, or list of the same): Default value of the option -
documentation
(string): Docs of the option
-
-
- Example:
>>> { "query-id": "0", "query": "describe-repl", "args": {} } <<< { "response": { "loaded-dependencies": [ "/build/fstar/compute/ulib/FStar.String.fsti", "/build/fstar/compute/ulib/FStar.List.fst", "/build/fstar/compute/ulib/FStar.List.Tot.fst", "/build/fstar/compute/ulib/FStar.List.Tot.Properties.fst", "/build/fstar/compute/ulib/FStar.List.Tot.Base.fst", "/build/fstar/compute/ulib/FStar.Classical.fst", "/build/fstar/compute/ulib/FStar.Squash.fsti", "/build/fstar/compute/ulib/FStar.Char.fsti", "/build/fstar/compute/ulib/FStar.All.fst", "/build/fstar/compute/ulib/FStar.ST.fst", "/build/fstar/compute/ulib/FStar.Heap.fst", "/build/fstar/compute/ulib/FStar.TSet.fst", "/build/fstar/compute/ulib/FStar.PredicateExtensionality.fst", "/build/fstar/compute/ulib/FStar.PropositionalExtensionality.fst", "/build/fstar/compute/ulib/FStar.FunctionalExtensionality.fst" ], "options": [{ "documentation": "JSON-based interactive mode for IDEs", "default": false, "value": true, "name": "ide"}, …] }, "status": "success", "query-id": "0", "kind": "response" }
Undo the last push
.
- Query
args
:{}
-
status
:"success"
if there was a segment to pop -
response
:null
- Example:
>>> { "query-id": "0", "query": "pop", "args": {} } <<< {"kind":"response","query-id":"0","status":"success","response":null}
Send a buffer segment to F★.
- Query
args
:-
kind
(string):"lax"
for quick checking (no VCs generated) or"full"
for complete checking. -
code
(string): A code fragment -
line
(int),column
(int): position of the beginning of the fragment in the current buffer. Consecutivepush
es should correspond to consecutive buffer segments. Lines are 1-indexed; columns are 0-indexed.
-
-
status
:"success"
if F★ accepted that code fragment;"failure"
otherwise -
response
(list): A collection of errors or warnings, each with:-
level
(string): One of"error"
,"warning"
,"info"
,"not-implemented"
-
message
(string): Issue message -
ranges
(list): A list of ranges, each with:-
fname
(string): Name of file in which the issue occurred, possibly "" -
beg
(list of int): Line and column where the issue starts -
end
(list of int): Line and column where the issue ends
-
-
- Interrupts: This query can be interrupted, except when loading dependencies. Interrupts will cause the query to fail early, returning a possibly-empty list of error messages.
- Example:
>>> { "query-id": "0", "query": "push", "args": { "kind": "full", "code": "module Test\nval a : int\nlet a : nat = 1", "line": 1, "column": 0 } } <<< { "kind" : "response", "query-id" : "0", "response" : [{ "level" : "warning", "message" : "Annotation from val declaration overrides inline type annotation", "ranges" : [{ "beg" : [3, 0], "end" : [3, 15], "fname" : "<input>" }] }], "status" : "success" }
Same as push
immediately followed by pop
. This is useful to implement on-the-fly checking.
- Query
args
: Likepush
, butkind
can also be"syntax"
to check syntax only. -
status
: Likepush
-
response
: Likepush
- Example:
>>> { "query-id": "0", "query": "peek", "args": { "kind": "lax", "code": "module Test\nval a : int\nlet a : nat = 1", "line": 1, "column": 0 } } <<< { "kind" : "response", "query-id" : "0", "response" : [{ "level" : "warning", "message" : "Annotation from val declaration overrides inline type annotation", "ranges" : [{ "beg" : [3, 0], "end" : [3, 15], "fname" : "<input>" }] }], "status" : "success" }
Find completions matching a search term (commonly a prefix). Results can be improved by specifying a context — the following contexts are recognized:
-
open
, e.g.open FSt|
-
include
, e.g.include FS.Li|
-
let-open
. e.g.let app = let open FStar.Lis| in append
-
module-alias
, e.g.module A = FStar.Se|
-
set-options
, e.g.#set-options "--a|bc"
-
reset-options
, e.g.#reset-options "--a|bc"
-
code
for all other code
- Query
args
:-
partial-symbol
(string): Part of a term to be completed. The term is either a partial identifier (incode
context), a partial module or namespace name (inopen
,include
,let-open
, andmodule-alias
contexts) or an option name with leading dashes (inset-options
andreset-options
contexts). -
context
(optional, string, default"code"
): A completion context (see above)
-
-
status
: alwayssuccess
-
response
(list): A list of 3-element lists[match-length, namespace, candidate]
-
match-length
(int): how far the match extends incandidate
-
namespace
(string): where the match lives -
candidate
(string): a completion candidate
-
- Example:
>>> { "query-id": "0", "query": "autocomplete", "args": { "partial-symbol": "fs" } } <<< { "kind": "response", "query-id": "0", "status": "success", "response": [[2, "Prims", "fst"]]} >>> { "query-id": "0", "query": "autocomplete", "args": { "partial-symbol": "Pr.fs" } } <<< { "kind": "response", "query-id": "0", "status": "success", "response": [[8, "", "Prims.fst"]]}
Find information about an identifier.
- Query
args
:-
symbol
(string): A term to get information on (see thesymbol
argument of theautocomplete
query) -
context
(optional, string, default"code"
): A completion context (see above) -
location
(optional,{ column, line, filename }
): where the symbol is in the file (lines are 1-indexed; columns are 0-indexed). -
requested-info
(list of string): Which fields to return (among"name"
,"defined-at"
,"documentation"
,"type"
, and"definition"
). Other fields are returned asnull
.
-
-
status
:"success"
if the symbol was found;"failure"
otherwise -
response
(dictionary):-
"name"
(string): Full name of the symbol -
"defined-at"
(dictionary): Location of the symbol's definition-
"beg"
: line, column where the definition begins -
"end"
: line, column where the definition ends -
"fname"
: file in which the symbol was defined
-
-
"documentation"
(string): FsDoc for this symbol -
"type"
(string): Pretty-printed type -
"definition"
(string): Pretty-printed definition
-
- Example:
>>> { "query-id": "0", "query": "lookup", "args": { "symbol": "fst", "requested-info": ["name", "defined-at"] } } <<< { "response": { "definition": null, "documentation": null, "type": null, "defined-at": { "end": [615, 7], "beg": [615, 4], "fname": "/build/fstar/compute/bin/../ulib/prims.fst" }, "name": "Prims.fst" }, "status": "success", "query-id": "0", "kind": "response" }
Reduce a term using F★'s normalizer.
- Query
args
:-
term
(string): A term to reduce -
rules
(optional, list of string, default to all rules): which reduction rules to use (one or more of"beta"
,"delta"
,"iota"
,"zeta"
)
-
-
status
:"success"
if the termed reduced successfully;"failure"
otherwise -
response
: A string containing the reduced term, or an error message in case of failure - Interrupts: This query can be interrupted. Interrupts will cause an early
"failure"
to be returned. - Example:
>>> { "query-id": "0", "query": "compute", "args": { "term": "let x = (fun x -> x + 1) 1 in x + x" } } <<< { "kind": "response", "query-id": "0", "status": "success", "response": "4" }
Search the current environment for functions or theorems.
- Query
args
:-
terms
(string): Search terms (user-provided). No search terms parsing should happen on the client side.
-
-
status
:"success"
if results were found;"failure"
otherwise -
response
(list): A list of search results, or an error message (string) if no results are found or if the search terms cannot be parsed:-
lid
: Result's ame -
type
: Result's type
-
- Example:
>>> { "query-id": "0", "query": "search", "args": { "terms": "FStar.List.op_At FStar.List.length" } } <<< { "response": [{ "type": "(l1:(list 'a@0) -> l2:(list 'a@1) -> Lemma …)", "lid": "append_length" }], "status": "success", "query-id": "0", "kind": "response" }
Map a file into F★'s virtual file system. A vfs-add
query for file a.fst
with contents xyz
tells F★ to assume that a.fst
contains xyz
ignoring the (possibly non-existent) a.fst
on disk.
This query is useful to notify F★ about yet-unsaved edits, as F★ reads the whole current file (to load its dependencies) before processing the first push and before every push
following a complete sequence of pop
s.
- Query
args
:-
filename
(optional string, defaults to the current file): The path of the file to map. -
contents
(string): The contents of the file.
-
-
status
: always"success"
-
response
:null
- Example:
>>> { "query-id": "1", "query": "vfs-add", "args": { "filename": null, "contents": "module Test\n" } } <<< { "response": null, "status": "success", "query-id": "1", "kind": "response" }
Split an F★ program into individual definitions.
- Query
args
:-
code
(string): A valid F★ program.
-
-
status
:"success"
if the program could be parsed into a list of declarations;"failure"
otherwise -
response
(dictionary):-
"decls"
(list of dictionaries): A list of contiguous definition ranges-
"def_range"
: Range covering a declaration ("beg"
,"end"
, and"fname"
)
-
-
- Example:
>>> { "query-id": "0", "query": "segment", "args": { "code": "let a = 1\nlet b = 2" } } <<< { "response": { "decls": [{ "def_range": { "beg": [1, 0], "end": [1, 9], "fname": "<input>" } }, { "def_range": { "beg": [2, 0], "end": [2, 9], "fname": "<input>" } }] }, "status": "success", "query-id": "0", "kind": "response" }
-
contents
: A string to display to the user.
-
contents
: A dictionary containing the proof state at the point of adump
, in the following format:-
label
: The string passed todump
-
depth
: Depth for tracing and debugging -
goals
andsmt-goals
: Lists of dictionaries containing the active goals and the goals that have been passed to the SMT solver, in the following format:-
hyps
: List of hypotheses, containingname
andtype
-
goal
: The goal, containingwitness
andtype
-
-
location
: Optional field, containing location information if available (file namefname
, beginning line and column listbeg
, ending line and column listend
)
-
-
Example:
{"label": "After applying f", "depth": 1, "goals": [ {"hyps": [ {"name": "h", "type": "Cases.p \/ Cases.q"}, {"name": "p", "type": "Prims.pure_post Prims.unit"}, {"name": "uu___", "type": "Prims.squash (forall (pure_result: Prims.unit). Cases.r ==> p pure_result)"}], "goal": {"witness": "(*?u61*) _ h p uu___933", "type": "Prims.squash (Cases.q ==> Cases.r)"}}], "smt-goals": [], "location": {"fname": "examples/tactics/Cases.fst", "beg": [20,35], "end": [20,46]}}
Differences between the old and the new protocol are documented in the commit message that introduced the new protocol (https://github.com/FStarLang/FStar/commit/90e901f502f8dcb3eda2e29208a699921ffa64ec). A simple port (not adding support for new features) should just require reading and writing JSON messages instead of plain text, and removing the #pop
queries that used to be required after failed #push
es.