Skip to content

Commit

Permalink
Introduce configuration-time constants in Forc.toml (FuelLabs#2549)
Browse files Browse the repository at this point in the history
* wip: compile time constants

* Configuration-time constants
  • Loading branch information
sezna authored Aug 18, 2022
1 parent f9ecd20 commit b35fdc8
Show file tree
Hide file tree
Showing 20 changed files with 253 additions and 22 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion forc-pkg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ sway-utils = { version = "0.19.2", path = "../sway-utils" }
toml = "0.5"
tracing = "0.1"
url = { version = "2.2", features = ["serde"] }
walkdir = "2"
vec1 = "1.8.0"
walkdir = "2"
7 changes: 7 additions & 0 deletions forc-pkg/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::{
};

use sway_core::{parse, TreeType};
pub use sway_types::ConfigTimeConstant;
use sway_utils::constants;

type PatchMap = BTreeMap<String, Dependency>;
Expand All @@ -30,6 +31,8 @@ pub struct Manifest {
pub network: Option<Network>,
pub dependencies: Option<BTreeMap<String, Dependency>>,
pub patch: Option<BTreeMap<String, PatchMap>>,
/// A list of [configuration-time constants](https://github.com/FuelLabs/sway/issues/1498).
pub constants: Option<BTreeMap<String, ConfigTimeConstant>>,
build_profile: Option<BTreeMap<String, BuildProfile>>,
}

Expand Down Expand Up @@ -222,6 +225,10 @@ impl ManifestFile {
}
})
}
/// Getter for the config time constants on the manifest.
pub fn config_time_constants(&self) -> BTreeMap<String, ConfigTimeConstant> {
self.constants.as_ref().cloned().unwrap_or_default()
}
}

impl Manifest {
Expand Down
30 changes: 21 additions & 9 deletions forc-pkg/src/pkg.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
lock::Lock,
manifest::{BuildProfile, Dependency, Manifest, ManifestFile},
manifest::{BuildProfile, ConfigTimeConstant, Dependency, Manifest, ManifestFile},
CORE, STD,
};
use anyhow::{anyhow, bail, Context, Error, Result};
Expand All @@ -16,7 +16,7 @@ use petgraph::{
};
use serde::{Deserialize, Serialize};
use std::{
collections::{hash_map, BTreeSet, HashMap, HashSet},
collections::{hash_map, BTreeMap, BTreeSet, HashMap, HashSet},
fmt,
fs::{self, File},
hash::{Hash, Hasher},
Expand Down Expand Up @@ -1479,8 +1479,9 @@ pub fn dependency_namespace(
namespace_map: &HashMap<NodeIx, namespace::Module>,
graph: &Graph,
node: NodeIx,
) -> namespace::Module {
let mut namespace = namespace::Module::default();
constants: BTreeMap<String, ConfigTimeConstant>,
) -> Result<namespace::Module, vec1::Vec1<CompileError>> {
let mut namespace = namespace::Module::default_with_constants(constants)?;

// Add direct dependencies.
let mut core_added = false;
Expand All @@ -1503,7 +1504,7 @@ pub fn dependency_namespace(
}
}

namespace
Ok(namespace)
}

/// Find the `core` dependency (whether direct or transitive) for the given node if it exists.
Expand Down Expand Up @@ -1601,14 +1602,14 @@ pub fn compile(
let entry_path = manifest.entry_path();
let sway_build_config = time_expr!(
"produce `sway_core::BuildConfig`",
sway_build_config(manifest.dir(), &entry_path, build_profile)?
sway_build_config(manifest.dir(), &entry_path, build_profile,)?
);
let silent_mode = build_profile.silent;

// First, compile to an AST. We'll update the namespace and check for JSON ABI output.
let ast_res = time_expr!(
"compile to ast",
compile_ast(manifest, build_profile, namespace)?
compile_ast(manifest, build_profile, namespace,)?
);
match &ast_res {
CompileAstResult::Failure { warnings, errors } => {
Expand Down Expand Up @@ -1925,9 +1926,18 @@ pub fn build(plan: &BuildPlan, profile: &BuildProfile) -> anyhow::Result<(Compil
let mut bytecode = vec![];
let mut tree_type = None;
for &node in &plan.compilation_order {
let dep_namespace = dependency_namespace(&namespace_map, &plan.graph, node);
let pkg = &plan.graph()[node];
let manifest = &plan.manifest_map()[&pkg.id()];
let constants = manifest.config_time_constants();
let dep_namespace = match dependency_namespace(&namespace_map, &plan.graph, node, constants)
{
Ok(o) => o,
Err(errs) => {
print_on_failure(profile.silent, &[], &errs);
bail!("Failed to compile {}", pkg.name);
}
};

let res = compile(pkg, manifest, profile, dep_namespace, &mut source_map)?;
let (compiled, maybe_namespace) = res;
if let Some(namespace) = maybe_namespace {
Expand Down Expand Up @@ -2087,9 +2097,11 @@ pub fn check(
let mut namespace_map = Default::default();
let mut source_map = SourceMap::new();
for (i, &node) in plan.compilation_order.iter().enumerate() {
let dep_namespace = dependency_namespace(&namespace_map, &plan.graph, node);
let pkg = &plan.graph[node];
let manifest = &plan.manifest_map()[&pkg.id()];
let constants = manifest.config_time_constants();
let dep_namespace =
dependency_namespace(&namespace_map, &plan.graph, node, constants).expect("TODO");
let parsed_result = parse(manifest, silent_mode)?;

let parse_program = match &parsed_result.value {
Expand Down
1 change: 1 addition & 0 deletions sway-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ sway-utils = { version = "0.19.2", path = "../sway-utils" }
thiserror = "1.0"
tracing = "0.1"
uint = "0.9"
vec1 = "1.8.0"
8 changes: 4 additions & 4 deletions sway-core/src/convert_parse_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ use {
thiserror::Error,
};

#[derive(Debug)]
#[derive(Debug, Default)]
/// Contains any errors or warnings that were generated during the conversion into the parse tree.
/// Typically these warnings and errors are populated as a side effect in the `From` and `Into`
/// implementations of error types into [ErrorEmitted].
pub struct ErrorContext {
warnings: Vec<CompileWarning>,
errors: Vec<CompileError>,
pub(crate) warnings: Vec<CompileWarning>,
pub(crate) errors: Vec<CompileError>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -790,7 +790,7 @@ fn item_abi_to_abi_declaration(
})
}

fn item_const_to_constant_declaration(
pub(crate) fn item_const_to_constant_declaration(
ec: &mut ErrorContext,
item_const: ItemConst,
) -> Result<ConstantDeclaration, ErrorEmitted> {
Expand Down
3 changes: 3 additions & 0 deletions sway-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,8 @@ pub enum CompileError {
ContinueOutsideLoop { span: Span },
#[error("arguments to \"main()\" are not yet supported. See the issue here: github.com/FuelLabs/sway/issues/845")]
MainArgsNotYetSupported { span: Span },
#[error("Configuration-time constant value is not a constant item.")]
ConfigTimeConstantNotAConstDecl { span: Span },
}

impl std::convert::From<TypeError> for CompileError {
Expand Down Expand Up @@ -1228,6 +1230,7 @@ impl Spanned for CompileError {
BreakOutsideLoop { span } => span.clone(),
ContinueOutsideLoop { span } => span.clone(),
MainArgsNotYetSupported { span } => span.clone(),
ConfigTimeConstantNotAConstDecl { span } => span.clone(),
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions sway-core/src/semantic_analysis/namespace/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use sway_types::{span::Span, Spanned};

use std::sync::Arc;

type SymbolMap = im::OrdMap<Ident, TypedDeclaration>;
type UseSynonyms = im::HashMap<Ident, Vec<Ident>>;
type UseAliases = im::HashMap<String, Ident>;
pub(super) type SymbolMap = im::OrdMap<Ident, TypedDeclaration>;
pub(super) type UseSynonyms = im::HashMap<Ident, Vec<Ident>>;
pub(super) type UseAliases = im::HashMap<String, Ident>;

/// The set of items that exist within some lexical scope via declaration or importing.
#[derive(Clone, Debug, Default, PartialEq)]
Expand Down
123 changes: 118 additions & 5 deletions sway-core/src/semantic_analysis/namespace/module.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
use crate::{
error::*,
parse_tree::Visibility,
semantic_analysis::{ast_node::TypedVariableDeclaration, declaration::VariableMutability},
CompileResult, Ident, TypedDeclaration,
parse_tree::{Declaration, Visibility},
semantic_analysis::{
ast_node::{TypedAstNode, TypedAstNodeContent, TypedVariableDeclaration},
declaration::VariableMutability,
TypeCheckContext,
},
AstNode, AstNodeContent, CompileResult, Ident, Namespace, TypedDeclaration,
};

use super::{items::Items, root::Root, ModuleName, Path};
use super::{
items::{Items, SymbolMap},
root::Root,
ModuleName, Path,
};

use sway_types::{span::Span, Spanned};
use std::collections::BTreeMap;
use sway_ast::ItemConst;
use sway_parse::{handler::Handler, lex, Parser};
use sway_types::{span::Span, ConfigTimeConstant, Spanned};

/// A single `Module` within a Sway project.
///
Expand All @@ -32,6 +43,108 @@ pub struct Module {
}

impl Module {
pub fn default_with_constants(
constants: BTreeMap<String, ConfigTimeConstant>,
) -> Result<Self, vec1::Vec1<CompileError>> {
let res = Module::default_with_constants_inner(constants);
match res.value {
Some(x) => Ok(x),
None => {
let mut errs = res.errors;
// it is an invariant that if `.value` is `None` then there's at least one
// error
assert!(!errs.is_empty());
let first_err = errs.pop().unwrap();
let mut errs_1 = vec1::vec1![first_err];
errs_1.append(&mut errs);
Err(errs_1)
}
}
}

fn default_with_constants_inner(
constants: BTreeMap<String, ConfigTimeConstant>,
) -> CompileResult<Self> {
// it would be nice to one day maintain a span from the manifest file, but
// we don't keep that around so we just use the span from the generated const decl instead.
let mut compiled_constants: SymbolMap = Default::default();
let mut ec: crate::convert_parse_tree::ErrorContext = Default::default();
let ec = &mut ec;
let mut warnings = vec![];
let mut errors = vec![];
// this for loop performs a miniature compilation of each const item in the config
for (name, ConfigTimeConstant { r#type, value }) in constants.into_iter() {
// parser config
let const_item = format!("const {name}: {type} = {value};");
let const_item_len = const_item.len();
let input_arc = std::sync::Arc::from(const_item);
let token_stream = lex(&input_arc, 0, const_item_len, None).unwrap();
let handler = Handler::default();
let mut parser = Parser::new(&token_stream, &handler);
// perform the parse
let const_item: ItemConst = match parser.parse() {
Ok(o) => o,
Err(_emit_signal) => {
// if an error was emitted, grab errors from the error context
errors.append(&mut ec.errors.clone());
warnings.append(&mut ec.warnings.clone());

return err(warnings, errors);
}
};
let const_item_span = const_item.span().clone();

// perform the conversions from parser code to parse tree types
let name = const_item.name.clone();
// convert to const decl
let const_decl =
match crate::convert_parse_tree::item_const_to_constant_declaration(ec, const_item)
{
Ok(o) => o,
Err(_emit_signal) => {
// if an error was emitted, grab errors from the error context
errors.append(&mut ec.errors.clone());
warnings.append(&mut ec.warnings.clone());

return err(warnings, errors);
}
};
let ast_node = AstNode {
content: AstNodeContent::Declaration(Declaration::ConstantDeclaration(const_decl)),
span: const_item_span.clone(),
};
let mut ns = Namespace::init_root(Default::default());
let type_check_ctx = TypeCheckContext::from_root(&mut ns);
let typed_node =
TypedAstNode::type_check(type_check_ctx, ast_node).unwrap(&mut vec![], &mut vec![]);
// get the decl out of the typed node:
// we know as an invariant this must be a const decl, as we hardcoded a const decl in
// the above `format!`. if it isn't we report an
// error that only constant items are alowed, defensive programming etc...
let typed_decl = match typed_node.content {
TypedAstNodeContent::Declaration(decl) => decl,
_ => {
errors.push(CompileError::ConfigTimeConstantNotAConstDecl {
span: const_item_span,
});
return err(warnings, errors);
}
};
compiled_constants.insert(name, typed_decl);
}
ok(
Self {
items: Items {
symbols: compiled_constants,
..Default::default()
},
..Default::default()
},
warnings,
errors,
)
}

/// Immutable access to this module's submodules.
pub fn submodules(&self) -> &im::OrdMap<ModuleName, Module> {
&self.submodules
Expand Down
5 changes: 5 additions & 0 deletions sway-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ where
}
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ConfigTimeConstant {
pub r#type: String,
pub value: String,
}
impl AsRef<PathBuf> for Source {
fn as_ref(&self) -> &PathBuf {
&self.path
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[package]]
name = 'config_time_constants'
source = 'root'
dependencies = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
license = "Apache-2.0"
name = "config_time_constants"
entry = "main.sw"
implicit-std = false


[constants]
# should fail because this is an invalid b256
some_contract_addr = { type = "b256", value = "0x58cb6ee759d9be0c0f78d3ef24e1b59300c625b3c61999967366dbbebad31c" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[
{
"inputs": [],
"name": "main",
"outputs": [
{
"components": null,
"name": "",
"type": "u64",
"typeArguments": null
}
],
"type": "function"
}
]
Loading

0 comments on commit b35fdc8

Please sign in to comment.