Skip to content

Commit

Permalink
Add standard library prelude (FuelLabs#2769)
Browse files Browse the repository at this point in the history
* add standard library prelude

* prelude: no shadow errors

* glob imports never result in shadow error

* std prelude: address review comments

Co-authored-by: Mohammad Fawaz <[email protected]>
  • Loading branch information
Centril and mohammadfawaz authored Sep 14, 2022
1 parent 9aa7b8b commit 82a554e
Show file tree
Hide file tree
Showing 21 changed files with 249 additions and 32 deletions.
1 change: 1 addition & 0 deletions forc-pkg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ pub use pkg::*;

const CORE: &str = "core";
const STD: &str = "std";
const PRELUDE: &str = "prelude";
29 changes: 26 additions & 3 deletions forc-pkg/src/pkg.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
lock::Lock,
manifest::{BuildProfile, ConfigTimeConstant, Dependency, Manifest, ManifestFile},
CORE, STD,
CORE, PRELUDE, STD,
};
use anyhow::{anyhow, bail, Context, Error, Result};
use forc_util::{
Expand Down Expand Up @@ -29,7 +29,7 @@ use sway_core::{
semantic_analysis::namespace, source_map::SourceMap, BytecodeCompilationResult,
CompileAstResult, CompileError, CompileResult, ParseProgram, TreeType,
};
use sway_types::{JsonABIProgram, JsonTypeApplication, JsonTypeDeclaration};
use sway_types::{Ident, JsonABIProgram, JsonTypeApplication, JsonTypeDeclaration};
use sway_utils::constants;
use tracing::{info, warn};
use url::Url;
Expand Down Expand Up @@ -1483,6 +1483,9 @@ pub fn sway_build_config(
///
/// This function ensures that if `core` exists in the graph (the vastly common case) it is also
/// present within the namespace. This is a necessity for operators to work for example.
///
/// This function also ensures that if `std` exists in the graph,
/// then the std prelude will also be added.
pub fn dependency_namespace(
namespace_map: &HashMap<NodeIx, namespace::Module>,
graph: &Graph,
Expand Down Expand Up @@ -1512,9 +1515,29 @@ pub fn dependency_namespace(
}
}

if has_std_dep(graph, node) {
namespace.star_import_with_reexports(&[STD, PRELUDE].map(Ident::new_no_span), &[]);
}

Ok(namespace)
}

/// Find the `std` dependency, if it is a direct one, of the given node.
fn has_std_dep(graph: &Graph, node: NodeIx) -> bool {
// If we are `std`, do nothing.
let pkg = &graph[node];
if pkg.name == STD {
return false;
}

// If we have `std` as a direct dep, use it.
graph.edges_directed(node, Direction::Outgoing).any(|edge| {
let dep_node = edge.target();
let dep = &graph[dep_node];
matches!(&dep.name[..], STD)
})
}

/// Find the `core` dependency (whether direct or transitive) for the given node if it exists.
fn find_core_dep(graph: &Graph, node: NodeIx) -> Option<NodeIx> {
// If we are `core`, do nothing.
Expand All @@ -1531,7 +1554,7 @@ fn find_core_dep(graph: &Graph, node: NodeIx) -> Option<NodeIx> {
match &dep.name[..] {
CORE => return Some(dep_node),
STD => maybe_std = Some(dep_node),
_ => (),
_ => {}
}
}

Expand Down
3 changes: 0 additions & 3 deletions sway-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -917,8 +917,6 @@ pub enum CompileError {
ShadowsOtherSymbol { name: Ident },
#[error("The name \"{name}\" is already used for a generic parameter in this scope.")]
GenericShadowsGeneric { name: Ident },
#[error("The name \"{name}\" imported through `*` shadows another symbol with the same name.")]
StarImportShadowsOtherSymbol { name: Ident },
#[error(
"Match expression arm has mismatched types.\n\
expected: {expected}\n\
Expand Down Expand Up @@ -1199,7 +1197,6 @@ impl Spanned for CompileError {
ArrayOutOfBounds { span, .. } => span.clone(),
ShadowsOtherSymbol { name } => name.span(),
GenericShadowsGeneric { name } => name.span(),
StarImportShadowsOtherSymbol { name } => name.span(),
MatchWrongType { span, .. } => span.clone(),
MatchExpressionNonExhaustive { span, .. } => span.clone(),
MatchStructPatternMissingFields { span, .. } => span.clone(),
Expand Down
15 changes: 13 additions & 2 deletions sway-core/src/semantic_analysis/namespace/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@ use sway_types::{span::Span, Spanned};

use std::sync::Arc;

/// Is this a glob (`use foo::*;`) import?
#[derive(Clone, Copy, PartialEq, Debug)]
pub(crate) enum GlobImport {
Yes,
No,
}

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

/// The set of items that exist within some lexical scope via declaration or importing.
Expand Down Expand Up @@ -132,6 +139,7 @@ impl Items {
let new_prefixes = if trait_name.prefixes.is_empty() {
self.use_synonyms
.get(&trait_name.suffix)
.map(|us| &us.0)
.unwrap_or(&trait_name.prefixes)
.clone()
} else {
Expand All @@ -155,7 +163,10 @@ impl Items {
}

pub(crate) fn get_canonical_path(&self, symbol: &Ident) -> &[Ident] {
self.use_synonyms.get(symbol).map(|v| &v[..]).unwrap_or(&[])
self.use_synonyms
.get(symbol)
.map(|v| &v.0[..])
.unwrap_or(&[])
}

pub(crate) fn has_storage_declared(&self) -> bool {
Expand Down
87 changes: 69 additions & 18 deletions sway-core/src/semantic_analysis/namespace/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
};

use super::{
items::{Items, SymbolMap},
items::{GlobImport, Items, SymbolMap},
root::Root,
ModuleName, Path,
};
Expand Down Expand Up @@ -213,6 +213,7 @@ impl Module {
warnings,
errors
);

let implemented_traits = src_ns.implemented_traits.clone();
let mut symbols = vec![];
for (symbol, decl) in src_ns.symbols.iter() {
Expand All @@ -230,13 +231,65 @@ impl Module {
let dst_ns = &mut self[dst];
dst_ns.implemented_traits.extend(implemented_traits);
for symbol in symbols {
if dst_ns.use_synonyms.contains_key(&symbol) {
errors.push(CompileError::StarImportShadowsOtherSymbol {
name: symbol.clone(),
});
dst_ns
.use_synonyms
.insert(symbol, (src.to_vec(), GlobImport::Yes));
}

ok((), warnings, errors)
}

/// Given a path to a `src` module, create synonyms to every symbol in that module to the given
/// `dst` module.
///
/// This is used when an import path contains an asterisk.
///
/// Paths are assumed to be relative to `self`.
pub fn star_import_with_reexports(&mut self, src: &Path, dst: &Path) -> CompileResult<()> {
let mut warnings = vec![];
let mut errors = vec![];
let src_ns = check!(
self.check_submodule(src),
return err(warnings, errors),
warnings,
errors
);

let implemented_traits = src_ns.implemented_traits.clone();
let use_synonyms = src_ns.use_synonyms.clone();
let mut symbols = src_ns.use_synonyms.keys().cloned().collect::<Vec<_>>();
for (symbol, decl) in src_ns.symbols.iter() {
let visibility = check!(
decl.visibility(),
return err(warnings, errors),
warnings,
errors
);
if visibility == Visibility::Public {
symbols.push(symbol.clone());
}
dst_ns.use_synonyms.insert(symbol, src.to_vec());
}

let dst_ns = &mut self[dst];
dst_ns.implemented_traits.extend(implemented_traits);
let mut try_add = |symbol, path| {
dst_ns.use_synonyms.insert(symbol, (path, GlobImport::Yes));
};

for symbol in symbols {
try_add(symbol, src.to_vec());
}
for (symbol, (mod_path, _)) in use_synonyms {
// N.B. We had a path like `::bar::baz`, which makes the module `bar` "crate-relative".
// Given that `bar`'s "crate" is `foo`, we'll need `foo::bar::baz` outside of it.
//
// FIXME(Centril, #2780): Seems like the compiler has no way of
// distinguishing between external and crate-relative paths?
let mut src = src[..1].to_vec();
src.extend(mod_path);
try_add(symbol, src);
}

ok((), warnings, errors)
}

Expand Down Expand Up @@ -304,24 +357,22 @@ impl Module {
impls_to_insert.append(&mut res);
// no matter what, import it this way though.
let dst_ns = &mut self[dst];
let mut add_synonym = |name| {
if let Some((_, GlobImport::No)) = dst_ns.use_synonyms.get(name) {
errors.push(CompileError::ShadowsOtherSymbol { name: name.clone() });
}
dst_ns
.use_synonyms
.insert(name.clone(), (src.to_vec(), GlobImport::No));
};
match alias {
Some(alias) => {
if dst_ns.use_synonyms.contains_key(&alias) {
errors.push(CompileError::ShadowsOtherSymbol {
name: alias.clone(),
});
}
dst_ns.use_synonyms.insert(alias.clone(), src.to_vec());
add_synonym(&alias);
dst_ns
.use_aliases
.insert(alias.as_str().to_string(), item.clone());
}
None => {
if dst_ns.use_synonyms.contains_key(item) {
errors.push(CompileError::ShadowsOtherSymbol { name: item.clone() });
}
dst_ns.use_synonyms.insert(item.clone(), src.to_vec());
}
None => add_synonym(item),
};
}
None => {
Expand Down
2 changes: 1 addition & 1 deletion sway-core/src/semantic_analysis/namespace/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl Root {
.get(symbol.as_str())
.unwrap_or(symbol);
match module.use_synonyms.get(symbol) {
Some(src_path) if mod_path != src_path => {
Some((src_path, _)) if mod_path != src_path => {
self.resolve_symbol(src_path, true_symbol)
}
_ => CompileResult::from(module.check_symbol(true_symbol)),
Expand Down
1 change: 1 addition & 0 deletions sway-lib-std/src/lib.sw
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ dep flags;
dep u128;
dep u256;
dep vec;
dep prelude;

use core::*;
13 changes: 13 additions & 0 deletions sway-lib-std/src/prelude.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
library prelude;

//! Defines the Sway standard library prelude.
//! The prelude consists of implicitly available items,
//! for which `use` is not required.
use ::address::Address;
use ::contract_id::ContractId;
use ::identity::Identity;
use ::vec::Vec;
use ::assert::assert;
use ::revert::require;
use ::revert::revert;
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ use ::bar::{Bar1, Bar1 as Foo};
// This is not okay because symbol `Foo` is already reserved
use ::bar::Bar2 as Foo;

// This is not okay
// This is not okay
use ::bar::{Bar2, Bar2};

// This not okay now because Bar1 and Bar2 have already been imported
// This okay. Although Bar1 + Bar2 have already been imported, glob imports don't cause shadow errors.
use ::bar::*;

fn main() -> bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,3 @@ category = "fail"

# check: $()The name "Foo" shadows another symbol with the same name.
# check: $()The name "Bar2" shadows another symbol with the same name.
# check: $()The name "Bar1" imported through `*` shadows another symbol with the same name.
# check: $()The name "Bar2" imported through `*` shadows another symbol with the same name.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ fn main() -> u32 {
let bal = asm() { bal };

let is = asm() { is };

let ret = asm() { ret };

let retl = asm() { retl };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[package]]
name = 'core'
source = 'path+from-root-24E795B40E4736EB'
dependencies = []

[[package]]
name = 'prelude_access'
source = 'root'
dependencies = ['std']

[[package]]
name = 'std'
source = 'path+from-root-24E795B40E4736EB'
dependencies = ['core']
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "prelude_access"

[dependencies]
std = { path = "../../../../../../../sway-lib-std" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"functions": [
{
"inputs": [],
"name": "main",
"output": {
"name": "",
"type": 0,
"typeArguments": null
}
}
],
"loggedTypes": [],
"types": [
{
"components": [],
"type": "()",
"typeId": 0,
"typeParameters": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
script;

struct A {
addr: Address,
}

// Ensure shadowing is allowed.
use std::address::Address;

fn main() {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
category = "run"
expected_result = { action = "return", value = 0 }
validate_abi = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[package]]
name = 'core'
source = 'path+from-root-B3B7F7F1BCA7D931'
dependencies = []

[[package]]
name = 'prelude_access2'
source = 'root'
dependencies = ['std']

[[package]]
name = 'std'
source = 'path+from-root-B3B7F7F1BCA7D931'
dependencies = ['core']
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "prelude_access2"

[dependencies]
std = { path = "../../../../../../../sway-lib-std" }
Loading

0 comments on commit 82a554e

Please sign in to comment.