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 Compilation API #1194

Merged
merged 3 commits into from
Dec 17, 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
5 changes: 5 additions & 0 deletions .changeset/fluffy-elephants-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/slang": minor
---

rename `parser/Parser.supportedVersions()` API to `utils/LanguageFacts.supportedVersions()`.
5 changes: 5 additions & 0 deletions .changeset/gentle-onions-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/slang": minor
---

expose the `BingingGraph` API to allow querying definitions/references between source files.
5 changes: 5 additions & 0 deletions .changeset/pink-flowers-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/slang": minor
---

add a `CompilationBuilder` API to incrementally load and resolve source files and their imports.
1 change: 1 addition & 0 deletions crates/codegen/runtime/cargo/crate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ description = "Cargo runtime copied over by codegen"
default = []
__experimental_bindings_api = ["dep:metaslang_bindings"]
__private_ariadne_errors = ["dep:ariadne"]
__private_compilation_api = []
__private_testing_utils = []

[build-dependencies]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use semver::Version;

use crate::bindings::BindingGraph;
use crate::parser::ParserInitializationError;

#[allow(clippy::needless_pass_by_value)]
pub fn add_built_ins(
_binding_graph: &mut BindingGraph,
_version: Version,
) -> Result<(), ParserInitializationError> {
unreachable!("Built-ins are Solidity-specific")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::cst::Cursor;

pub struct ImportPathsExtractor;

impl ImportPathsExtractor {
pub fn new() -> Self {
Self
}

#[allow(clippy::unused_self)]
#[allow(clippy::needless_pass_by_value)]
pub fn extract(&self, _: Cursor) -> Vec<Cursor> {
unreachable!("Import paths are Solidity-specific")
}
}
8 changes: 8 additions & 0 deletions crates/codegen/runtime/cargo/crate/src/extensions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[cfg(all(
feature = "__experimental_bindings_api",
feature = "__private_compilation_api"
))]
pub mod compilation;

#[cfg(feature = "__experimental_bindings_api")]
pub mod bindings;
1 change: 1 addition & 0 deletions crates/codegen/runtime/cargo/crate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The final code (generated in output crates) is checked for dead-code anyways.
#![allow(dead_code, unused_imports)]

mod extensions;
mod runtime;

pub use runtime::*;
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use semver::Version;

// TODO: This should be moved to the Solidity-specific 'extensions' sub-module.
#[allow(unused_variables)]
pub fn get_contents(version: &Version) -> &'static str {
pub fn get_built_ins_contents(version: &Version) -> &'static str {
{%- if not rendering_in_stubs -%}
{%- for version in model.bindings.built_ins_versions %}
{%- if not loop.first -%}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 26 additions & 11 deletions crates/codegen/runtime/cargo/crate/src/runtime/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,46 @@
mod binding_rules;

#[path = "generated/built_ins.rs"]
mod built_ins;
pub mod built_ins;

use std::sync::Arc;
use std::rc::Rc;

use metaslang_bindings::{self, PathResolver};
use semver::Version;

use crate::cst::KindTypes;

pub type Bindings = metaslang_bindings::Bindings<KindTypes>;
pub type BindingGraph = metaslang_bindings::BindingGraph<KindTypes>;
pub type Definition<'a> = metaslang_bindings::Definition<'a, KindTypes>;
pub type Reference<'a> = metaslang_bindings::Reference<'a, KindTypes>;
pub type BindingLocation = metaslang_bindings::BindingLocation<KindTypes>;
pub type UserFileLocation = metaslang_bindings::UserFileLocation<KindTypes>;

pub use metaslang_bindings::{BuiltInLocation, PathResolver};

use crate::parser::ParserInitializationError;

#[derive(thiserror::Error, Debug)]
pub enum BindingGraphInitializationError {
#[error(transparent)]
ParserInitialization(#[from] ParserInitializationError),
}

pub fn create_with_resolver(
version: Version,
resolver: Arc<dyn PathResolver + Sync + Send>,
) -> Bindings {
Bindings::create(version, binding_rules::BINDING_RULES_SOURCE, resolver)
resolver: Rc<dyn PathResolver<KindTypes>>,
) -> Result<BindingGraph, ParserInitializationError> {
let mut binding_graph = BindingGraph::create(
version.clone(),
binding_rules::BINDING_RULES_SOURCE,
resolver,
);

crate::extensions::bindings::add_built_ins(&mut binding_graph, version)?;

Ok(binding_graph)
}

#[cfg(feature = "__private_testing_utils")]
pub fn get_binding_rules() -> &'static str {
binding_rules::BINDING_RULES_SOURCE
}

pub fn get_built_ins(version: &semver::Version) -> &'static str {
built_ins::get_contents(version)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use std::collections::BTreeMap;

use metaslang_cst::text_index::TextIndex;

use crate::cst::{Cursor, Node};

#[derive(Clone)]
pub struct File {
id: String,
tree: Node,

resolved_imports: BTreeMap<usize, String>,
}

impl File {
pub(super) fn new(id: String, tree: Node) -> Self {
Self {
id,
tree,

resolved_imports: BTreeMap::new(),
}
}

pub fn id(&self) -> &str {
&self.id
}

pub fn tree(&self) -> &Node {
&self.tree
}

pub fn create_tree_cursor(&self) -> Cursor {
self.tree.clone().cursor_with_offset(TextIndex::ZERO)
}

pub(super) fn resolve_import(&mut self, import_path: &Cursor, destination_file_id: String) {
self.resolved_imports
.insert(import_path.node().id(), destination_file_id);
}

pub(super) fn resolved_import(&self, import_path: &Cursor) -> Option<&String> {
self.resolved_imports.get(&import_path.node().id())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::collections::BTreeMap;
use std::rc::Rc;

use semver::Version;

use crate::compilation::{CompilationUnit, File};
use crate::cst::Cursor;
use crate::extensions::compilation::ImportPathsExtractor;
use crate::parser::{Parser, ParserInitializationError};

pub struct InternalCompilationBuilder {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be called CompilationBuilder? I assume the Internal prefix is because it's only used for the Typescript interface, but looks like a useful component for Rust users as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

After reading the whole PR I got to the actual CompilationBuilder. I still find the Internal prefix a bit confusing here, but it's more a nit than anything else.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking that we would then expose a similar callback-based API in Rust as well using a new CompilationBuilder that would wrap the InternalCompilationBuilder here. WDYT?
I'm happy to rename it as well if you have a better idea.

parser: Parser,
imports: ImportPathsExtractor,
files: BTreeMap<String, File>,
}

#[derive(thiserror::Error, Debug)]
pub enum CompilationInitializationError {
#[error(transparent)]
ParserInitialization(#[from] ParserInitializationError),
}

impl InternalCompilationBuilder {
pub fn create(language_version: Version) -> Result<Self, CompilationInitializationError> {
let parser = Parser::create(language_version)?;

Ok(Self {
parser,
imports: ImportPathsExtractor::new(),
files: BTreeMap::new(),
})
}

pub fn add_file(&mut self, id: String, contents: &str) -> AddFileResponse {
if self.files.contains_key(&id) {
// Already added. No need to process it again:
return AddFileResponse {
import_paths: vec![],
};
}

let parse_output = self.parser.parse(Parser::ROOT_KIND, contents);

let import_paths = self.imports.extract(parse_output.create_tree_cursor());

let file = File::new(id.clone(), parse_output.tree().clone());
self.files.insert(id, file);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we check if the file was already added?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. We do that check in TS, but wouldn't hurt to add it here as well.


AddFileResponse { import_paths }
}

pub fn resolve_import(
&mut self,
source_file_id: &str,
import_path: &Cursor,
destination_file_id: String,
) -> Result<(), ResolveImportError> {
self.files
.get_mut(source_file_id)
.ok_or_else(|| ResolveImportError::SourceFileNotFound(source_file_id.to_owned()))?
.resolve_import(import_path, destination_file_id);

Ok(())
}

pub fn build(&self) -> CompilationUnit {
let language_version = self.parser.language_version().to_owned();

let files = self
.files
.iter()
.map(|(id, file)| (id.to_owned(), Rc::new(file.to_owned())))
.collect();

CompilationUnit::new(language_version, files)
}
}

pub struct AddFileResponse {
pub import_paths: Vec<Cursor>,
}

#[derive(thiserror::Error, Debug)]
pub enum ResolveImportError {
#[error(
"Source file not found: '{0}'. Make sure to add it first, before resolving its imports."
)]
SourceFileNotFound(String),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod file;
mod internal_builder;
mod unit;

pub use file::File;
pub use internal_builder::{AddFileResponse, InternalCompilationBuilder};
pub use unit::CompilationUnit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use std::cell::OnceCell;
use std::collections::BTreeMap;
use std::rc::Rc;

use semver::Version;

use crate::bindings::{
create_with_resolver, BindingGraph, BindingGraphInitializationError, PathResolver,
};
use crate::compilation::File;
use crate::cst::{Cursor, KindTypes};

pub struct CompilationUnit {
language_version: Version,
files: BTreeMap<String, Rc<File>>,
binding_graph: OnceCell<Result<Rc<BindingGraph>, BindingGraphInitializationError>>,
}

impl CompilationUnit {
pub(super) fn new(language_version: Version, files: BTreeMap<String, Rc<File>>) -> Self {
Self {
language_version,
files,
binding_graph: OnceCell::new(),
}
}

pub fn language_version(&self) -> &Version {
&self.language_version
}

pub fn files(&self) -> Vec<Rc<File>> {
self.files.values().cloned().collect()
}

pub fn file(&self, id: &str) -> Option<Rc<File>> {
self.files.get(id).cloned()
}

pub fn binding_graph(&self) -> &Result<Rc<BindingGraph>, BindingGraphInitializationError> {
self.binding_graph.get_or_init(|| {
let resolver = Resolver {
files: self.files.clone(),
};

let mut binding_graph =
create_with_resolver(self.language_version.clone(), Rc::new(resolver))?;

for (id, file) in &self.files {
binding_graph.add_user_file(id, file.create_tree_cursor());
}

Ok(Rc::new(binding_graph))
})
}
}

struct Resolver {
files: BTreeMap<String, Rc<File>>,
}

impl PathResolver<KindTypes> for Resolver {
fn resolve_path(&self, context_path: &str, path_to_resolve: &Cursor) -> Option<String> {
self.files
.get(context_path)?
.resolved_import(path_to_resolve)
.cloned()
}
}
6 changes: 6 additions & 0 deletions crates/codegen/runtime/cargo/crate/src/runtime/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
#[cfg(feature = "__experimental_bindings_api")]
pub mod bindings;
#[cfg(all(
feature = "__experimental_bindings_api",
feature = "__private_compilation_api"
))]
pub mod compilation;
pub mod cst;
pub mod diagnostic;
pub mod parser;
pub mod utils;
Loading
Loading