I am proud to announce the successful completion of the Web3 Foundation grant for developing ink! analyzer - a collection of modular and reusable libraries and tools for semantic analysis of ink! smart contract code.
ink! analyzer aims to improve ink! language support in integrated development environments (IDEs), source code editors and other development tools by providing modular and reusable building blocks for implementing language features (e.g. diagnostic errors, quick fixes, code completion suggestions, code/intent actions and hover content e.t.c) for the ink! programming language which is used for writing smart contracts for blockchains built with Substrate.
- Semantic Analyzer (source code, crates.io, docs.rs).
- Language Server (source code, binary/executable releases, crates.io, docs.rs).
- Visual Studio Code Extension (source code, VS Code Marketplace listing, VSIX releases).
Problem
ink! is an Embedded Domain Specific Language (eDSL)
that you can use to write WebAssembly based smart contracts in the
Rust programming language. In fact,
"ink! is just standard Rust in a well-defined "contract format" with specialized #[ink(…)]
attribute macros".
This allows ink! developers to leverage Rust tooling like clippy, cargo, crates.io and excellent IDE/code editor support via rust-analyzer and IntelliJ Rust.
However, relying on only generic Rust language support in IDEs, code editors and other development tools has some significant limitations for the developer experience including:
- No language support (e.g. diagnostic errors/warnings and quick fixes) for ink!'s domain specific semantic rules for
smart contracts (e.g. exactly one
#[ink(storage)]
struct, at least one#[ink(message)]
method and the same for#[ink(constructor)]
, ink! attributes should be applied to items of the correct type, ink!env
andenvironment
argument values mustimpl Environment
e.t.c). - Inconsistent editor experience with issues like no code completion and/or hover content for some
ink! attribute arguments (e.g.
#[ink(payable)]
) because macro expansion/name resolution and trait resolution are hard problems for generic IDE/code editor tools (see also https://rust-analyzer.github.io/blog/2021/11/21/ides-and-macros.html).
Solution
To solve the above challenges and improve ink! language support in IDEs, code editors and other development tools, ink! analyzer creates two main components:
- A modular domain-specific semantic analysis library for ink! built on a resilient and lossless parser.
- A Language Server Protocol (LSP) implementation built on top of the aforementioned semantic analysis library.
These two components can be reused to add ink! language support to multiple IDEs, code editors and other development tools.
In particular, a large number of IDEs and code editors support LSP servers either via configurable LSP clients or robust LSP client libraries/APIs/modules, including Visual Studio Code, Visual Studio, Vim / Neovim, Emacs, Atom, Sublime Text, Acme, Lapce, Eclipse and many more.
ink! analyzer makes it relatively easy for:
- Users to enable ink! language support for their IDE, code editor or other development tool if it has either a native/built-in or third-party LSP client that can be configured to launch an LSP server using an executable command (i.e. the path to an installed ink! Language Server binary) and can use stdio (standard in/standard out) as the message transport.
- Developers to either build extensions/plugins/integrations that add ink! language support to any tool with robust LSP client libraries/APIs/modules, or add first-class ink! language support to an existing LSP client (e.g. an open-source extension/plugin/integration).
The latter option typically provides a better user experience as the user doesn't have to manually install (and update) the ink! Language Server as it can be bundled (or installed/updated) by the extension/plugin/integration.
Therefore, in addition to
distributing compiled ink! Language Server (ink-lsp-server
) binaries for most of the major platforms/architectures,
ink! analyzer additionally distributes a
Visual Studio Code extension
that ships with a bundled ink! Language Server as a showcase and
reference implementation for the latter use case.
Architecture
Figure 1: ink! Analyzer - LSP based Architecture Diagram
1. Semantic Analyzer
The semantic analyzer is responsible for parsing the smart contract code, analyzing it based on ink!'s semantic rules for smart contracts and returning semantic information for other components to consume.
It is written in Rust and uses rust-analyzer's ra_ap_syntax crate for generating the syntax tree of the smart contract code that it analyzes.
It uses ra_ap_syntax instead of other Rust parsing and syntax tree libraries because ink! analyzer has similar design goals to rust-analyzer. The most important being that parsing should be:
- resilient (even if the input is invalid, parser tries to see as much syntax tree fragments in the input as it can).
- lossless (even if the input is invalid, the tree produced by the parser represents it exactly).
As a concrete example, while ink!'s ink_ir crate includes syntax tree and intermediate representation (IR) modules, it uses syn as its parser. Syn however, (being "geared toward use in Rust procedural macros") assumes "that all input to it is well-formed". So for the ink!/Rust code snippet below that contains only one invalid line, syn simply returns an error while ra_ap_syntax still generates a lossless syntax tree with errors represented by "error nodes", thus allowing analysis to still be performed on the rest of the valid code.
#[ink::contract]
mod flipper {
#[ink(storage)]
pub struct Flipper {
value: bool,
}
bad statement; // This is an invalid line
}
This makes syn (and by extension ink!'s ink_ir crate) unsuitable for our IDE/code editor use case.
2. Language Server
The language server implements the Language Server Protocol (LSP) and acts as a backend that provides language support features like diagnostic errors, code completion suggestions, code/intent actions and hover content to IDEs, code editors and other development tools. It uses the semantic analyzer as the engine for providing ink! language support features by:
- translating LSP requests into semantic analyzer interface calls.
- translating semantic analysis results into corresponding LSP types.
It can be reused by multiple IDEs, code editors and other development tools that support LSP servers either via native/built-in LSP clients or third-party LSP client extensions/plugins/integrations.
It is written in Rust and uses:
- rust-analyzer's lsp-server crate to handle LSP protocol handshaking and parsing messages
- the lsp-types crate for LSP type definitions.
3. Extension/Plugin/Integration
Extensions/plugins/integrations add ink! language support to a specific IDE, code editor or development tool by communicating with the language server running as a separate process using the Language Server Protocol via JSON-RPC.
Only a VS Code extension is currently implemented. The VS Code extension is written in TypeScript using VS Code's extension API and language server client library.
Diving Deeper
You can learn more about the above components as well as other lower-level crates/modules used by ink! analyzer, including: installation, development, usage and testing instructions, library documentation, low-level technical and architectural descriptions, and access the source code using the following resources:
- Rust monorepo that includes:
- The semantic analyzer, language server and other crates.
- An architectural description of all the various crates and how they relate to each other.
- Crate specific READMEs with installation and usage instructions, links to documentation on docs.rs and testing instructions.
- Extensive inline source documentation.
- Source code repository for the Visual Studio Code extension that includes:
- A README that describes the currently implemented features and settings for the extension.
- A detailed Development and Testing guide.
- Extensive inline source documentation.
- Rust crates published on crates.io:
- Documentation for Rust crates published on docs.rs:
- Distributable releases for major operating systems (Windows, Linux and macOS) and processor architectures (x86_64/amd64 and arm64/aarch64) for:
- Published packages for the extension on the Visual Studio Code marketplace.
Conclusion
ink! analyzer is only at the very beginning of its development, so issues, bug reports, PRs and feature requests are welcome at the aforementioned GitHub repositories 🙂.
Special thanks to the Web3 Foundation for funding ink! analyzer's development via a generous grant.