-
Notifications
You must be signed in to change notification settings - Fork 12
feat(tdl): Add DetectStructCircularDependency analysis pass.
#237
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
base: main
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| #ifndef SPIDER_TDL_PASS_PASS_HPP | ||
| #define SPIDER_TDL_PASS_PASS_HPP | ||
|
|
||
| #include <memory> | ||
| #include <string> | ||
|
|
||
| #include <boost/outcome/std_result.hpp> | ||
|
|
||
| namespace spider::tdl::pass { | ||
| /** | ||
| * Represents an abstract pass over a TDL AST. | ||
| */ | ||
| class Pass { | ||
| public: | ||
| // Types | ||
| /** | ||
| * Represents an abstract error that can occur during the execution of a pass. | ||
| */ | ||
| class Error { | ||
| public: | ||
| // Constructor | ||
| Error() = default; | ||
|
|
||
| // Delete copy constructor and assignment operator | ||
| Error(Error const&) = delete; | ||
| auto operator=(Error const&) -> Error& = delete; | ||
|
|
||
| // Default move constructor and assignment operator | ||
| Error(Error&&) = default; | ||
| auto operator=(Error&&) -> Error& = default; | ||
|
|
||
| // Destructor | ||
| virtual ~Error() = default; | ||
|
|
||
| // Methods | ||
| [[nodiscard]] virtual auto to_string() const -> std::string = 0; | ||
| }; | ||
|
|
||
| // Constructors | ||
| Pass() = default; | ||
|
|
||
| // Delete copy constructor and assignment operator | ||
| Pass(Pass const&) = delete; | ||
| auto operator=(Pass const&) -> Pass& = delete; | ||
|
|
||
| // Default move constructor and assignment operator | ||
| Pass(Pass&&) = default; | ||
| auto operator=(Pass&&) -> Pass& = default; | ||
|
|
||
| // Destructor | ||
| virtual ~Pass() = default; | ||
|
|
||
| // Methods | ||
| /** | ||
| * Executes the pass. | ||
| * @return A void result on success, or a pointer to the error on failure. | ||
| */ | ||
| [[nodiscard]] virtual auto run() -> boost::outcome_v2::std_checked<void, std::unique_ptr<Error>> | ||
| = 0; | ||
| }; | ||
| } // namespace spider::tdl::pass | ||
|
|
||
| #endif // SPIDER_TDL_PASS_PASS_HPP |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| #include "DetectStructCircularDependency.hpp" | ||
|
|
||
| #include <algorithm> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <utility> | ||
| #include <vector> | ||
|
|
||
| #include <boost/outcome/std_result.hpp> | ||
| #include <boost/outcome/success_failure.hpp> | ||
| #include <fmt/format.h> | ||
| #include <fmt/ranges.h> | ||
|
|
||
| #include <spider/tdl/parser/ast/nodes.hpp> | ||
| #include <spider/tdl/pass/Pass.hpp> | ||
|
|
||
| namespace spider::tdl::pass::analysis { | ||
| auto DetectStructCircularDependency::Error::to_string() const -> std::string { | ||
| std::vector<std::string> circular_dependency_group_error_messages; | ||
| circular_dependency_group_error_messages.reserve(m_strongly_connected_components.size()); | ||
| for (auto const& group : m_strongly_connected_components) { | ||
| std::vector<std::string> struct_descriptions; | ||
| struct_descriptions.reserve(group.size()); | ||
| for (auto const& struct_spec : group) { | ||
| struct_descriptions.emplace_back( | ||
| fmt::format( | ||
| " `{}` at {}", | ||
| struct_spec->get_name(), | ||
| struct_spec->get_source_location().serialize_to_str() | ||
| ) | ||
| ); | ||
| } | ||
| circular_dependency_group_error_messages.emplace_back( | ||
| fmt::format( | ||
| "Found a circular dependency group of {} struct spec(s):\n{}", | ||
| group.size(), | ||
| fmt::join(struct_descriptions, "\n") | ||
| ) | ||
| ); | ||
| } | ||
| return fmt::format( | ||
| "Found {} circular dependency group(s):\n{}", | ||
| m_strongly_connected_components.size(), | ||
| fmt::join(circular_dependency_group_error_messages, "\n") | ||
| ); | ||
| } | ||
|
|
||
| auto DetectStructCircularDependency::run() | ||
| -> boost::outcome_v2::std_checked<void, std::unique_ptr<Pass::Error>> { | ||
| auto const& strongly_connected_components{ | ||
| m_struct_spec_dependency_graph->get_strongly_connected_components() | ||
| }; | ||
| if (strongly_connected_components.empty()) { | ||
| return boost::outcome_v2::success(); | ||
| } | ||
|
|
||
| std::vector<std::vector<std::shared_ptr<parser::ast::StructSpec const>>> | ||
| circular_dependency_groups; | ||
| circular_dependency_groups.reserve(strongly_connected_components.size()); | ||
| for (auto const& scc : strongly_connected_components) { | ||
| std::vector<std::shared_ptr<parser::ast::StructSpec const>> group; | ||
| group.reserve(scc.size()); | ||
| for (auto const id : scc) { | ||
| group.emplace_back(m_struct_spec_dependency_graph->get_struct_spec_from_id(id)); | ||
| } | ||
| std::ranges::sort(group, [](auto const& lhs, auto const& rhs) -> bool { | ||
| return lhs->get_source_location() < rhs->get_source_location(); | ||
| }); | ||
| circular_dependency_groups.emplace_back(std::move(group)); | ||
| } | ||
|
|
||
| std::ranges::sort(circular_dependency_groups, [](auto const& lhs, auto const& rhs) -> bool { | ||
| // Compare by the source location of the first struct spec in each group. This is safe | ||
| // because: | ||
| // - Each group is guaranteed to be non-empty. | ||
| // - Each struct spec should only appear in one SCC, which guarantees the source locations | ||
| // are unique. | ||
| return lhs.front()->get_source_location() < rhs.front()->get_source_location(); | ||
| }); | ||
|
|
||
| return boost::outcome_v2::failure( | ||
| std::make_unique<Error>(std::move(circular_dependency_groups)) | ||
| ); | ||
| } | ||
| } // namespace spider::tdl::pass::analysis | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| #ifndef SPIDER_TDL_PASS_ANALYSIS_DETECTSTRUCTCIRCULARDEPENDENCY_HPP | ||
| #define SPIDER_TDL_PASS_ANALYSIS_DETECTSTRUCTCIRCULARDEPENDENCY_HPP | ||
|
|
||
| #include <memory> | ||
| #include <string> | ||
| #include <utility> | ||
| #include <vector> | ||
|
|
||
| #include <boost/outcome/std_result.hpp> | ||
|
|
||
| #include <spider/tdl/parser/ast/nodes.hpp> | ||
| #include <spider/tdl/pass/analysis/StructSpecDependencyGraph.hpp> | ||
| #include <spider/tdl/pass/Pass.hpp> | ||
|
|
||
| namespace spider::tdl::pass::analysis { | ||
| /** | ||
| * Wrapper of `StructSpecDependencyGraph` to detect circular dependencies among struct specs. | ||
| */ | ||
| class DetectStructCircularDependency : public Pass { | ||
| public: | ||
| // Types | ||
| /** | ||
| * Represents an error including all circular dependency groups (reported as strongly connected | ||
| * components). | ||
| */ | ||
| class Error : public Pass::Error { | ||
| public: | ||
| // Constructor | ||
| explicit Error( | ||
| std::vector<std::vector<std::shared_ptr<parser::ast::StructSpec const>>> | ||
| strongly_connected_components | ||
| ) | ||
| : m_strongly_connected_components{std::move(strongly_connected_components)} {} | ||
|
|
||
| // Methods implementing `Pass::Error` | ||
| [[nodiscard]] auto to_string() const -> std::string override; | ||
|
|
||
| private: | ||
| // Variables | ||
| std::vector<std::vector<std::shared_ptr<parser::ast::StructSpec const>>> | ||
| m_strongly_connected_components; | ||
| }; | ||
|
|
||
| // Constructor | ||
| explicit DetectStructCircularDependency( | ||
| std::shared_ptr<StructSpecDependencyGraph> struct_spec_dependency_graph | ||
| ) | ||
| : m_struct_spec_dependency_graph{std::move(struct_spec_dependency_graph)} {} | ||
|
|
||
| // Methods implementing `Pass` | ||
| /** | ||
| * @return A void result on success, or a pointer to `DetectStructCircularDependency::Error` | ||
| * on failure. | ||
| */ | ||
| [[nodiscard]] auto run() | ||
| -> boost::outcome_v2::std_checked<void, std::unique_ptr<Pass::Error>> override; | ||
|
|
||
| private: | ||
| std::shared_ptr<StructSpecDependencyGraph> m_struct_spec_dependency_graph; | ||
| }; | ||
| } // namespace spider::tdl::pass::analysis | ||
|
|
||
| #endif // SPIDER_TDL_PASS_ANALYSIS_DETECTSTRUCTCIRCULARDEPENDENCY_HPP |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,74 @@ | ||||||||||||||||||||||||||||||||||||||||
| #include "DetectUndefinedStruct.hpp" | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| #include <algorithm> | ||||||||||||||||||||||||||||||||||||||||
| #include <memory> | ||||||||||||||||||||||||||||||||||||||||
| #include <string> | ||||||||||||||||||||||||||||||||||||||||
| #include <tuple> | ||||||||||||||||||||||||||||||||||||||||
| #include <utility> | ||||||||||||||||||||||||||||||||||||||||
| #include <vector> | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| #include <boost/outcome/std_result.hpp> | ||||||||||||||||||||||||||||||||||||||||
| #include <boost/outcome/success_failure.hpp> | ||||||||||||||||||||||||||||||||||||||||
| #include <fmt/format.h> | ||||||||||||||||||||||||||||||||||||||||
| #include <fmt/ranges.h> | ||||||||||||||||||||||||||||||||||||||||
| #include <ystdlib/error_handling/Result.hpp> | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| #include <spider/tdl/parser/ast/nodes.hpp> | ||||||||||||||||||||||||||||||||||||||||
| #include <spider/tdl/pass/Pass.hpp> | ||||||||||||||||||||||||||||||||||||||||
| #include <spider/tdl/pass/utils.hpp> | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| namespace spider::tdl::pass::analysis { | ||||||||||||||||||||||||||||||||||||||||
| auto DetectUndefinedStruct::Error::to_string() const -> std::string { | ||||||||||||||||||||||||||||||||||||||||
| std::vector<std::string> undefined_struct_error_messages; | ||||||||||||||||||||||||||||||||||||||||
| undefined_struct_error_messages.reserve(m_undefined_struct.size()); | ||||||||||||||||||||||||||||||||||||||||
| for (auto const* undefined_struct : m_undefined_struct) { | ||||||||||||||||||||||||||||||||||||||||
| undefined_struct_error_messages.emplace_back( | ||||||||||||||||||||||||||||||||||||||||
| fmt::format( | ||||||||||||||||||||||||||||||||||||||||
| "Referencing to an undefined struct `{}` at {}", | ||||||||||||||||||||||||||||||||||||||||
| undefined_struct->get_name(), | ||||||||||||||||||||||||||||||||||||||||
| undefined_struct->get_source_location().serialize_to_str() | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| return fmt::format( | ||||||||||||||||||||||||||||||||||||||||
| "Found {} undefined struct reference(s):\n{}", | ||||||||||||||||||||||||||||||||||||||||
| m_undefined_struct.size(), | ||||||||||||||||||||||||||||||||||||||||
| fmt::join(undefined_struct_error_messages, "\n") | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| auto DetectUndefinedStruct::run() | ||||||||||||||||||||||||||||||||||||||||
| -> boost::outcome_v2::std_checked<void, std::unique_ptr<Pass::Error>> { | ||||||||||||||||||||||||||||||||||||||||
| std::vector<parser::ast::Struct const*> undefined_structs; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| auto struct_visitor | ||||||||||||||||||||||||||||||||||||||||
| = [&](parser::ast::Struct const* struct_node) -> ystdlib::error_handling::Result<void> { | ||||||||||||||||||||||||||||||||||||||||
| if (nullptr == m_translation_unit->get_struct_spec(struct_node->get_name())) { | ||||||||||||||||||||||||||||||||||||||||
| undefined_structs.emplace_back(struct_node); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| return ystdlib::error_handling::success(); | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| std::ignore = visit_struct_node_using_dfs(m_translation_unit, struct_visitor); | ||||||||||||||||||||||||||||||||||||||||
| std::ignore = m_translation_unit->visit_struct_specs( | ||||||||||||||||||||||||||||||||||||||||
| [&]( | ||||||||||||||||||||||||||||||||||||||||
| parser::ast::StructSpec const* struct_spec | ||||||||||||||||||||||||||||||||||||||||
| ) -> ystdlib::error_handling::Result<void> { | ||||||||||||||||||||||||||||||||||||||||
| return visit_struct_node_using_dfs(struct_spec, struct_visitor); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+52
to
+59
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure traversal failures surface. Both DFS traversals return - std::ignore = visit_struct_node_using_dfs(m_translation_unit, struct_visitor);
- std::ignore = m_translation_unit->visit_struct_specs(
- [&](parser::ast::StructSpec const* struct_spec) -> ystdlib::error_handling::Result<void> {
- return visit_struct_node_using_dfs(struct_spec, struct_visitor);
- }
- );
+ YSTDLIB_ERROR_HANDLING_TRYV(
+ visit_struct_node_using_dfs(m_translation_unit, struct_visitor)
+ );
+ YSTDLIB_ERROR_HANDLING_TRYV(
+ m_translation_unit->visit_struct_specs(
+ [&](parser::ast::StructSpec const* struct_spec)
+ -> ystdlib::error_handling::Result<void> {
+ return visit_struct_node_using_dfs(struct_spec, struct_visitor);
+ }
+ )
+ );📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (undefined_structs.empty()) { | ||||||||||||||||||||||||||||||||||||||||
| return boost::outcome_v2::success(); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| std::ranges::sort( | ||||||||||||||||||||||||||||||||||||||||
| undefined_structs, | ||||||||||||||||||||||||||||||||||||||||
| [](parser::ast::Struct const* lhs, parser::ast::Struct const* rhs) -> bool { | ||||||||||||||||||||||||||||||||||||||||
| return lhs->get_source_location() < rhs->get_source_location(); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| return boost::outcome_v2::failure( | ||||||||||||||||||||||||||||||||||||||||
| std::make_unique<DetectUndefinedStruct::Error>(std::move(undefined_structs)) | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } // namespace spider::tdl::pass::analysis | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment here is a little confusing. How does uniqueness have to do with the safety of the sort?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah sorry my bad. I think instead of "is safe", I should say "is deterministic and non-ambiguous"