Skip to content

Commit bd93d39

Browse files
committed
feat(errors): add modular template error rules
1 parent d1d46d0 commit bd93d39

7 files changed

Lines changed: 519 additions & 0 deletions

File tree

include/vix/cli/errors/ErrorPipeline.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <vix/cli/errors/CompilerError.hpp>
2020
#include <vix/cli/errors/ErrorContext.hpp>
2121
#include <vix/cli/errors/IErrorRule.hpp>
22+
#include <vix/cli/errors/template/ITemplateErrorRule.hpp>
2223

2324
namespace vix::cli::errors
2425
{
@@ -30,6 +31,7 @@ namespace vix::cli::errors
3031

3132
private:
3233
std::vector<std::unique_ptr<IErrorRule>> rules_;
34+
std::vector<std::unique_ptr<vix::cli::errors::template_rules::ITemplateErrorRule>> templateRules_;
3335
};
3436
} // namespace vix::cli::errors
3537

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
*
3+
* @file ITemplateErrorRule.hpp
4+
* @author Gaspard Kirira
5+
*
6+
* Copyright 2025, Gaspard Kirira. All rights reserved.
7+
* https://github.com/vixcpp/vix
8+
* Use of this source code is governed by a MIT license
9+
* that can be found in the License file.
10+
*
11+
* Vix.cpp
12+
*/
13+
#ifndef VIX_I_TEMPLATE_ERROR_RULE_HPP
14+
#define VIX_I_TEMPLATE_ERROR_RULE_HPP
15+
16+
#include <memory>
17+
18+
#include <vix/cli/errors/CompilerError.hpp>
19+
#include <vix/cli/errors/ErrorContext.hpp>
20+
21+
namespace vix::cli::errors::template_rules
22+
{
23+
/// A single friendly template error rule.
24+
/// - match(): decides if this rule applies to a compiler error
25+
/// - handle(): prints a custom friendly message and returns true if handled
26+
class ITemplateErrorRule
27+
{
28+
public:
29+
virtual ~ITemplateErrorRule() = default;
30+
31+
virtual bool match(const vix::cli::errors::CompilerError &err) const = 0;
32+
33+
virtual bool handle(
34+
const vix::cli::errors::CompilerError &err,
35+
const vix::cli::errors::ErrorContext &ctx) const = 0;
36+
};
37+
38+
std::unique_ptr<ITemplateErrorRule> makeDependentTypenameRule();
39+
std::unique_ptr<ITemplateErrorRule> makeNoTypeNamedRule();
40+
std::unique_ptr<ITemplateErrorRule> makeTemplateArgumentMismatchRule();
41+
std::unique_ptr<ITemplateErrorRule> makeSubstitutionFailureRule();
42+
} // namespace vix::cli::errors::template_rules
43+
44+
#endif

src/errors/ErrorPipeline.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,20 @@
1313
*/
1414
#include <vix/cli/errors/ErrorPipeline.hpp>
1515
#include <vix/cli/errors/RulesFactory.hpp>
16+
#include <vix/cli/errors/template/ITemplateErrorRule.hpp>
1617

1718
#include <string>
1819

1920
namespace vix::cli::errors
2021
{
2122
ErrorPipeline::ErrorPipeline()
2223
{
24+
// Template-specific compile errors
25+
templateRules_.push_back(vix::cli::errors::template_rules::makeDependentTypenameRule());
26+
templateRules_.push_back(vix::cli::errors::template_rules::makeNoTypeNamedRule());
27+
templateRules_.push_back(vix::cli::errors::template_rules::makeTemplateArgumentMismatchRule());
28+
templateRules_.push_back(vix::cli::errors::template_rules::makeSubstitutionFailureRule());
29+
2330
// Beginner / syntax / common mistakes
2431
rules_.push_back(makeCoutNotDeclaredRule());
2532
rules_.push_back(makeHeaderNotFoundRule());
@@ -64,6 +71,12 @@ namespace vix::cli::errors
6471
if (!isUserFirst(err, ctx))
6572
continue;
6673

74+
for (const auto &rule : templateRules_)
75+
{
76+
if (rule && rule->match(err))
77+
return rule->handle(err, ctx);
78+
}
79+
6780
for (const auto &rule : rules_)
6881
{
6982
if (rule && rule->match(err))
@@ -73,6 +86,12 @@ namespace vix::cli::errors
7386

7487
for (const auto &err : errors)
7588
{
89+
for (const auto &rule : templateRules_)
90+
{
91+
if (rule && rule->match(err))
92+
return rule->handle(err, ctx);
93+
}
94+
7695
for (const auto &rule : rules_)
7796
{
7897
if (rule && rule->match(err))
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
*
3+
* @file DependentTypenameRule.cpp
4+
* @author Gaspard Kirira
5+
*
6+
* Copyright 2025, Gaspard Kirira. All rights reserved.
7+
* https://github.com/vixcpp/vix
8+
* Use of this source code is governed by a MIT license
9+
* that can be found in the License file.
10+
*
11+
* Vix.cpp
12+
*
13+
*/
14+
#include <vix/cli/errors/template/ITemplateErrorRule.hpp>
15+
#include <vix/cli/errors/CodeFrame.hpp>
16+
17+
#include <filesystem>
18+
#include <iostream>
19+
#include <memory>
20+
#include <string>
21+
22+
#include <vix/cli/Style.hpp>
23+
24+
using namespace vix::cli::style;
25+
26+
namespace vix::cli::errors::template_rules
27+
{
28+
namespace
29+
{
30+
bool icontains(const std::string &text, const std::string &needle)
31+
{
32+
if (needle.empty())
33+
return true;
34+
35+
auto lower = [](unsigned char c) -> char
36+
{
37+
if (c >= 'A' && c <= 'Z')
38+
return static_cast<char>(c + ('a' - 'A'));
39+
return static_cast<char>(c);
40+
};
41+
42+
if (text.size() < needle.size())
43+
return false;
44+
45+
for (std::size_t i = 0; i + needle.size() <= text.size(); ++i)
46+
{
47+
bool ok = true;
48+
49+
for (std::size_t j = 0; j < needle.size(); ++j)
50+
{
51+
if (lower(static_cast<unsigned char>(text[i + j])) !=
52+
lower(static_cast<unsigned char>(needle[j])))
53+
{
54+
ok = false;
55+
break;
56+
}
57+
}
58+
59+
if (ok)
60+
return true;
61+
}
62+
63+
return false;
64+
}
65+
} // namespace
66+
67+
class DependentTypenameRule final : public ITemplateErrorRule
68+
{
69+
public:
70+
bool match(const vix::cli::errors::CompilerError &err) const override
71+
{
72+
const std::string &m = err.message;
73+
74+
return icontains(m, "need") &&
75+
icontains(m, "typename") &&
76+
icontains(m, "dependent scope");
77+
}
78+
79+
bool handle(
80+
const vix::cli::errors::CompilerError &err,
81+
const vix::cli::errors::ErrorContext &ctx) const override
82+
{
83+
std::filesystem::path filePath(err.file);
84+
const std::string fileName = filePath.filename().string();
85+
86+
std::cerr << RED
87+
<< "error: missing typename before dependent type"
88+
<< RESET << "\n";
89+
90+
printCodeFrame(err, ctx);
91+
92+
std::cerr << YELLOW
93+
<< "hint: this type depends on a template parameter, so the compiler needs typename to know it is a type"
94+
<< RESET << "\n";
95+
96+
std::cerr << YELLOW
97+
<< "hint: write typename before names like T::value_type, T::iterator, or U::type when they refer to a type"
98+
<< RESET << "\n";
99+
100+
std::cerr << GREEN
101+
<< "at: " << fileName << ":" << err.line << ":" << err.column
102+
<< RESET << "\n";
103+
104+
return true;
105+
}
106+
};
107+
108+
std::unique_ptr<ITemplateErrorRule> makeDependentTypenameRule()
109+
{
110+
return std::make_unique<DependentTypenameRule>();
111+
}
112+
} // namespace vix::cli::errors::template_rules
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
*
3+
* @file NoTypeNamedRule.cpp
4+
* @author Gaspard Kirira
5+
*
6+
* Copyright 2025, Gaspard Kirira. All rights reserved.
7+
* https://github.com/vixcpp/vix
8+
* Use of this source code is governed by a MIT license
9+
* that can be found in the License file.
10+
*
11+
* Vix.cpp
12+
*
13+
*/
14+
#include <vix/cli/errors/template/ITemplateErrorRule.hpp>
15+
#include <vix/cli/errors/CodeFrame.hpp>
16+
17+
#include <filesystem>
18+
#include <iostream>
19+
#include <memory>
20+
#include <string>
21+
22+
#include <vix/cli/Style.hpp>
23+
24+
using namespace vix::cli::style;
25+
26+
namespace vix::cli::errors::template_rules
27+
{
28+
namespace
29+
{
30+
bool icontains(const std::string &text, const std::string &needle)
31+
{
32+
if (needle.empty())
33+
return true;
34+
35+
auto lower = [](unsigned char c) -> char
36+
{
37+
if (c >= 'A' && c <= 'Z')
38+
return static_cast<char>(c + ('a' - 'A'));
39+
return static_cast<char>(c);
40+
};
41+
42+
if (text.size() < needle.size())
43+
return false;
44+
45+
for (std::size_t i = 0; i + needle.size() <= text.size(); ++i)
46+
{
47+
bool ok = true;
48+
49+
for (std::size_t j = 0; j < needle.size(); ++j)
50+
{
51+
if (lower(static_cast<unsigned char>(text[i + j])) !=
52+
lower(static_cast<unsigned char>(needle[j])))
53+
{
54+
ok = false;
55+
break;
56+
}
57+
}
58+
59+
if (ok)
60+
return true;
61+
}
62+
63+
return false;
64+
}
65+
} // namespace
66+
67+
class NoTypeNamedRule final : public ITemplateErrorRule
68+
{
69+
public:
70+
bool match(const vix::cli::errors::CompilerError &err) const override
71+
{
72+
const std::string &m = err.message;
73+
74+
return (icontains(m, "no type named") && icontains(m, "in")) ||
75+
(icontains(m, "no type named") && icontains(m, "value_type")) ||
76+
(icontains(m, "has no member named") && icontains(m, "type"));
77+
}
78+
79+
bool handle(
80+
const vix::cli::errors::CompilerError &err,
81+
const vix::cli::errors::ErrorContext &ctx) const override
82+
{
83+
std::filesystem::path filePath(err.file);
84+
const std::string fileName = filePath.filename().string();
85+
86+
std::cerr << RED
87+
<< "error: type alias or nested type not found"
88+
<< RESET << "\n";
89+
90+
printCodeFrame(err, ctx);
91+
92+
std::cerr << YELLOW
93+
<< "hint: the template argument does not provide the nested type you are trying to use"
94+
<< RESET << "\n";
95+
96+
std::cerr << YELLOW
97+
<< "hint: check names like T::value_type, T::iterator, or U::type and make sure the chosen type actually defines them"
98+
<< RESET << "\n";
99+
100+
std::cerr << GREEN
101+
<< "at: " << fileName << ":" << err.line << ":" << err.column
102+
<< RESET << "\n";
103+
104+
return true;
105+
}
106+
};
107+
108+
std::unique_ptr<ITemplateErrorRule> makeNoTypeNamedRule()
109+
{
110+
return std::make_unique<NoTypeNamedRule>();
111+
}
112+
} // namespace vix::cli::errors::template_rules

0 commit comments

Comments
 (0)