Add tint::diag namespace for diagnostics

Diagnostics will be used for printing parser / validator error mesasges.
Diagnostics are collected into a `diag::List`, and can then be formatted into a human readable message with `diag::Formatter`.

Bug: tint:282
Change-Id: I8bbef3db22b72d62cb9467c878d9a346890589ad
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/31480
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
This commit is contained in:
Ben Clayton 2020-11-02 15:41:08 +00:00 committed by Commit Bot service account
parent 580d6c7f3e
commit f0740ae0f2
7 changed files with 453 additions and 0 deletions

View File

@ -377,6 +377,10 @@ source_set("libtint_core_src") {
"src/ast/workgroup_decoration.h", "src/ast/workgroup_decoration.h",
"src/context.cc", "src/context.cc",
"src/context.h", "src/context.h",
"src/diagnostic/diagnostic.cc",
"src/diagnostic/diagnostic.h",
"src/diagnostic/formatter.cc",
"src/diagnostic/formatter.h",
"src/inspector/entry_point.cc", "src/inspector/entry_point.cc",
"src/inspector/entry_point.h", "src/inspector/entry_point.h",
"src/inspector/inspector.cc", "src/inspector/inspector.cc",
@ -767,6 +771,7 @@ source_set("tint_unittests_core_src") {
"src/ast/variable_decl_statement_test.cc", "src/ast/variable_decl_statement_test.cc",
"src/ast/variable_test.cc", "src/ast/variable_test.cc",
"src/ast/workgroup_decoration_test.cc", "src/ast/workgroup_decoration_test.cc",
"src/diagnostic/formatter_test.cc",
"src/inspector/inspector_test.cc", "src/inspector/inspector_test.cc",
"src/scope_stack_test.cc", "src/scope_stack_test.cc",
"src/transform/bound_array_accessors_transform_test.cc", "src/transform/bound_array_accessors_transform_test.cc",

View File

@ -198,6 +198,10 @@ set(TINT_LIB_SRCS
ast/workgroup_decoration.h ast/workgroup_decoration.h
context.cc context.cc
context.h context.h
diagnostic/diagnostic.cc
diagnostic/diagnostic.h
diagnostic/formatter.cc
diagnostic/formatter.h
inspector/entry_point.cc inspector/entry_point.cc
inspector/entry_point.h inspector/entry_point.h
inspector/inspector.cc inspector/inspector.cc
@ -377,6 +381,7 @@ set(TINT_TEST_SRCS
ast/variable_decl_statement_test.cc ast/variable_decl_statement_test.cc
ast/variable_test.cc ast/variable_test.cc
ast/workgroup_decoration_test.cc ast/workgroup_decoration_test.cc
diagnostic/formatter_test.cc
inspector/inspector_test.cc inspector/inspector_test.cc
scope_stack_test.cc scope_stack_test.cc
transform/bound_array_accessors_transform_test.cc transform/bound_array_accessors_transform_test.cc

View File

@ -0,0 +1,25 @@
// Copyright 2020 The Tint Authors.
//
// 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.
#include "src/diagnostic/diagnostic.h"
namespace tint {
namespace diag {
List::List() = default;
List::List(std::initializer_list<Diagnostic> list) : entries_(list) {}
List::~List() = default;
} // namespace diag
} // namespace tint

View File

@ -0,0 +1,79 @@
// Copyright 2020 The Tint Authors.
//
// 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.
#ifndef SRC_DIAGNOSTIC_DIAGNOSTIC_H_
#define SRC_DIAGNOSTIC_DIAGNOSTIC_H_
#include <initializer_list>
#include <string>
#include <utility>
#include <vector>
#include "src/source.h"
namespace tint {
namespace diag {
/// Severity is an enumerator of diagnostic severities.
enum class Severity { Info, Warning, Error, Fatal };
/// @return true iff |a| is more than, or of equal severity to |b|.
inline bool operator>=(Severity a, Severity b) {
return static_cast<int>(a) >= static_cast<int>(b);
}
/// Diagnostic holds all the information for a single compiler diagnostic
/// message.
class Diagnostic {
public:
Severity severity = Severity::Error;
Source source;
std::string message;
};
/// List is a container of Diagnostic messages.
class List {
public:
using iterator = std::vector<Diagnostic>::const_iterator;
List();
List(std::initializer_list<Diagnostic> list);
~List();
void add(Diagnostic&& diag) {
entries_.emplace_back(std::move(diag));
if (diag.severity >= Severity::Error) {
contains_errors_ = true;
}
}
/// @returns true iff the diagnostic list contains errors diagnostics (or of
/// higher severity).
bool contains_errors() const { return contains_errors_; }
/// @returns the number of entries in the list.
size_t count() const { return entries_.size(); }
/// @returns the first diagnostic in the list.
iterator begin() const { return entries_.begin(); }
/// @returns the last diagnostic in the list.
iterator end() const { return entries_.end(); }
private:
std::vector<Diagnostic> entries_;
bool contains_errors_ = false;
};
} // namespace diag
} // namespace tint
#endif // SRC_DIAGNOSTIC_DIAGNOSTIC_H_

152
src/diagnostic/formatter.cc Normal file
View File

@ -0,0 +1,152 @@
// Copyright 2020 The Tint Authors.
//
// 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.
#include "src/diagnostic/formatter.h"
#include <algorithm>
#include <sstream>
#include "src/diagnostic/diagnostic.h"
namespace tint {
namespace diag {
namespace {
template <typename CharT, typename Traits>
std::basic_ostream<CharT, Traits>& operator<<(
std::basic_ostream<CharT, Traits>& stream,
Severity severity) {
switch (severity) {
case Severity::Info:
stream << "info";
break;
case Severity::Warning:
stream << "warning";
break;
case Severity::Error:
stream << "error";
break;
case Severity::Fatal:
stream << "fatal";
break;
}
return stream;
}
template <typename CharT, typename Traits>
std::basic_ostream<CharT, Traits>& operator<<(
std::basic_ostream<CharT, Traits>& stream,
const Source::Location& location) {
if (location.line > 0) {
stream << location.line;
if (location.column > 0) {
stream << ":" << location.column;
}
}
return stream;
}
class BasicFormatter : public Formatter {
public:
BasicFormatter(bool print_file, bool print_severity, bool print_line)
: print_file_(print_file),
print_severity_(print_severity),
print_line_(print_line) {}
std::string format(const List& list) const override {
bool first = true;
std::stringstream ss;
for (auto diag : list) {
if (!first) {
ss << std::endl;
}
format(diag, ss);
first = false;
}
return ss.str();
}
private:
void format(const Diagnostic& diag, std::stringstream& ss) const {
auto const& src = diag.source;
auto const& rng = src.range;
if (print_file_ && src.file != nullptr && !src.file->path.empty()) {
ss << src.file->path;
if (rng.begin.line > 0) {
ss << ":" << rng.begin;
}
} else {
ss << rng.begin;
}
if (print_severity_) {
ss << " " << diag.severity;
}
ss << ": " << diag.message;
if (print_line_ && src.file != nullptr && rng.begin.line > 0) {
ss << std::endl;
for (size_t line = rng.begin.line; line <= rng.end.line; line++) {
if (line < src.file->lines.size() + 1) {
auto len = src.file->lines[line - 1].size();
ss << src.file->lines[line - 1];
ss << std::endl;
if (line == rng.begin.line && line == rng.end.line) {
// Single line
repeat(' ', rng.begin.column - 1, ss);
repeat('^', std::max<size_t>(rng.end.column - rng.begin.column, 1),
ss);
} else if (line == rng.begin.line) {
// Start of multi-line
repeat(' ', rng.begin.column - 1, ss);
repeat('^', len - (rng.begin.column - 1), ss);
} else if (line == rng.end.line) {
// End of multi-line
repeat('^', rng.end.column - 1, ss);
} else {
// Middle of multi-line
repeat('^', len, ss);
}
ss << std::endl;
}
}
}
}
void repeat(char c, size_t n, std::stringstream& ss) const {
while (n-- > 0) {
ss << c;
}
}
const bool print_file_ = false;
const bool print_severity_ = false;
const bool print_line_ = false;
};
} // namespace
std::unique_ptr<Formatter> Formatter::create(bool print_file,
bool print_severity,
bool print_line) {
return std::make_unique<BasicFormatter>(print_file, print_severity,
print_line);
}
Formatter::~Formatter() = default;
} // namespace diag
} // namespace tint

View File

@ -0,0 +1,47 @@
// Copyright 2020 The Tint Authors.
//
// 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.
#ifndef SRC_DIAGNOSTIC_FORMATTER_H_
#define SRC_DIAGNOSTIC_FORMATTER_H_
#include <memory>
#include <string>
namespace tint {
namespace diag {
class List;
/// Formatters are used to format a list of diagnostics into a human readable
/// string.
class Formatter {
public:
/// @returns a basic diagnostic formatter
/// @param print_file include the file path for each diagnostic
/// @param print_severity include the severity for each diagnostic
/// @param print_line include the source line(s) for the diagnostic
static std::unique_ptr<Formatter> create(bool print_file,
bool print_severity,
bool print_line);
virtual ~Formatter();
/// @return the human readable list of diagnostics formatted to a string.
virtual std::string format(const List&) const = 0;
};
} // namespace diag
} // namespace tint
#endif // SRC_DIAGNOSTIC_FORMATTER_H_

View File

@ -0,0 +1,140 @@
// Copyright 2020 The Tint Authors.
//
// 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.
#include "src/diagnostic/formatter.h"
#include "src/diagnostic/diagnostic.h"
#include "gtest/gtest.h"
namespace tint {
namespace diag {
namespace {
constexpr const char* content =
R"(the cat says meow
the dog says woof
the snake says quack
the snail says ???
)";
class DiagFormatterTest : public testing::Test {
public:
Source::File file{"file.name", content};
Diagnostic diag_info{Severity::Info,
Source{Source::Range{Source::Location{1, 14}}, &file},
"purr"};
Diagnostic diag_warn{Severity::Warning,
Source{Source::Range{{2, 14}, {2, 18}}, &file}, "grrr"};
Diagnostic diag_err{Severity::Error,
Source{Source::Range{{3, 16}, {3, 21}}, &file}, "hiss"};
Diagnostic diag_fatal{Severity::Fatal,
Source{Source::Range{{4, 16}, {4, 19}}, &file},
"nothing"};
};
TEST_F(DiagFormatterTest, Simple) {
auto fmt = Formatter::create(false, false, false);
auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal});
auto* expect = R"(1:14: purr
2:14: grrr
3:16: hiss
4:16: nothing)";
ASSERT_EQ(expect, got);
}
TEST_F(DiagFormatterTest, WithFile) {
auto fmt = Formatter::create(true, false, false);
auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal});
auto* expect = R"(file.name:1:14: purr
file.name:2:14: grrr
file.name:3:16: hiss
file.name:4:16: nothing)";
ASSERT_EQ(expect, got);
}
TEST_F(DiagFormatterTest, WithSeverity) {
auto fmt = Formatter::create(false, true, false);
auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal});
auto* expect = R"(1:14 info: purr
2:14 warning: grrr
3:16 error: hiss
4:16 fatal: nothing)";
ASSERT_EQ(expect, got);
}
TEST_F(DiagFormatterTest, WithLine) {
auto fmt = Formatter::create(false, false, true);
auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal});
auto* expect = R"(1:14: purr
the cat says meow
^
2:14: grrr
the dog says woof
^^^^
3:16: hiss
the snake says quack
^^^^^
4:16: nothing
the snail says ???
^^^
)";
ASSERT_EQ(expect, got);
}
TEST_F(DiagFormatterTest, BasicWithFileSeverityLine) {
auto fmt = Formatter::create(true, true, true);
auto got = fmt->format(List{diag_info, diag_warn, diag_err, diag_fatal});
auto* expect = R"(file.name:1:14 info: purr
the cat says meow
^
file.name:2:14 warning: grrr
the dog says woof
^^^^
file.name:3:16 error: hiss
the snake says quack
^^^^^
file.name:4:16 fatal: nothing
the snail says ???
^^^
)";
ASSERT_EQ(expect, got);
}
TEST_F(DiagFormatterTest, BasicWithMultiLine) {
Diagnostic multiline{Severity::Warning,
Source{Source::Range{{2, 9}, {4, 15}}, &file},
"multiline"};
auto fmt = Formatter::create(false, false, true);
auto got = fmt->format(List{multiline});
auto* expect = R"(2:9: multiline
the dog says woof
^^^^^^^^^
the snake says quack
^^^^^^^^^^^^^^^^^^^^
the snail says ???
^^^^^^^^^^^^^^
)";
ASSERT_EQ(expect, got);
}
} // namespace
} // namespace diag
} // namespace tint