From e70cb773f873914ec3c9bc24bf82aa67e8af900a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Matos?= Date: Fri, 1 Dec 2023 17:51:45 +0000 Subject: [PATCH] Introduce recursive analysis via cycle finding in dependency graph. (#5210) ## Description This PR introduces a new recursive analysis pass, which is implemented for for regular and impl traits functions. It works computing a dependency graph, and then running the Johnson graph cycle finding algorithm in the graph. Regular functions still use the old recursive analysis pass due to issues with disabling the old dependency pass. When disabling the old recursion analysis code for regular functions, we get a stack overflow in the existing dependency map building code, which is shared with the existing type recursion analysis code. Closes https://github.com/FuelLabs/sway/issues/4954. ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. Co-authored-by: Joshua Batty Co-authored-by: Sophie Dankel <47993817+sdankel@users.noreply.github.com> --- Cargo.lock | 13 + sway-core/Cargo.toml | 1 + sway-core/src/language/ty/ast_node.rs | 43 +++ .../src/language/ty/declaration/trait.rs | 3 +- .../ty/expression/expression_variant.rs | 8 +- sway-core/src/language/ty/module.rs | 20 +- sway-core/src/language/ty/program.rs | 8 + sway-core/src/lib.rs | 11 +- .../ast_node/declaration/function.rs | 45 +++- .../ast_node/declaration/impl_trait.rs | 9 +- .../semantic_analysis/type_check_analysis.rs | 247 +++++++++++++++--- .../should_fail/impl_self_recursive/Forc.lock | 3 + .../should_fail/impl_self_recursive/Forc.toml | 8 + .../impl_self_recursive/src/main.sw | 18 ++ .../should_fail/impl_self_recursive/test.toml | 13 + 15 files changed, 394 insertions(+), 56 deletions(-) create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/test.toml diff --git a/Cargo.lock b/Cargo.lock index 27a3a054a14..e656eeaa42e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", + "getrandom 0.2.10", "once_cell", "version_check", "zerocopy", @@ -2806,6 +2807,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "graph-cycles" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6ad932c6dd3cfaf16b66754a42f87bbeefd591530c4b6a8334270a7df3e853" +dependencies = [ + "ahash", + "petgraph", + "thiserror", +] + [[package]] name = "graphql-parser" version = "0.4.0" @@ -6034,6 +6046,7 @@ dependencies = [ "fuel-etk-dasm", "fuel-etk-ops", "fuel-vm", + "graph-cycles", "hashbrown 0.13.2", "hex", "im", diff --git a/sway-core/Cargo.toml b/sway-core/Cargo.toml index f8a14617dc5..882f03b2dce 100644 --- a/sway-core/Cargo.toml +++ b/sway-core/Cargo.toml @@ -21,6 +21,7 @@ etk-dasm = { package = "fuel-etk-dasm", version = "0.3.1-dev" } etk-ops = { package = "fuel-etk-ops", version = "0.3.1-dev" } fuel-abi-types = "0.1" fuel-vm = { workspace = true, features = ["serde"] } +graph-cycles = "0.1.0" hashbrown = "0.13.1" hex = { version = "0.4", optional = true } im = "15.0" diff --git a/sway-core/src/language/ty/ast_node.rs b/sway-core/src/language/ty/ast_node.rs index 5091702f562..64026e7fd37 100644 --- a/sway-core/src/language/ty/ast_node.rs +++ b/sway-core/src/language/ty/ast_node.rs @@ -399,6 +399,49 @@ impl TyAstNode { TyAstNodeContent::SideEffect(_) | TyAstNodeContent::Error(_, _) => {} } } + + pub(crate) fn check_recursive( + &self, + engines: &Engines, + handler: &Handler, + ) -> Result<(), ErrorEmitted> { + handler.scope(|handler| { + match &self.content { + TyAstNodeContent::Declaration(node) => match node { + TyDecl::VariableDecl(_decl) => {} + TyDecl::ConstantDecl(_decl) => {} + TyDecl::TraitTypeDecl(_) => {} + TyDecl::FunctionDecl(decl) => { + let fn_decl_id = decl.decl_id; + let mut ctx = TypeCheckAnalysisContext::new(engines); + let _ = fn_decl_id.type_check_analyze(handler, &mut ctx); + let _ = ctx.check_recursive_calls(handler); + } + TyDecl::ImplTrait(decl) => { + let decl = engines.de().get(&decl.decl_id); + for item in decl.items.iter() { + let mut ctx = TypeCheckAnalysisContext::new(engines); + let _ = item.type_check_analyze(handler, &mut ctx); + let _ = ctx.check_recursive_calls(handler); + } + } + TyDecl::AbiDecl(_) + | TyDecl::GenericTypeForFunctionScope(_) + | TyDecl::ErrorRecovery(_, _) + | TyDecl::StorageDecl(_) + | TyDecl::TraitDecl(_) + | TyDecl::StructDecl(_) + | TyDecl::EnumDecl(_) + | TyDecl::EnumVariantDecl(_) + | TyDecl::TypeAliasDecl(_) => {} + }, + TyAstNodeContent::Expression(_node) => {} + TyAstNodeContent::ImplicitReturnExpression(_node) => {} + TyAstNodeContent::SideEffect(_) | TyAstNodeContent::Error(_, _) => {} + }; + Ok(()) + }) + } } #[derive(Clone, Debug)] diff --git a/sway-core/src/language/ty/declaration/trait.rs b/sway-core/src/language/ty/declaration/trait.rs index ceef149ff81..780dc560eb6 100644 --- a/sway-core/src/language/ty/declaration/trait.rs +++ b/sway-core/src/language/ty/declaration/trait.rs @@ -188,8 +188,7 @@ impl TypeCheckAnalysis for TyTraitItem { match self { TyTraitItem::Fn(node) => { - let item_fn = decl_engine.get_function(node); - item_fn.type_check_analyze(handler, ctx)?; + node.type_check_analyze(handler, ctx)?; } TyTraitItem::Constant(node) => { let item_const = decl_engine.get_constant(node); diff --git a/sway-core/src/language/ty/expression/expression_variant.rs b/sway-core/src/language/ty/expression/expression_variant.rs index 778b5a52c85..a36f4769c00 100644 --- a/sway-core/src/language/ty/expression/expression_variant.rs +++ b/sway-core/src/language/ty/expression/expression_variant.rs @@ -908,12 +908,18 @@ impl TypeCheckAnalysis for TyExpressionVariant { match self { TyExpressionVariant::Literal(_) => {} TyExpressionVariant::FunctionApplication { fn_ref, .. } => { - let fn_node = ctx.get_node_from_impl_trait_fn_ref_app(fn_ref); + let fn_decl_id = ctx.get_normalized_fn_node_id(fn_ref.id()); + + let fn_node = ctx.get_node_for_fn_decl(&fn_decl_id); if let Some(fn_node) = fn_node { ctx.add_edge_from_current( fn_node, TyNodeDepGraphEdge(TyNodeDepGraphEdgeInfo::FnApp), ); + + if !ctx.node_stack.contains(&fn_node) { + let _ = fn_decl_id.type_check_analyze(handler, ctx); + } } } TyExpressionVariant::LazyOperator { lhs, rhs, .. } => { diff --git a/sway-core/src/language/ty/module.rs b/sway-core/src/language/ty/module.rs index 6abcefe1c95..3a74293b493 100644 --- a/sway-core/src/language/ty/module.rs +++ b/sway-core/src/language/ty/module.rs @@ -1,4 +1,4 @@ -use sway_error::handler::Handler; +use sway_error::handler::{ErrorEmitted, Handler}; use sway_types::Span; use crate::{ @@ -86,6 +86,24 @@ impl TyModule { node.check_deprecated(engines, handler, allow_deprecated); } } + + pub(crate) fn check_recursive( + &self, + engines: &Engines, + handler: &Handler, + ) -> Result<(), ErrorEmitted> { + handler.scope(|handler| { + for (_, submodule) in self.submodules.iter() { + let _ = submodule.module.check_recursive(engines, handler); + } + + for node in self.all_nodes.iter() { + let _ = node.check_recursive(engines, handler); + } + + Ok(()) + }) + } } impl<'module> Iterator for SubmodulesRecursive<'module> { diff --git a/sway-core/src/language/ty/program.rs b/sway-core/src/language/ty/program.rs index 670fd5f913d..da5a19da34e 100644 --- a/sway-core/src/language/ty/program.rs +++ b/sway-core/src/language/ty/program.rs @@ -399,6 +399,14 @@ impl TyProgram { self.root .check_deprecated(engines, handler, &mut allow_deprecated); } + + pub fn check_recursive( + &self, + engines: &Engines, + handler: &Handler, + ) -> Result<(), ErrorEmitted> { + self.root.check_recursive(engines, handler) + } } impl CollectTypesMetadata for TyProgram { diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index 64d1288b3b6..d390800cd56 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -31,7 +31,6 @@ pub use build_config::{BuildConfig, BuildTarget}; use control_flow_analysis::ControlFlowGraph; use metadata::MetadataManager; use query_engine::{ModuleCacheKey, ModulePath, ProgramsCacheEntry}; -use semantic_analysis::{TypeCheckAnalysis, TypeCheckAnalysisContext}; use std::collections::hash_map::DefaultHasher; use std::collections::HashMap; use std::hash::{Hash, Hasher}; @@ -485,9 +484,13 @@ pub fn parsed_to_ast( typed_program.check_deprecated(engines, handler); - // Analyze the AST for dependency information. - let mut ctx = TypeCheckAnalysisContext::new(engines); - typed_program.type_check_analyze(handler, &mut ctx)?; + match typed_program.check_recursive(engines, handler) { + Ok(()) => {} + Err(e) => { + handler.dedup(); + return Err(e); + } + }; // Collect information about the types used in this program let types_metadata_result = typed_program diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs index 4deb7738938..8af042894d6 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs @@ -8,9 +8,10 @@ use sway_error::{ }; use crate::{ + decl_engine::{DeclId, DeclRefFunction}, language::{ parsed::*, - ty::{self, TyCodeBlock}, + ty::{self, TyCodeBlock, TyFunctionDecl}, CallPath, Visibility, }, semantic_analysis::{type_check_context::EnforceTypeArguments, *}, @@ -235,6 +236,48 @@ fn unify_return_statements( }) } +impl TypeCheckAnalysis for DeclId { + fn type_check_analyze( + &self, + handler: &Handler, + ctx: &mut TypeCheckAnalysisContext, + ) -> Result<(), ErrorEmitted> { + handler.scope(|handler| { + let node = ctx.get_node_for_fn_decl(self); + if let Some(node) = node { + ctx.node_stack.push(node); + + let item_fn = ctx.engines.de().get_function(self); + let _ = item_fn.type_check_analyze(handler, ctx); + + ctx.node_stack.pop(); + } + Ok(()) + }) + } +} + +impl TypeCheckAnalysis for DeclRefFunction { + fn type_check_analyze( + &self, + handler: &Handler, + ctx: &mut TypeCheckAnalysisContext, + ) -> Result<(), ErrorEmitted> { + handler.scope(|handler| { + let node = ctx.get_node_for_fn_decl(self.id()); + if let Some(node) = node { + ctx.node_stack.push(node); + + let item_fn = ctx.engines.de().get_function(self); + let _ = item_fn.type_check_analyze(handler, ctx); + + ctx.node_stack.pop(); + } + Ok(()) + }) + } +} + impl TypeCheckAnalysis for ty::TyFunctionDecl { fn type_check_analyze( &self, diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs b/sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs index 0a16281298b..31af4e8f470 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs @@ -1499,18 +1499,15 @@ impl TypeCheckAnalysis for ty::ImplTrait { let impl_trait = decl_engine.get_impl_trait(&self.decl_id); // Lets create a graph node for the impl trait and for every item in the trait. - ctx.push_impl_trait(self); + ctx.push_nodes_for_impl_trait(self); // Now lets analyze each impl trait item. for (i, item) in impl_trait.items.iter().enumerate() { - let node = ctx.items_node_stack[i]; - ctx.node_stack.push(node); + let _node = ctx.items_node_stack[i]; item.type_check_analyze(handler, ctx)?; - ctx.node_stack.pop(); } - // Clear the work-in-progress node stacks. - ctx.node_stack.clear(); + // Clear the work-in-progress node stack. ctx.items_node_stack.clear(); Ok(()) diff --git a/sway-core/src/semantic_analysis/type_check_analysis.rs b/sway-core/src/semantic_analysis/type_check_analysis.rs index e6fa8d24b35..3148224c935 100644 --- a/sway-core/src/semantic_analysis/type_check_analysis.rs +++ b/sway-core/src/semantic_analysis/type_check_analysis.rs @@ -7,13 +7,16 @@ use std::fs; use petgraph::stable_graph::NodeIndex; use petgraph::Graph; +use sway_error::error::CompileError; use sway_error::handler::{ErrorEmitted, Handler}; -use crate::decl_engine::{DeclId, DeclIdIndexType, DeclRef}; +use crate::decl_engine::{AssociatedItemDeclId, DeclId, DeclIdIndexType}; use crate::engine_threading::DebugWithEngines; -use crate::language::ty::{self, TyImplItem, TyTraitItem}; +use crate::language::ty::{self, TyFunctionDecl, TyTraitItem}; use crate::Engines; +use graph_cycles::Cycles; + pub type TyNodeDepGraphNodeId = petgraph::graph::NodeIndex; #[derive(Clone, Debug)] @@ -36,6 +39,7 @@ impl Display for TyNodeDepGraphEdge { pub enum TyNodeDepGraphNode { ImplTrait { node: ty::ImplTrait }, ImplTraitItem { node: ty::TyTraitItem }, + Fn { node: DeclId }, } // Represents an ordered graph between declaration id indexes. @@ -55,13 +59,73 @@ impl TypeCheckAnalysisContext<'_> { self.dep_graph.add_node(node) } - pub fn add_edge_from_current(&mut self, a: TyNodeDepGraphNodeId, edge: TyNodeDepGraphEdge) { - self.dep_graph - .add_edge(*self.node_stack.last().unwrap(), a, edge); + pub fn add_edge_from_current(&mut self, to: TyNodeDepGraphNodeId, edge: TyNodeDepGraphEdge) { + let from = *self.node_stack.last().unwrap(); + if !self.dep_graph.contains_edge(from, to) { + self.dep_graph.add_edge(from, to, edge); + } + } + + #[allow(clippy::map_entry)] + pub fn get_or_create_node_for_impl_item(&mut self, item: &TyTraitItem) -> TyNodeDepGraphNodeId { + let id = match item { + TyTraitItem::Fn(decl_ref) => decl_ref.id().inner(), + TyTraitItem::Constant(decl_ref) => decl_ref.id().inner(), + TyTraitItem::Type(decl_ref) => decl_ref.id().inner(), + }; + if self.nodes.contains_key(&id) { + *self.nodes.get(&id).unwrap() + } else { + let item_node = self.add_node(TyNodeDepGraphNode::ImplTraitItem { node: item.clone() }); + + self.nodes.insert(id, item_node); + item_node + } } + /// This functions either gets an existing node in the graph, or creates a new + /// node corresponding to the passed function declaration node. + /// The function will try to find a non-monomorphized declaration node id so that + /// future acesses always normalize to the same node id. #[allow(clippy::map_entry)] - pub(crate) fn push_impl_trait(&mut self, impl_trait: &ty::ImplTrait) -> TyNodeDepGraphNodeId { + pub fn get_or_create_node_for_fn_decl( + &mut self, + fn_decl_id: &DeclId, + ) -> TyNodeDepGraphNodeId { + let parents = self + .engines + .de() + .find_all_parents(self.engines, fn_decl_id) + .into_iter() + .filter_map(|f| match f { + AssociatedItemDeclId::TraitFn(_) => None, + AssociatedItemDeclId::Function(fn_id) => Some(fn_id), + AssociatedItemDeclId::Constant(_) => None, + AssociatedItemDeclId::Type(_) => None, + }) + .collect::>(); + let id = if !parents.is_empty() { + parents.first().unwrap().inner() + } else { + fn_decl_id.inner() + }; + if self.nodes.contains_key(&id) { + *self.nodes.get(&id).unwrap() + } else { + let item_node = self.add_node(TyNodeDepGraphNode::Fn { node: *fn_decl_id }); + + self.nodes.insert(id, item_node); + item_node + } + } + + /// This function will process an impl trait declaration, pushing graph nodes + /// corresponding to each item in the trait impl. + #[allow(clippy::map_entry)] + pub(crate) fn push_nodes_for_impl_trait( + &mut self, + impl_trait: &ty::ImplTrait, + ) -> TyNodeDepGraphNodeId { if self.nodes.contains_key(&impl_trait.decl_id.inner()) { *self.nodes.get(&impl_trait.decl_id.inner()).unwrap() } else { @@ -74,8 +138,7 @@ impl TypeCheckAnalysisContext<'_> { let impl_trait = decl_engine.get_impl_trait(&impl_trait.decl_id); for item in impl_trait.items.iter() { - let item_node = - self.add_node(TyNodeDepGraphNode::ImplTraitItem { node: item.clone() }); + let item_node = self.get_or_create_node_for_impl_item(item); // Connect the item node to the impl trait node. self.dep_graph.add_edge( @@ -86,56 +149,71 @@ impl TypeCheckAnalysisContext<'_> { self.items_node_stack.push(item_node); } + node } } - #[allow(dead_code)] - pub(crate) fn get_node_from_impl_trait_item( - &self, - item: &TyImplItem, + /// This function will return an option to the node that represents the + /// function being referenced by a function application. + /// It will look through all the parent nodes in the engine to deal with + /// monomorphized function references. + pub(crate) fn get_node_for_fn_decl( + &mut self, + fn_decl_id: &DeclId, ) -> Option { - for index in self.items_node_stack.iter().rev() { - let node = self - .dep_graph - .node_weight(*index) - .expect("expecting valid node id"); - if let TyNodeDepGraphNode::ImplTraitItem { node } = node { - let matches = match (item, node) { - (TyTraitItem::Fn(item_fn_ref), TyTraitItem::Fn(fn_ref)) => { - fn_ref.name() == item_fn_ref.name() - } - _ => unreachable!(), - }; - if matches { - return Some(*index); - } + let parents = self + .engines + .de() + .find_all_parents(self.engines, fn_decl_id) + .into_iter() + .filter_map(|f| match f { + AssociatedItemDeclId::TraitFn(_) => None, + AssociatedItemDeclId::Function(fn_id) => Some(fn_id), + AssociatedItemDeclId::Constant(_) => None, + AssociatedItemDeclId::Type(_) => None, + }) + .collect::>(); + + let mut possible_nodes = vec![*fn_decl_id]; + possible_nodes.append(&mut parents.clone()); + + for possible_node in possible_nodes.iter().rev() { + if let Some(found) = self.nodes.get(&possible_node.inner()) { + return Some(*found); } } - None - } - - pub(crate) fn get_node_from_impl_trait_fn_ref_app( - &self, - fn_ref: &DeclRef>, - ) -> Option { for index in self.items_node_stack.iter().rev() { let node = self .dep_graph .node_weight(*index) .expect("expecting valid node id"); - if let TyNodeDepGraphNode::ImplTraitItem { - node: TyTraitItem::Fn(item_fn_ref), - } = node - { - if fn_ref.name() == item_fn_ref.name() { + + let fn_decl_id = match node { + TyNodeDepGraphNode::ImplTrait { node: _ } => unreachable!(), + TyNodeDepGraphNode::ImplTraitItem { + node: TyTraitItem::Fn(item_fn_ref), + } => item_fn_ref.id(), + TyNodeDepGraphNode::Fn { node: fn_decl_id } => fn_decl_id, + _ => continue, + }; + + for possible_node in possible_nodes.iter() { + if possible_node.inner() == fn_decl_id.inner() { return Some(*index); } } } - None + // If no node has been found yet, create it. + let base_id = if !parents.is_empty() { + parents.first().unwrap() + } else { + fn_decl_id + }; + let node = self.get_or_create_node_for_fn_decl(base_id); + Some(node) } /// Prints out GraphViz DOT format for the dependency graph. @@ -176,6 +254,89 @@ impl TypeCheckAnalysisContext<'_> { } } + /// Performs recursive analysis by running the Johnson's algorithm to find all cycles + /// in the previously constructed dependency graph. + pub(crate) fn check_recursive_calls(&self, handler: &Handler) -> Result<(), ErrorEmitted> { + handler.scope(|handler| { + let cycles = self.dep_graph.cycles(); + if cycles.is_empty() { + return Ok(()); + } + for mut sub_cycles in cycles { + // Manipulate the cycles order to get the same ordering as the source code's lexical order. + sub_cycles.rotate_left(1); + if sub_cycles.len() == 1 { + let node = self.dep_graph.node_weight(sub_cycles[0]).unwrap(); + let fn_decl_id = self.get_fn_decl_id_from_node(node); + + let fn_decl = self.engines.de().get_function(&fn_decl_id); + handler.emit_err(CompileError::RecursiveCall { + fn_name: fn_decl.name.clone(), + span: fn_decl.span.clone(), + }); + } else { + let node = self.dep_graph.node_weight(sub_cycles[0]).unwrap(); + + let mut call_chain = vec![]; + for i in sub_cycles.into_iter().skip(1) { + let node = self.dep_graph.node_weight(i).unwrap(); + let fn_decl_id = self.get_fn_decl_id_from_node(node); + let fn_decl = self.engines.de().get_function(&fn_decl_id); + call_chain.push(fn_decl.name.to_string()); + } + + let fn_decl_id = self.get_fn_decl_id_from_node(node); + let fn_decl = self.engines.de().get_function(&fn_decl_id); + handler.emit_err(CompileError::RecursiveCallChain { + fn_name: fn_decl.name.clone(), + call_chain: call_chain.join(" -> "), + span: fn_decl.span.clone(), + }); + } + } + Ok(()) + }) + } + + pub(crate) fn get_normalized_fn_node_id( + &self, + fn_decl_id: &DeclId, + ) -> DeclId { + let parents = self + .engines + .de() + .find_all_parents(self.engines, fn_decl_id) + .into_iter() + .filter_map(|f| match f { + AssociatedItemDeclId::TraitFn(_) => None, + AssociatedItemDeclId::Function(fn_id) => Some(fn_id), + AssociatedItemDeclId::Constant(_) => None, + AssociatedItemDeclId::Type(_) => None, + }) + .collect::>(); + + if !parents.is_empty() { + *parents.first().unwrap() + } else { + *fn_decl_id + } + } + + pub(crate) fn get_fn_decl_id_from_node( + &self, + node: &TyNodeDepGraphNode, + ) -> DeclId { + match node { + TyNodeDepGraphNode::ImplTrait { .. } => unreachable!(), + TyNodeDepGraphNode::ImplTraitItem { node } => match node { + TyTraitItem::Fn(node) => *node.id(), + TyTraitItem::Constant(_) => unreachable!(), + TyTraitItem::Type(_) => unreachable!(), + }, + TyNodeDepGraphNode::Fn { node } => *node, + } + } + pub(crate) fn get_sub_graph( &self, node_index: NodeIndex, @@ -199,7 +360,7 @@ impl TypeCheckAnalysisContext<'_> { } impl DebugWithEngines for TyNodeDepGraphNode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>, _engines: &Engines) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>, engines: &Engines) -> std::fmt::Result { let text = match self { TyNodeDepGraphNode::ImplTraitItem { node } => { let str = match node { @@ -212,6 +373,10 @@ impl DebugWithEngines for TyNodeDepGraphNode { TyNodeDepGraphNode::ImplTrait { node } => { format!("{:?}", node.name.as_str()) } + TyNodeDepGraphNode::Fn { node } => { + let fn_decl = engines.de().get_function(node); + format!("{:?}", fn_decl.name.as_str()) + } }; f.write_str(&text) } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/Forc.lock new file mode 100644 index 00000000000..c60aee96b19 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = 'impl_self_recursive' +source = 'member' diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/Forc.toml new file mode 100644 index 00000000000..5024adec979 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "impl_self_recursive" +implicit-std = false + +[dependencies] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/src/main.sw new file mode 100644 index 00000000000..c4aebb6b54f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/src/main.sw @@ -0,0 +1,18 @@ +script; + +struct Foo {} + +impl Foo { + pub fn rec_0(self) -> u32 { self.rec_0() } + + pub fn rec_1(self) -> u32 { self.rec_2() } + pub fn rec_2(self) -> u32 { self.rec_1() } + + pub fn rec_3(self) -> u32 { self.rec_4() } + pub fn rec_4(self) -> u32 { self.rec_5() } + pub fn rec_5(self) -> u32 { self.rec_3() } +} + +fn main() -> u32 { + 0 +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/test.toml new file mode 100644 index 00000000000..6be2d24e8dd --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/impl_self_recursive/test.toml @@ -0,0 +1,13 @@ +category = "fail" + +# check: $()Function rec_0 is recursive, which is unsupported at this time. + +# check: $()Function rec_1 is recursive via rec_2, which is unsupported at this time. + +# check: $()Function rec_2 is recursive via rec_1, which is unsupported at this time. + +# check: $()Function rec_3 is recursive via rec_4 -> rec_5, which is unsupported at this time. + +# check: $()Function rec_4 is recursive via rec_5 -> rec_3, which is unsupported at this time. + +# check: $()Function rec_5 is recursive via rec_3 -> rec_4, which is unsupported at this time.