Skip to content

Commit

Permalink
Merge branch 'master' into josh/async_io
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaBatty authored Dec 12, 2023
2 parents cf23169 + 9e27e8f commit ff157c7
Show file tree
Hide file tree
Showing 27 changed files with 386 additions and 6 deletions.
251 changes: 246 additions & 5 deletions sway-core/src/semantic_analysis/module.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,215 @@
use sway_error::handler::{ErrorEmitted, Handler};
use std::{collections::HashMap, fmt::Display, fs};

use graph_cycles::Cycles;
use sway_error::{
error::CompileError,
handler::{ErrorEmitted, Handler},
};

use crate::{
engine_threading::DebugWithEngines,
language::{parsed::*, ty, ModName},
semantic_analysis::*,
Engines,
};

#[derive(Clone, Debug)]
pub struct ModuleDepGraphEdge();

impl Display for ModuleDepGraphEdge {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "")
}
}

pub type ModuleDepGraphNodeId = petgraph::graph::NodeIndex;

#[derive(Clone, Debug)]
pub enum ModuleDepGraphNode {
Module {},
Submodule { name: ModName },
}

impl DebugWithEngines for ModuleDepGraphNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, _engines: &Engines) -> std::fmt::Result {
let text = match self {
ModuleDepGraphNode::Module { .. } => {
format!("{:?}", "Root module")
}
ModuleDepGraphNode::Submodule { name: mod_name } => {
format!("{:?}", mod_name.as_str())
}
};
f.write_str(&text)
}
}

// Represents an ordered graph between declaration id indexes.
pub type ModuleDepNodeGraph = petgraph::graph::DiGraph<ModuleDepGraphNode, ModuleDepGraphEdge>;

pub struct ModuleDepGraph {
dep_graph: ModuleDepNodeGraph,
root: ModuleDepGraphNodeId,
node_name_map: HashMap<String, ModuleDepGraphNodeId>,
}

pub type ModuleEvaluationOrder = Vec<ModName>;

impl ModuleDepGraph {
pub(crate) fn new() -> Self {
Self {
dep_graph: Default::default(),
root: Default::default(),
node_name_map: Default::default(),
}
}

pub fn add_node(&mut self, node: ModuleDepGraphNode) -> ModuleDepGraphNodeId {
let node_id = self.dep_graph.add_node(node.clone());
match node {
ModuleDepGraphNode::Module {} => {}
ModuleDepGraphNode::Submodule { name: mod_name } => {
self.node_name_map.insert(mod_name.to_string(), node_id);
}
};
node_id
}

pub fn add_root_node(&mut self) -> ModuleDepGraphNodeId {
self.root = self.add_node(super::module::ModuleDepGraphNode::Module {});
self.root
}

fn get_node_id_for_module(
&self,
mod_name: &sway_types::BaseIdent,
) -> Option<ModuleDepGraphNodeId> {
self.node_name_map.get(&mod_name.to_string()).copied()
}

/// Prints out GraphViz DOT format for the dependency graph.
#[allow(dead_code)]
pub(crate) fn visualize(&self, engines: &Engines, print_graph: Option<String>) {
if let Some(graph_path) = print_graph {
use petgraph::dot::{Config, Dot};
let string_graph = self.dep_graph.filter_map(
|_idx, node| Some(format!("{:?}", engines.help_out(node))),
|_idx, edge| Some(format!("{}", edge)),
);

let output = format!(
"{:?}",
Dot::with_attr_getters(
&string_graph,
&[Config::NodeNoLabel, Config::EdgeNoLabel],
&|_, er| format!("label = {:?}", er.weight()),
&|_, nr| {
let _node = &self.dep_graph[nr.0];
let shape = "";
let url = "".to_string();
format!("{shape} label = {:?} {url}", nr.1)
},
)
);

if graph_path.is_empty() {
tracing::info!("{output}");
} else {
let result = fs::write(graph_path.clone(), output);
if let Some(error) = result.err() {
tracing::error!(
"There was an issue while outputing module dep analysis graph to path {graph_path:?}\n{error}"
);
}
}
}
}

/// Computes the ordered list by dependency, which will be used for evaluating the modules
/// in the correct order. We run a topological sort and cycle finding algorithm to check
/// for unsupported cyclic dependency cases.
pub(crate) fn compute_order(
&self,
handler: &Handler,
) -> Result<ModuleEvaluationOrder, ErrorEmitted> {
// Check for dependency cycles in the graph by running the Johnson's algorithm.
let cycles = self.dep_graph.cycles();
if !cycles.is_empty() {
let mut modules = Vec::new();
for cycle in cycles.first().unwrap() {
let node = self.dep_graph.node_weight(*cycle).unwrap();
match node {
ModuleDepGraphNode::Module {} => unreachable!(),
ModuleDepGraphNode::Submodule { name } => modules.push(name.clone()),
};
}
return Err(handler.emit_err(CompileError::ModuleDepGraphCyclicReference { modules }));
}

// Do a topological sort to compute an ordered list of nodes.
let sorted = match petgraph::algo::toposort(&self.dep_graph, None) {
Ok(value) => value,
// If we were not able to toposort, this means there is likely a cycle in the module dependency graph,
// which we already handled above, so lets just return an empty evaluation order instead of panic'ing.
// module dependencies, which we have already reported.
Err(_) => return Err(handler.emit_err(CompileError::ModuleDepGraphEvaluationError {})),
};

let sorted = sorted
.into_iter()
.filter_map(|node_index| {
let node = self.dep_graph.node_weight(node_index);
match node {
Some(node) => match node {
ModuleDepGraphNode::Module {} => None, // root module
ModuleDepGraphNode::Submodule { name: mod_name } => Some(mod_name.clone()),
},
None => None,
}
})
.rev()
.collect::<Vec<_>>();

Ok(sorted)
}
}

impl ty::TyModule {
/// Analyzes the given parsed module to produce a dependency graph.
pub fn analyze(
handler: &Handler,
parsed: &ParseModule,
) -> Result<ModuleDepGraph, ErrorEmitted> {
let mut dep_graph = ModuleDepGraph::new();
dep_graph.add_root_node();

let ParseModule { submodules, .. } = parsed;

// Create graph nodes for each submodule.
submodules.iter().for_each(|(name, _submodule)| {
let sub_mod_node =
dep_graph.add_node(ModuleDepGraphNode::Submodule { name: name.clone() });
dep_graph
.dep_graph
.add_edge(dep_graph.root, sub_mod_node, ModuleDepGraphEdge {});
});

// Analyze submodules first in order of declaration.
submodules.iter().for_each(|(name, submodule)| {
let _ = ty::TySubmodule::analyze(handler, &mut dep_graph, name.clone(), submodule);
});

Ok(dep_graph)
}

/// Type-check the given parsed module to produce a typed module.
///
/// Recursively type-checks submodules first.
pub fn type_check(
handler: &Handler,
mut ctx: TypeCheckContext,
parsed: &ParseModule,
module_eval_order: ModuleEvaluationOrder,
) -> Result<Self, ErrorEmitted> {
let ParseModule {
submodules,
Expand All @@ -23,10 +220,14 @@ impl ty::TyModule {
..
} = parsed;

// Type-check submodules first in order of declaration.
let submodules_res = submodules
// Type-check submodules first in order of evaluation previously computed by the dependency graph.
let submodules_res = module_eval_order
.iter()
.map(|(name, submodule)| {
.map(|eval_mod_name| {
let (name, submodule) = submodules
.iter()
.find(|(submod_name, _submodule)| eval_mod_name == submod_name)
.unwrap();
Ok((
name.clone(),
ty::TySubmodule::type_check(handler, ctx.by_ref(), name.clone(), submodule)?,
Expand Down Expand Up @@ -70,6 +271,43 @@ impl ty::TyModule {
}

impl ty::TySubmodule {
pub fn analyze(
_handler: &Handler,
module_dep_graph: &mut ModuleDepGraph,
mod_name: ModName,
submodule: &ParseSubmodule,
) -> Result<(), ErrorEmitted> {
let ParseSubmodule { module, .. } = submodule;
let sub_mod_node = module_dep_graph.get_node_id_for_module(&mod_name).unwrap();
for node in module.tree.root_nodes.iter() {
match &node.content {
AstNodeContent::UseStatement(use_stmt) => {
if let Some(use_mod_ident) = use_stmt.call_path.first() {
if let Some(mod_name_node) =
module_dep_graph.get_node_id_for_module(use_mod_ident)
{
// Prevent adding edge loops between the same node as that will throw off
// the cyclic dependency analysis.
if sub_mod_node != mod_name_node {
module_dep_graph.dep_graph.add_edge(
sub_mod_node,
mod_name_node,
ModuleDepGraphEdge {},
);
}
}
}
}
AstNodeContent::Declaration(_) => {}
AstNodeContent::Expression(_) => {}
AstNodeContent::ImplicitReturnExpression(_) => {}
AstNodeContent::IncludeStatement(_) => {}
AstNodeContent::Error(_, _) => {}
}
}
Ok(())
}

pub fn type_check(
handler: &Handler,
parent_ctx: TypeCheckContext,
Expand All @@ -81,8 +319,11 @@ impl ty::TySubmodule {
mod_name_span,
visibility,
} = submodule;
let modules_dep_graph = ty::TyModule::analyze(handler, module)?;
let module_eval_order = modules_dep_graph.compute_order(handler)?;
parent_ctx.enter_submodule(mod_name, *visibility, module.span.clone(), |submod_ctx| {
let module_res = ty::TyModule::type_check(handler, submod_ctx, module);
let module_res =
ty::TyModule::type_check(handler, submod_ctx, module, module_eval_order);
module_res.map(|module| ty::TySubmodule {
module,
mod_name_span: mod_name_span.clone(),
Expand Down
7 changes: 6 additions & 1 deletion sway-core/src/semantic_analysis/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ impl TyProgram {
let ctx =
TypeCheckContext::from_root(&mut namespace, engines).with_kind(parsed.kind.clone());
let ParseProgram { root, kind } = parsed;
ty::TyModule::type_check(handler, ctx, root).and_then(|root| {

// Analyze the dependency order for the submodules.
let modules_dep_graph = ty::TyModule::analyze(handler, root)?;
let module_eval_order = modules_dep_graph.compute_order(handler)?;

ty::TyModule::type_check(handler, ctx, root, module_eval_order).and_then(|root| {
let res = Self::validate_root(handler, engines, &root, kind.clone(), package_name);
res.map(|(kind, declarations, configurables)| Self {
kind,
Expand Down
12 changes: 12 additions & 0 deletions sway-error/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ impl fmt::Display for InterfaceName {
// this type.
#[derive(Error, Debug, Clone, PartialEq, Eq, Hash)]
pub enum CompileError {
#[error(
"There was an error while evaluating the evaluation order for the module dependency graph."
)]
ModuleDepGraphEvaluationError {},
#[error("A cyclic reference was found between the modules: {}.",
modules.iter().map(|ident| ident.as_str().to_string())
.collect::<Vec<_>>()
.join(", "))]
ModuleDepGraphCyclicReference { modules: Vec<BaseIdent> },

#[error("Variable \"{var_name}\" does not exist in this scope.")]
UnknownVariable { var_name: Ident, span: Span },
#[error("Identifier \"{name}\" was used as a variable, but it is actually a {what_it_is}.")]
Expand Down Expand Up @@ -756,6 +766,8 @@ impl Spanned for CompileError {
fn span(&self) -> Span {
use CompileError::*;
match self {
ModuleDepGraphEvaluationError { .. } => Span::dummy(),
ModuleDepGraphCyclicReference { .. } => Span::dummy(),
UnknownVariable { span, .. } => span.clone(),
NotAVariable { span, .. } => span.clone(),
Unimplemented(_, span) => span.clone(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[package]]
name = "module_cyclic_reference"
source = "member"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "lib.sw"
license = "Apache-2.0"
name = "module_cyclic_reference"
implicit-std = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
library;

use ::b::*;

pub fn a() { b(); }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
library;

use ::a::*;

pub fn b() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
library;

mod a;
mod b;

fn main() -> u32 {
a::a();
0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
category = "fail"

# check: $()A cyclic reference was found between the modules: a, b.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[package]]
name = "module_dep"
source = "member"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "lib.sw"
license = "Apache-2.0"
name = "module_dep"
implicit-std = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
library;

use ::b::*;

pub fn a() { b(); }
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library;

pub fn b() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
library;

mod a;
mod b;

fn main() -> u32 {
1
}
Loading

0 comments on commit ff157c7

Please sign in to comment.