Skip to content

Commit

Permalink
misc: better diagnostic messages
Browse files Browse the repository at this point in the history
  • Loading branch information
baszalmstra committed Jul 25, 2020
1 parent 1b4a365 commit 23608f1
Show file tree
Hide file tree
Showing 16 changed files with 455 additions and 8 deletions.
19 changes: 19 additions & 0 deletions crates/mun_diagnostics/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "mun_diagnostics"
version = "0.1.0"
authors = ["The Mun Team <team@mun-lang.org>"]
edition = "2018"
description = "Provides in depth diagnostic information for compiler errors"
documentation = "https://docs.mun-lang.org/v0.2"
readme = "README.md"
homepage = "https://mun-lang.org"
repository = "https://github.com/mun-lang/mun"
license = "MIT OR Apache-2.0"
keywords = ["game", "hot-reloading", "language", "mun", "diagnostics"]
categories = ["game-development", "mun"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
hir = { version = "=0.2.0", path="../mun_hir", package="mun_hir" }
mun_syntax = { version = "=0.2.0", path = "../mun_syntax" }
56 changes: 56 additions & 0 deletions crates/mun_diagnostics/src/hir.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
mod access_unknown_field;
mod expected_function;
mod mismatched_type;
mod missing_fields;
mod possibly_unitialized_variable;
mod unresolved_type;
mod unresolved_value;

use crate::{Diagnostic, DiagnosticForWith, SourceAnnotation};
use hir::Diagnostic as HirDiagnostic;
use mun_syntax::TextRange;

// Provides conversion of a hir::Diagnostic to a crate::Diagnostic. This requires a database for
// most operations.
impl<DB: hir::HirDatabase> DiagnosticForWith<DB> for dyn hir::Diagnostic {
fn with_diagnostic<R, F: FnMut(&dyn Diagnostic) -> R>(&self, with: &DB, mut f: F) -> R {
if let Some(v) = self.downcast_ref::<hir::diagnostics::UnresolvedValue>() {
f(&unresolved_value::UnresolvedValue::new(with, v))
} else if let Some(v) = self.downcast_ref::<hir::diagnostics::UnresolvedType>() {
f(&unresolved_type::UnresolvedType::new(with, v))
} else if let Some(v) = self.downcast_ref::<hir::diagnostics::ExpectedFunction>() {
f(&expected_function::ExpectedFunction::new(with, v))
} else if let Some(v) = self.downcast_ref::<hir::diagnostics::MismatchedType>() {
f(&mismatched_type::MismatchedType::new(with, v))
} else if let Some(v) =
self.downcast_ref::<hir::diagnostics::PossiblyUninitializedVariable>()
{
f(&possibly_unitialized_variable::PossiblyUninitializedVariable::new(with, v))
} else if let Some(v) = self.downcast_ref::<hir::diagnostics::AccessUnknownField>() {
f(&access_unknown_field::AccessUnknownField::new(with, v))
} else if let Some(v) = self.downcast_ref::<hir::diagnostics::MissingFields>() {
f(&missing_fields::MissingFields::new(with, v))
} else {
f(&GenericHirDiagnostic { diagnostic: self })
}
}
}

/// Diagnostic handler for generic hir diagnostics
struct GenericHirDiagnostic<'diag> {
diagnostic: &'diag dyn hir::Diagnostic,
}

impl<'diag> Diagnostic for GenericHirDiagnostic<'diag> {
fn range(&self) -> TextRange {
self.diagnostic.highlight_range()
}

fn label(&self) -> String {
self.diagnostic.message()
}

fn primary_annotation(&self) -> Option<SourceAnnotation> {
None
}
}
44 changes: 44 additions & 0 deletions crates/mun_diagnostics/src/hir/access_unknown_field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use super::HirDiagnostic;
use crate::{Diagnostic, SourceAnnotation};
use hir::HirDisplay;
use mun_syntax::{ast, AstNode, TextRange};

pub struct AccessUnknownField<'db, 'diag, DB: hir::HirDatabase> {
db: &'db DB,
diag: &'diag hir::diagnostics::AccessUnknownField,
location: TextRange,
}

impl<'db, 'diag, DB: hir::HirDatabase> Diagnostic for AccessUnknownField<'db, 'diag, DB> {
fn range(&self) -> TextRange {
self.location
}

fn label(&self) -> String {
format!(
"no field `{}` on type `{}`",
self.diag.name,
self.diag.receiver_ty.display(self.db),
)
}

fn primary_annotation(&self) -> Option<SourceAnnotation> {
Some(SourceAnnotation {
range: self.location,
message: "unknown field".to_string(),
})
}
}

impl<'db, 'diag, DB: hir::HirDatabase> AccessUnknownField<'db, 'diag, DB> {
/// Constructs a new instance of `AccessUnknownField`
pub fn new(db: &'db DB, diag: &'diag hir::diagnostics::AccessUnknownField) -> Self {
let parse = db.parse(diag.file);

let location = ast::FieldExpr::cast(diag.expr.to_node(&parse.syntax_node()))
.map(|f| f.field_range())
.unwrap_or_else(|| diag.highlight_range());

AccessUnknownField { db, diag, location }
}
}
36 changes: 36 additions & 0 deletions crates/mun_diagnostics/src/hir/expected_function.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use super::HirDiagnostic;
use crate::{Diagnostic, SourceAnnotation};
use hir::HirDisplay;
use mun_syntax::TextRange;

pub struct ExpectedFunction<'db, 'diag, DB: hir::HirDatabase> {
db: &'db DB,
diag: &'diag hir::diagnostics::ExpectedFunction,
}

impl<'db, 'diag, DB: hir::HirDatabase> Diagnostic for ExpectedFunction<'db, 'diag, DB> {
fn range(&self) -> TextRange {
self.diag.highlight_range()
}

fn label(&self) -> String {
format!(
"expected function, found `{}`",
self.diag.found.display(self.db)
)
}

fn primary_annotation(&self) -> Option<SourceAnnotation> {
Some(SourceAnnotation {
range: self.diag.highlight_range(),
message: "not a function".to_owned(),
})
}
}

impl<'db, 'diag, DB: hir::HirDatabase> ExpectedFunction<'db, 'diag, DB> {
/// Constructs a new instance of `ExpectedFunction`
pub fn new(db: &'db DB, diag: &'diag hir::diagnostics::ExpectedFunction) -> Self {
ExpectedFunction { db, diag }
}
}
34 changes: 34 additions & 0 deletions crates/mun_diagnostics/src/hir/mismatched_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use super::HirDiagnostic;
use crate::{Diagnostic, SourceAnnotation};
use hir::HirDisplay;
use mun_syntax::TextRange;

pub struct MismatchedType<'db, 'diag, DB: hir::HirDatabase> {
db: &'db DB,
diag: &'diag hir::diagnostics::MismatchedType,
}

impl<'db, 'diag, DB: hir::HirDatabase> Diagnostic for MismatchedType<'db, 'diag, DB> {
fn range(&self) -> TextRange {
self.diag.highlight_range()
}

fn label(&self) -> String {
format!(
"expected `{}`, found `{}`",
self.diag.expected.display(self.db),
self.diag.found.display(self.db)
)
}

fn primary_annotation(&self) -> Option<SourceAnnotation> {
None
}
}

impl<'db, 'diag, DB: hir::HirDatabase> MismatchedType<'db, 'diag, DB> {
/// Constructs a new instance of `MismatchedType`
pub fn new(db: &'db DB, diag: &'diag hir::diagnostics::MismatchedType) -> Self {
MismatchedType { db, diag }
}
}
56 changes: 56 additions & 0 deletions crates/mun_diagnostics/src/hir/missing_fields.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use super::HirDiagnostic;
use crate::{Diagnostic, SourceAnnotation};
use hir::HirDisplay;
use mun_syntax::{ast, AstNode, TextRange};

pub struct MissingFields<'db, 'diag, DB: hir::HirDatabase> {
db: &'db DB,
diag: &'diag hir::diagnostics::MissingFields,
location: TextRange,
missing_fields: String,
}

impl<'db, 'diag, DB: hir::HirDatabase> Diagnostic for MissingFields<'db, 'diag, DB> {
fn range(&self) -> TextRange {
self.location
}

fn label(&self) -> String {
format!(
"missing fields {} in initializer of `{}`",
self.missing_fields,
self.diag.struct_ty.display(self.db)
)
}

fn primary_annotation(&self) -> Option<SourceAnnotation> {
Some(SourceAnnotation {
range: self.location,
message: self.missing_fields.clone(),
})
}
}

impl<'db, 'diag, DB: hir::HirDatabase> MissingFields<'db, 'diag, DB> {
/// Constructs a new instance of `MissingFields`
pub fn new(db: &'db DB, diag: &'diag hir::diagnostics::MissingFields) -> Self {
let parse = db.parse(diag.file);
let missing_fields = diag
.field_names
.iter()
.map(|n| format!("`{}`", n))
.collect::<Vec<String>>()
.join(", ");
let location = ast::RecordLit::cast(diag.fields.to_node(&parse.syntax_node()))
.and_then(|f| f.type_ref())
.map(|t| t.syntax().text_range())
.unwrap_or_else(|| diag.highlight_range());

MissingFields {
db,
diag,
location,
missing_fields,
}
}
}
41 changes: 41 additions & 0 deletions crates/mun_diagnostics/src/hir/possibly_unitialized_variable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use super::HirDiagnostic;
use crate::{Diagnostic, SourceAnnotation};
use mun_syntax::TextRange;

pub struct PossiblyUninitializedVariable<'db, 'diag, DB: hir::HirDatabase> {
_db: &'db DB,
diag: &'diag hir::diagnostics::PossiblyUninitializedVariable,
value_name: String,
}

impl<'db, 'diag, DB: hir::HirDatabase> Diagnostic
for PossiblyUninitializedVariable<'db, 'diag, DB>
{
fn range(&self) -> TextRange {
self.diag.highlight_range()
}

fn label(&self) -> String {
format!("use of possibly-uninitialized `{}`", self.value_name)
}

fn primary_annotation(&self) -> Option<SourceAnnotation> {
None
}
}

impl<'db, 'diag, DB: hir::HirDatabase> PossiblyUninitializedVariable<'db, 'diag, DB> {
/// Constructs a new instance of `PossiblyUninitializedVariable`
pub fn new(db: &'db DB, diag: &'diag hir::diagnostics::PossiblyUninitializedVariable) -> Self {
let parse = db.parse(diag.file);

// Get the text of the value as a string
let value_name = diag.pat.to_node(&parse.syntax_node()).text().to_string();

PossiblyUninitializedVariable {
_db: db,
diag,
value_name,
}
}
}
47 changes: 47 additions & 0 deletions crates/mun_diagnostics/src/hir/unresolved_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use super::HirDiagnostic;
use crate::{Diagnostic, SourceAnnotation};
use mun_syntax::{AstNode, TextRange};

pub struct UnresolvedType<'db, 'diag, DB: hir::HirDatabase> {
_db: &'db DB,
diag: &'diag hir::diagnostics::UnresolvedType,
value_name: String,
}

impl<'db, 'diag, DB: hir::HirDatabase> Diagnostic for UnresolvedType<'db, 'diag, DB> {
fn range(&self) -> TextRange {
self.diag.highlight_range()
}

fn label(&self) -> String {
format!("cannot find type `{}` in this scope", self.value_name)
}

fn primary_annotation(&self) -> Option<SourceAnnotation> {
Some(SourceAnnotation {
range: self.diag.highlight_range(),
message: "not found in this scope".to_owned(),
})
}
}

impl<'db, 'diag, DB: hir::HirDatabase> UnresolvedType<'db, 'diag, DB> {
/// Constructs a new instance of `UnresolvedType`
pub fn new(db: &'db DB, diag: &'diag hir::diagnostics::UnresolvedType) -> Self {
let parse = db.parse(diag.file);

// Get the text of the value as a string
let value_name = diag
.type_ref
.to_node(&parse.syntax_node())
.syntax()
.text()
.to_string();

UnresolvedType {
_db: db,
diag,
value_name,
}
}
}
42 changes: 42 additions & 0 deletions crates/mun_diagnostics/src/hir/unresolved_value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use super::HirDiagnostic;
use crate::{Diagnostic, SourceAnnotation};
use mun_syntax::{AstNode, TextRange};

pub struct UnresolvedValue<'db, 'diag, DB: hir::HirDatabase> {
_db: &'db DB,
diag: &'diag hir::diagnostics::UnresolvedValue,
value_name: String,
}

impl<'db, 'diag, DB: hir::HirDatabase> Diagnostic for UnresolvedValue<'db, 'diag, DB> {
fn range(&self) -> TextRange {
self.diag.highlight_range()
}

fn label(&self) -> String {
format!("cannot find value `{}` in this scope", self.value_name)
}

fn primary_annotation(&self) -> Option<SourceAnnotation> {
Some(SourceAnnotation {
range: self.diag.highlight_range(),
message: "not found in this scope".to_owned(),
})
}
}

impl<'db, 'diag, DB: hir::HirDatabase> UnresolvedValue<'db, 'diag, DB> {
/// Constructs a new instance of `UnresolvedValue`
pub fn new(db: &'db DB, diag: &'diag hir::diagnostics::UnresolvedValue) -> Self {
let parse = db.parse(diag.file);

// Get the text of the value as a string
let value_name = diag.expr.to_node(&parse.tree().syntax()).text().to_string();

UnresolvedValue {
_db: db,
diag,
value_name,
}
}
}
Loading

0 comments on commit 23608f1

Please sign in to comment.