Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ the cache). Check-only modes: `--syntax-only` stops after parsing,
`--semantic-only` after type-checking — neither writes output — and
`--transient-cc` keeps only the linked `.so`, removing the generated C++
intermediates.
Diagnostics name only your code's position; `--compiler-locations` appends
the compiler source line that raised each one, for filing a compiler bug.

Exercise the Orlyscript test suite against compiled `.orly` programs:

Expand Down
1 change: 1 addition & 0 deletions changelog.d/557-clean-diagnostics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **Fixed**: compiler diagnostics no longer show users a path into the compiler's own C++ source. `orlyc` printed `2:28-2:47 [orly/type/add_visitor.h, 50]This expression is invalid.`: an internal path that reads like a crash, glued to the message with no space. The throw site is now a separate field on `TSourceError` instead of being spliced into `what()`. `orlyc --compiler-locations` shows it, and WS `source_error` replies carry it in a separate `compiler_loc` key, leaving `result` clean. Internal compiler errors keep it in the message, because there it is what the report needs. A mutation whose target is not a stored value now says so. A bare address (`<['counter']> += n`, the form the quickstart led a newcomer to write) gets a message that shows the working form, `*<['counter']>::(int) += n`, and a plain value, which used to **segfault orlyc** with no output at all, gets a clean error. Both are pinned by new diagnostic lang_tests (#557).
5 changes: 5 additions & 0 deletions docs/PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ sent as one WebSocket text message. The server replies with one JSON message:
- `result` is present on statements that produce a value (see each statement).
Its shape depends on the statement; for `try` it is the JSON marshaling of the
method's return value (see "JSON marshaling" below).
- A statement that fails to compile replies `"status": "source_error"`, with
the message in `result` and its position in `pos` (`line:col-line:col`).
`compiler_loc` names the compiler source line that raised it -- useful in a
compiler bug report, meaningless to the statement's author, so clients should
not show it by default (#557).

## Statements

Expand Down
28 changes: 22 additions & 6 deletions orly/error.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,39 @@
#include <cassert>

#include <base/as_str.h>
#include <base/code_location.h>
#include <base/thrower.h>
#include <orly/pos_range.h>

namespace Orly {

/* what() is the message for the person who wrote the orlyscript. The
compiler source line that threw it is kept apart, in GetCodeLocation(),
because to that person it reads like a crash rather than a diagnosis of
their code (#557). Front ends show it only on request. */
class TSourceError : public std::runtime_error {
public:

const Base::TCodeLocation &GetCodeLocation() const {
return CodeLocation;
}

const TPosRange &GetPosRange() const {
return PosRange;
}

protected:

TSourceError(const TPosRange &pos_range, const char *msg)
: std::runtime_error(msg), PosRange(pos_range) {}
TSourceError(
const Base::TCodeLocation &code_location,
const TPosRange &pos_range,
const char *msg)
: std::runtime_error(msg), CodeLocation(code_location), PosRange(pos_range) {}

private:

const Base::TCodeLocation CodeLocation;

const TPosRange PosRange;

}; // TSourceError
Expand All @@ -51,18 +65,20 @@ namespace Orly {
const Base::TCodeLocation &code_location,
const TPosRange &pos_range,
const char *message = "This feature is not yet implemented")
: TSourceError(pos_range, Base::AsStr(code_location, message).c_str()) {}
: TSourceError(code_location, pos_range, message) {}

}; // TNotImplementedError

/* A compiler bug, not a user error, so the message keeps the compiler
source location: "here is where" is what the report needs. */
class TImpossibleError : public TSourceError {
public:

TImpossibleError(
const Base::TCodeLocation &code_location,
const TPosRange &pos_range,
const char *message = "Internal Compiler Error: We shouldn't have reached this line of code in the compiler.")
: TSourceError(pos_range, Base::AsStr(code_location, message).c_str()) {}
: TSourceError(code_location, pos_range, Base::AsStr(code_location, ' ', message).c_str()) {}

}; // TImpossibleError

Expand All @@ -73,7 +89,7 @@ namespace Orly {
const Base::TCodeLocation &code_location,
const TPosRange &pos_range,
const char *message)
: TSourceError(pos_range, Base::AsStr(code_location, message).c_str()) {}
: TSourceError(code_location, pos_range, message) {}

}; // TCompileError

Expand All @@ -89,7 +105,7 @@ namespace Orly {
const Base::TCodeLocation &code_location,
const TPosRange &pos_range,
const char *message = DefaultMessage)
: TSourceError(pos_range, Base::AsStr(code_location, message).c_str()) {}
: TSourceError(code_location, pos_range, message) {}

}; // TExprError

Expand Down
11 changes: 10 additions & 1 deletion orly/orlyc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class TCompilerConfig : public Base::TCmd {
MachineForm(false),
OutputDir(Util::GetCwd()),
SemanticOnly(false),
ShowCompilerLocations(false),
SkipTests(false),
SyntaxOnly(false),
TransientCc(false),
Expand All @@ -69,6 +70,8 @@ class TCompilerConfig : public Base::TCmd {
Param(&TCompilerConfig::MachineForm, "machine_form", Optional, "machine-form\0m\0", "Print out machine readable progress.");
Param(&TCompilerConfig::OutputDir, "output_directory", Optional, "output\0o\0", "The directory to write output to.");
Param(&TCompilerConfig::SemanticOnly, "semantic_only", Optional, "semantic-only\0", "Don't produce output, just syntactically and semantically validate the program.");
Param(&TCompilerConfig::ShowCompilerLocations, "show_compiler_locations", Optional, "compiler-locations\0",
"Append the compiler source line that raised each diagnostic. For reporting a compiler bug; it says nothing about your code.");
Param(&TCompilerConfig::SkipTests, "skip_tests", Optional, "skip-tests\0", "Don't run tests after compiling.");
Param(&TCompilerConfig::SyntaxOnly, "syntax_only", Optional, "syntax-only\0", "Don't produce output or type-check, just syntactically validate the program.");
Param(&TCompilerConfig::TransientCc, "transient_cc", Optional, "transient-cc\0", "Remove the generated C++ intermediates after a successful compile, leaving only the linked package.");
Expand Down Expand Up @@ -98,6 +101,7 @@ class TCompilerConfig : public Base::TCmd {
bool MachineForm;
std::string OutputDir;
bool SemanticOnly;
bool ShowCompilerLocations;
std::string Source;
bool SkipTests;
bool SyntaxOnly;
Expand Down Expand Up @@ -285,7 +289,12 @@ int CompileCode(const TCompilerConfig &cmd) {
cerr << "compile failure: " << ex.what() << endl;
}
} catch (const TSourceError &src_error) {
cerr << src_error.GetPosRange() << ' ' << src_error.what() << endl;
cerr << src_error.GetPosRange() << ' ' << src_error.what();
/* An impossible error already names its location in the message. */
if (cmd.ShowCompilerLocations && !dynamic_cast<const TImpossibleError *>(&src_error)) {
cerr << ' ' << src_error.GetCodeLocation();
}
cerr << endl;
} catch (const exception &ex) {
cerr << "error: " << ex.what() << endl;
}
Expand Down
3 changes: 3 additions & 0 deletions orly/server/ws.cc
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,9 @@ class TWsImpl final
} catch (const TSourceError &src_error) {
reply["result"] = src_error.what();
reply["pos"] = AsStr(src_error.GetPosRange());
/* Kept out of "result" so a client shows a clean message; the
compiler line is there for whoever is reporting a bug (#557). */
reply["compiler_loc"] = AsStr(src_error.GetCodeLocation());
reply["status"] = "source_error";
} catch (const exception &ex) {
reply["result"] = ex.what();
Expand Down
17 changes: 17 additions & 0 deletions orly/symbol/stmt/mutate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <base/as_str.h>
#include <orly/error.h>
#include <orly/type/add_visitor.h>
#include <orly/type/addr.h>
#include <orly/type/comp_visitor.h>
#include <orly/type/div_visitor.h>
#include <orly/type/equal_visitor.h>
Expand All @@ -32,6 +33,8 @@
#include <orly/type/min_max_visitor.h>
#include <orly/type/mod_visitor.h>
#include <orly/type/mult_visitor.h>
#include <orly/type/mutable.h>
#include <orly/type/seq.h>
#include <orly/type/set_ops_visitor.h>
#include <orly/type/sub_visitor.h>
#include <orly/type/unwrap.h>
Expand Down Expand Up @@ -120,6 +123,20 @@ const TMutator &TMutate::GetMutator() const {
}

void TMutate::TypeCheck() const {
/* A mutation writes to the database, so its target must be a stored value:
`*<[key]>::(type)`, or something bound to one. Code gen assumes this and
dereferences the target's mutable type, so a plain value here crashed
orlyc, and a bare address fell through to the operator's visitor as
"This expression is invalid." (#557). Sequences are left for the visitor,
which has its own diagnostic for them. */
const Type::TType lhs_type = GetLhs()->GetExpr()->GetType();
if (!lhs_type.Is<Type::TSeq>() && !Type::UnwrapOptional(lhs_type).Is<Type::TMutable>()) {
throw TExprError(HERE, GetPosRange(), lhs_type.Is<Type::TAddr>()
? "The left side of a mutation must be a stored value, but this is an address. "
"Name the value stored at it with `*` and its type, as in `*<['counter']>::(int) += n`."
: "The left side of a mutation must be a stored value, such as `*<['counter']>::(int)`, "
"but this is a plain value.");
}
Type::TType dummy;
/* NOTE: It would be nice to use a templatized helper function for this but that would require
the helper function and the TMutateTypeVisitor to be in the header.
Expand Down
2 changes: 2 additions & 0 deletions tests/lang_tests/.xfail
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ general/unsorted/sequence_in_effecting.orly #75
# DESIGN -- their .state baselines pin the exact compiler error messages.
general/diag_mutable_type.orly #314
general/diag_assign_type_mismatch.orly #314
general/diag_mutate_address.orly #557
general/diag_mutate_plain_value.orly #557
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
],
[
"TypeCheck",
"23:40-23:52 [orly/symbol/stmt/mutate.cc]cannot assign a value of type std::string to a mutable holding int64_t"
"23:40-23:52 cannot assign a value of type std::string to a mutable holding int64_t"
]
]
]
13 changes: 13 additions & 0 deletions tests/lang_tests/general/.diag_mutate_address.orly.test.state
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
1,
[
[],
[
"Synth + Symbols"
],
[
"TypeCheck",
"24:28-24:47 The left side of a mutation must be a stored value, but this is an address. Name the value stored at it with `*` and its type, as in `*<['counter']>::(int) += n`."
]
]
]
13 changes: 13 additions & 0 deletions tests/lang_tests/general/.diag_mutate_plain_value.orly.test.state
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
1,
[
[],
[
"Synth + Symbols"
],
[
"TypeCheck",
"22:28-22:35 The left side of a mutation must be a stored value, such as `*<['counter']>::(int)`, but this is a plain value."
]
]
]
26 changes: 26 additions & 0 deletions tests/lang_tests/general/diag_mutate_address.orly
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* <orly/lang_tests/general/diag_mutate_address.orly>

Diagnostics pin (issue #557): a mutation whose target is a bare address
-- `<['counter']>` with no `*` or `::(type)` -- must say it needs the
stored value and show the form that works, not the generic "This
expression is invalid." This is the snippet the published quickstart led
a newcomer to write. EXPECTED to fail compilation (see .xfail); the
.state baseline pins the exact message.

Copyright 2010-2026 Atomic Kismet Company

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */

bump = ((true) effecting { <['counter']> += n; } ) where {
n = given::(int);
};
25 changes: 25 additions & 0 deletions tests/lang_tests/general/diag_mutate_plain_value.orly
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* <orly/lang_tests/general/diag_mutate_plain_value.orly>

Diagnostics pin (issue #557): a mutation whose target is a plain value,
not a stored one, used to crash orlyc with a segfault and no message.
It must report a clean error. EXPECTED to fail compilation (see
.xfail); the .state baseline pins the exact message.

Copyright 2010-2026 Atomic Kismet Company

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */

bump = ((true) effecting { x += n; } ) where {
n = given::(int);
x = 1;
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
],
[
"Code Gen",
"2:7-2:12 [orly/code_gen/builder.cc]Sequences in effecting blocks"
"2:7-2:12 Sequences in effecting blocks"
]
]
]
Loading