libexpr: Warn on incorrect string usage

The indentation stripping semantics of strings are fairly bad and have a
few gotchas where they behave unintuitively. But the good news is, that
these cases are easy to catch and can be avoided.
This commit adds a warning in the parser when such strings are detected.

Unfortunately Nixpkgs uses this kind of a lot, so we won't be able to
actually enable this warning for a while to come.

Co-authored-by: Commentator2.0 <lix@crystal-cavern.systems>
Change-Id: I3b3b68c2eee4cd70959d3f4ca643cb6caf3a2217
This commit is contained in:
piegames
2026-01-31 15:32:27 +01:00
co-authored by Commentator2.0
parent 4e1c216fd3
commit f1fbd1d095
11 changed files with 125 additions and 3 deletions
+21
View File
@@ -656,9 +656,20 @@ template<> struct BuildAST<grammar::v1::string> : change_head<StringState> {
struct IndStringState : SubexprState {
using SubexprState::SubexprState;
// If the first line (after the '') is empty it gets completely removed.
// We track that in the grammar because no need to process it any further,
// but we still require the information to know the actual number of lines
// in the string.
bool firstLineStripped = false;
std::vector<IndStringLine> lines;
};
template<> struct BuildAST<grammar::v1::ind_string::strip_first_line> {
static void apply(const auto & in, IndStringState & s, State & ps) {
s.firstLineStripped = true;
}
};
template<> struct BuildAST<grammar::v1::ind_string::line_start> {
static void apply(const auto & in, IndStringState & s, State & ps) {
s.lines.push_back(IndStringLine { in.string_view(), ps.at(in) });
@@ -717,6 +728,16 @@ template<> struct BuildAST<grammar::v1::ind_string::nul> {
template<> struct BuildAST<grammar::v1::ind_string> : change_head<IndStringState> {
static void success(const auto & in, IndStringState & s, ExprState & e, State & ps) {
if (!ps.featureSettings.isEnabled(Dep::BrokenStringIndent)) {
/* Check for semantically incorrect code: Single-line string with indentation */
if (s.lines.size() == 1 && !s.firstLineStripped && s.lines.front().indentation.size() > 0) {
ps.badSingleLineIndStringFound(ps.at(in));
}
/* Check for semantically incorrect code: Multi-line string with text on the first line */
if (s.lines.size() > 1 && !s.firstLineStripped) {
ps.badFirstLineIndStringFound(ps.at(in));
}
}
e.pushExpr(noPos, ps.stripIndentation(ps.at(in), std::move(s.lines)));
}
};