use std::rc::Rc;
use crate::core::{
KernelForm, KernelType, Keyword, ModulePath, Namespace, PassingAspect, PassingDifference,
};
use crate::diagnostic::{Counted, Diagnostic, DiagnosticCode, Label, MissingName, Suggestion};
use crate::interpret::InterpretProblems;
use crate::reader::Form;
use crate::source::Span;
pub struct ExpandProblems;
impl ExpandProblems {
const QUOTE_SHORTHAND: char = '\'';
const CODE_REPLACEMENTS: [(&'static str, &'static str, &'static str); 9] = [
("add", "number-add", "code"),
("sub", "number-sub", "code"),
("lt", "number-lt", "code"),
("le", "number-le", "code"),
("gt", "number-gt", "code"),
("ge", "number-ge", "code"),
("eq", "value-eq", "code"),
("not", "bool-not", "code"),
("map", "map-list", "prelude/macros"),
];
pub fn malformed_type_template(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::BAD_TYPE_TEMPLATE,
"this type template has a malformed placeholder",
Label::new(span, "a placeholder is `{`, a number and `}`"),
)
.with_help("write a literal brace as `{{` or `}}`")
}
pub fn unknown_type_placeholder(name: &str, parameters: &[String], span: Span) -> Diagnostic {
let help = if parameters.is_empty() {
format!("add `({name})` to the template's `where`")
} else {
let names: Vec<String> = parameters
.iter()
.map(|parameter| format!("`{parameter}`"))
.collect();
format!("the type parameters are {}", names.join(", "))
};
Diagnostic::error(
DiagnosticCode::TYPE_PARAMETER_PLACEHOLDER,
format!("`{{{name}}}` names no type parameter"),
Label::new(span, "this template's `where` has no such type parameter"),
)
.with_help(help)
}
pub fn type_placeholder_in_type_template(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::TYPE_PLACEHOLDER_IN_RUST_TYPE,
format!("a `rust-type` template cannot name `{name}`"),
Label::new(
span,
"`rust-type` templates name their parameters by number",
),
)
.with_help("write the placeholder as `{0}`")
}
pub fn rust_type_clauses_need_one_parameter(
name: &str,
parameters: usize,
span: Span,
) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::RUST_TYPE_CLAUSES_NEED_ONE_PARAMETER,
"a `rust-type` with clauses takes one type parameter",
Label::new(
span,
format!(
"this type has {}",
Counted::noun(parameters, "type parameter")
),
),
)
.with_help(format!(
"give the type one parameter, as in `(rust-type ({name} item) ...)`"
))
}
pub fn rust_clauses_need_one_parameter(parameters: usize, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::RUST_CLAUSES_NEED_ONE_PARAMETER,
"a `rust` with clauses takes one type parameter",
Label::new(
span,
format!(
"this type binds {}",
Counted::noun(parameters, "type parameter")
),
),
)
.with_help("bind one parameter, as in `(where ((s source)) (fn ((ref s)) string))`")
}
pub fn structural_clause_needs_one_method(clause: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MALFORMED_STRUCTURAL_CLAUSE,
"a `structural` clause needs one method that takes `(ref self)`",
Label::new(clause, "this clause fills the body of one method"),
)
.with_help("declare one `(method name (fn ((ref self) ...) result))` in this `rust-trait`")
}
pub fn structural_clause_on_derived_trait(name: &str, path: &str, clause: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MALFORMED_STRUCTURAL_CLAUSE,
format!("`{name}` binds `{path}`, which `derive` already writes"),
Label::new(
clause,
"every struct would get both the derive and this impl",
),
)
.with_help(
"keep the clause off this `rust-trait`, or bind a trait that `derive` does not write",
)
}
pub fn structural_clause_on_bound_path(
name: &str,
path: &str,
existing: &str,
clause: Span,
) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MALFORMED_STRUCTURAL_CLAUSE,
format!("`{name}` binds `{path}`, which `{existing}` already binds"),
Label::new(clause, "the two traits would infer two impls of one Rust trait"),
)
.with_help(
"keep the clause off this `rust-trait`, or give it a Rust path that no other `rust-trait` binds",
)
}
pub fn repeated_structural_clause(repeated: Span, first: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MALFORMED_STRUCTURAL_CLAUSE,
"this `rust-trait` has a second `structural` clause",
Label::new(repeated, "second clause"),
)
.with_label(Label::new(first, "first clause"))
.with_help("merge the two clauses into one")
}
pub fn second_structural_path(
name: &str,
path: &str,
first: &str,
clause: Span,
first_clause: Span,
) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MALFORMED_STRUCTURAL_CLAUSE,
format!("`{name}` binds `{path}`, which `{first}` already infers"),
Label::new(
clause,
"the two clauses would infer two impls of one Rust trait",
),
)
.with_label(Label::new(
first_clause,
format!("`{first}` infers `{path}` here"),
))
.with_help("keep one of the two clauses, or give this `rust-trait` another Rust path")
}
pub fn missing_structural_key(key: &str, clause: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MALFORMED_STRUCTURAL_CLAUSE,
format!("`{key}` is missing from this `structural` clause"),
Label::new(clause, format!("this clause has no `{key}`")),
)
.with_help(format!("add `{key}` and its template string"))
}
pub fn unknown_structural_key(written: &str, keys: &[String], span: Span) -> Diagnostic {
let listed: Vec<String> = keys.iter().map(|key| format!("`{key}`")).collect();
Diagnostic::error(
DiagnosticCode::MALFORMED_STRUCTURAL_CLAUSE,
format!("`{written}` is not a key of a `structural` clause"),
Label::new(span, "expected a key here"),
)
.with_help(format!("a `structural` clause takes {}", listed.join(", ")))
}
pub fn repeated_structural_key(key: &str, repeated: Span, first: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MALFORMED_STRUCTURAL_CLAUSE,
format!("`{key}` appears twice in this `structural` clause"),
Label::new(repeated, "repeated here"),
)
.with_label(Label::new(first, "first given here"))
.with_help(format!("remove one of the two `{key}` templates"))
}
pub fn structural_key_without_template(key: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MALFORMED_STRUCTURAL_CLAUSE,
format!("`{key}` has no template string after it"),
Label::new(span, "expected a string after this key"),
)
.with_help(format!(
"write the Rust text after the key, as in `{key} \"...\"`"
))
}
pub fn unfilled_structural_placeholder(
key: &str,
placeholder: &str,
accepted: &[String],
template: Span,
) -> Diagnostic {
let listed: Vec<String> = accepted
.iter()
.map(|accepted| format!("`{accepted}`"))
.collect();
Diagnostic::error(
DiagnosticCode::MALFORMED_STRUCTURAL_CLAUSE,
format!("`{key}` cannot fill `{placeholder}`"),
Label::new(template, format!("this template holds `{placeholder}`")),
)
.with_help(format!(
"`{key}` takes {}, and `{{{{` or `}}}}` for a literal brace",
listed.join(", ")
))
}
pub fn clause_passes_arguments_unlike_template(
difference: PassingDifference,
clause: Span,
template: Span,
) -> Diagnostic {
let (clause_label, template_label) = match difference {
PassingDifference::Argument(argument, aspect) => (
Self::passing_aspect_label(argument, aspect),
format!("unlike the template's `{{{argument}}}` here"),
),
PassingDifference::CallOnlyInClause => (
"this clause calls a function".to_string(),
"the template calls no function".to_string(),
),
PassingDifference::CallOnlyInTemplate => (
"this clause calls no function".to_string(),
"the template calls a function".to_string(),
),
};
Diagnostic::error(
DiagnosticCode::CLAUSE_PASSES_ARGUMENTS_UNLIKE_TEMPLATE,
"a `rust` clause must pass its arguments as its template does",
Label::new(clause, clause_label),
)
.with_label(Label::new(template, template_label))
.with_help(
"write each placeholder as the template writes it, with the same borrow, receiver, index or call",
)
}
fn passing_aspect_label(argument: usize, aspect: PassingAspect) -> String {
let placeholder = format!("`{{{argument}}}`");
match aspect {
PassingAspect::Use => format!("this clause differs in whether it uses {placeholder}"),
PassingAspect::Borrow => format!("this clause borrows {placeholder} differently"),
PassingAspect::Receiver => {
format!("this clause differs in whether {placeholder} receives a method call")
}
PassingAspect::Index => {
format!("this clause differs in whether it indexes {placeholder}")
}
PassingAspect::Dereference => {
format!("this clause differs in whether it dereferences {placeholder}")
}
PassingAspect::Call => format!("this clause differs in whether it calls {placeholder}"),
PassingAspect::Operand => {
format!("this clause differs in whether {placeholder} is an operator's operand")
}
}
}
pub fn malformed_template(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MALFORMED_TEMPLATE,
"this Rust template has a malformed placeholder",
Label::new(span, "a placeholder is `{`, a number and `}`"),
)
.with_help("write a literal brace as `{{` or `}}`")
}
pub fn borrowed_type_placeholder(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::BORROWED_TYPE_PLACEHOLDER,
"a type template cannot borrow a placeholder",
Label::new(span, "`{&...}` only makes sense for a value"),
)
.with_help("write the placeholder as `{0}`")
}
pub fn placeholder_without_parameter(
index: usize,
parameters: usize,
name: &str,
template: &str,
placeholders: usize,
span: Span,
) -> Diagnostic {
let names: Vec<String> = (0..placeholders)
.map(|position| char::from(b'a' + position as u8).to_string())
.collect();
Diagnostic::error(
DiagnosticCode::PLACEHOLDER_WITHOUT_PARAMETER,
format!("`{{{index}}}` names no type parameter"),
Label::new(
span,
format!(
"this type has {}",
Counted::noun(parameters, "type parameter")
),
),
)
.with_help(format!(
"declare the type parameters on the name, as in `(rust-type ({name} {}) \"{template}\")`",
names.join(" ")
))
}
pub fn unused_type_placeholder(index: usize, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::UNUSED_TYPE_PLACEHOLDER,
"the template never uses this type parameter",
Label::new(span, format!("its placeholder is `{{{index}}}`")),
)
.with_help(format!(
"use `{{{index}}}` in the template, or remove the parameter"
))
}
pub fn unbound(
name: &str,
namespace: Namespace,
span: Span,
similar: Option<Rc<str>>,
) -> Diagnostic {
Self::unbound_report(name, namespace, span, similar).with_missing_name(MissingName {
name: name.into(),
namespace,
})
}
fn unbound_report(
name: &str,
namespace: Namespace,
span: Span,
similar: Option<Rc<str>>,
) -> Diagnostic {
if namespace == Namespace::Type && name.starts_with(Self::QUOTE_SHORTHAND) {
return Diagnostic::error(
DiagnosticCode::UNBOUND_NAME,
format!("cannot find lifetime `{name}` in this scope"),
Label::new(span, "not found in this scope"),
)
.with_help("declare the lifetime in the enclosing `where` or type head");
}
let what = Self::describe(namespace);
let diagnostic = Diagnostic::error(
DiagnosticCode::UNBOUND_NAME,
format!("cannot find {what}`{name}` in this scope"),
Label::new(span, "not found in this scope"),
);
let diagnostic = match similar {
Some(similar) => diagnostic.with_suggestion(
"a name with a similar spelling exists",
Suggestion::new(span, similar.to_string()),
),
None => diagnostic,
};
if namespace == Namespace::Value && name.starts_with(Self::QUOTE_SHORTHAND) {
diagnostic.with_help(format!(
"there is no quote shorthand; write `(quote {})`",
&name[1..]
))
} else {
diagnostic
}
}
pub fn bounded_lifetime(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::BOUNDED_TYPE_PARAMETER,
"a lifetime cannot require traits",
Label::new(span, "traits given here"),
)
.with_help("write the lifetime alone, as in `'a`")
}
pub fn ambiguous(name: &str, span: Span, definitions: Vec<Span>) -> Diagnostic {
let diagnostic = Diagnostic::error(
DiagnosticCode::AMBIGUOUS_NAME,
format!("`{name}` is ambiguous"),
Label::new(span, "this name could refer to more than one binding"),
);
definitions
.into_iter()
.fold(diagnostic, |diagnostic, definition| {
diagnostic.with_label(Label::new(definition, "one candidate is defined here"))
})
}
pub fn enum_rust_template(span: Span, label: &str, example: &str) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::ENUM_RUST_TEMPLATE,
"a `rust` template needs one variant with a part for each placeholder",
Label::new(span, label),
)
.with_help(format!(
"write one positional part for each placeholder, as in `{example}`"
))
}
pub fn duplicate(name: &str, span: Span, previous: Option<Span>) -> Diagnostic {
let diagnostic = Diagnostic::error(
DiagnosticCode::DUPLICATE_DEFINITION,
format!("`{name}` is defined more than once"),
Label::new(span, "defined again here"),
);
match previous {
Some(previous) => diagnostic.with_label(Label::new(previous, "first defined here")),
None => diagnostic.with_note(format!(
"`{name}` is already defined by the prelude or an import"
)),
}
}
pub fn set_aside(input: &SetAsideInput<'_>, error: &Diagnostic) -> Diagnostic {
let primary = error.primary();
let warning = Diagnostic::warning(
DiagnosticCode::INPUT_SET_ASIDE,
input.title(),
Label::new(primary.span(), primary.message()),
);
let labeled = error
.labels()
.skip(1)
.fold(warning, |warning, label| warning.with_label(label.clone()));
let noted = error.notes().iter().fold(
labeled.with_note(Self::error_line(error)),
|warning, note| warning.with_note(note.clone()),
);
let helped = Self::with_helps_of(noted, error).with_help(input.enter_again());
match error.missing_name() {
Some(missing) => helped.with_missing_name(missing.clone()),
None => helped,
}
}
pub fn dropped(input: &SetAsideInput<'_>, replacing: &str, span: Span) -> Diagnostic {
Diagnostic::warning(
DiagnosticCode::INPUT_SET_ASIDE,
input.title(),
Label::new(span, "set aside"),
)
.with_note(format!(
"`{replacing}` replaced the input that defined {}",
input.listed_names()
))
.with_help(input.enter_again())
}
pub fn set_aside_reason(replacing: &str, dropped: &str) -> String {
format!("`{replacing}` replaced the input that defined `{dropped}`")
}
pub fn set_aside_note(setter: &str, name: &str, reason: &str) -> String {
format!("`{setter}` set aside `{name}`: {reason}")
}
pub fn would_set_aside_note(setter: &str, name: &str, reason: &str) -> String {
format!("`{setter}` would set aside `{name}`: {reason}")
}
pub fn no_longer_defines_note(input: &str, name: &str, setter: &str) -> String {
format!("`{input}` no longer defines `{name}` after `{setter}`")
}
pub fn checks_no_longer(diagnostic: Diagnostic) -> Diagnostic {
diagnostic.with_help("enter a definition to set aside the inputs that no longer check")
}
fn error_line(error: &Diagnostic) -> String {
let code = error.code().expect("an error has a code");
format!("error[{}]: {}", code.as_str(), error.title())
}
fn with_helps_of(warning: Diagnostic, error: &Diagnostic) -> Diagnostic {
error
.helps()
.iter()
.fold(warning, |warning, help| match help.suggestion() {
Some(suggestion) => warning.with_suggestion(help.message(), suggestion.clone()),
None => warning.with_help(help.message()),
})
}
pub fn unknown_module(path: &ModulePath, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::UNKNOWN_MODULE,
format!("cannot find module `{path}`"),
Label::new(span, "no module with this path"),
)
.with_help(
"a module path names a file relative to the root directory, without its extension",
)
}
pub fn missing_export(
path: &ModulePath,
name: &str,
span: Span,
similar: Option<Rc<str>>,
) -> Diagnostic {
let diagnostic = Diagnostic::error(
DiagnosticCode::MISSING_EXPORT,
format!("module `{path}` does not export `{name}`"),
Label::new(span, "not exported"),
);
match similar {
Some(similar) => diagnostic.with_suggestion(
"an export with a similar name exists",
Suggestion::new(span, similar.to_string()),
),
None => diagnostic.with_help(format!("add `{name}` to the `export` list of `{path}`")),
}
}
pub fn import_cycle(path: &ModulePath, span: Span, cycle: Vec<String>) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::IMPORT_CYCLE,
format!("importing `{path}` creates a cycle"),
Label::new(span, "this import closes the cycle"),
)
.with_note(format!(
"the modules import each other: {}",
cycle.join(" -> ")
))
.with_help("move the shared definitions into a module that both can import")
}
pub fn not_a_definition(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::NOT_A_DEFINITION,
"expected a definition at module level",
Label::new(span, "this is not a definition"),
)
.with_help("put expressions inside a function, such as `(def run (fn () ...))`")
}
pub fn evaluated_definition(span: Span) -> Diagnostic {
Diagnostic::note(
"the module defines this form, so it does not run",
Label::new(span, "a definition of the module"),
)
}
pub fn module_level_only(definer: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MODULE_LEVEL_ONLY,
format!("`{definer}` can only appear at module level"),
Label::new(span, "not allowed inside an expression"),
)
.with_help("use `local` to bind a name inside a body")
}
pub fn binding_outside_body(binding_form: KernelForm, span: Span) -> Diagnostic {
let name = binding_form.name();
Diagnostic::error(
DiagnosticCode::LET_OUTSIDE_BODY,
format!("`{name}` can only appear directly in a body"),
Label::new(span, "not allowed here"),
)
.with_help(format!(
"wrap the `{name}` and the code that uses it in `(do ...)`"
))
}
pub fn macro_outside_def(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MACRO_OUTSIDE_DEF,
"`macro` must be the value of a module-level `def`",
Label::new(span, "not allowed here"),
)
.with_help("write `(def name (macro (form) body ...))`")
}
pub fn empty_call(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::EMPTY_CALL,
"`()` is not an expression",
Label::new(span, "an empty list has nothing to call"),
)
.with_suggestion(
"the unit value is written `(do)`",
Suggestion::new(span, "(do)"),
)
}
pub fn form_as_value(kernel: KernelForm, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::FORM_AS_VALUE,
format!("`{}` is a form, not a value", kernel.name()),
Label::new(span, "used as a value here"),
)
.with_help(format!("write it as `{}`", kernel.shape()))
}
pub fn macro_as_value(name: &str, span: Span, function: Option<Rc<str>>) -> Diagnostic {
let diagnostic = Diagnostic::error(
DiagnosticCode::MACRO_AS_VALUE,
format!("`{name}` is a macro, not a value"),
Label::new(span, "used as a value here"),
);
match function {
Some(function) => diagnostic.with_suggestion(
format!("a macro can only be called; pass `{function}` instead"),
Suggestion::new(span, function.to_string()),
),
None => diagnostic.with_help("a macro can only be called; pass a function instead"),
}
}
pub fn wrong_namespace(name: &str, expected: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::WRONG_NAMESPACE,
format!("expected {expected}, found `{name}`"),
Label::new(span, format!("not {expected}")),
)
}
pub fn recur_outside_tail(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::RECUR_OUTSIDE_TAIL,
"`recur` is not in tail position",
Label::new(span, "this `recur` is not the last thing the function does"),
)
.with_help("make `recur` the last form of the function, or call the function by name")
}
pub fn recur_argument_count(expected: usize, found: usize, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::RECUR_ARGUMENT_COUNT,
format!(
"`recur` passes {}, but its function takes {expected}",
Counted::noun(found, "argument")
),
Label::new(span, format!("passes {}", Counted::noun(found, "argument"))),
)
.with_help(format!(
"write `recur` with {}, one for each parameter of the function",
Counted::noun(expected, "argument")
))
}
pub fn try_outside_fn(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::TRY_OUTSIDE_FN,
"`try` is not inside a function",
Label::new(span, "there is no function here for `try` to return from"),
)
.with_help("write `try` inside a `fn` body")
}
pub fn recur_outside_fn(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::RECUR_OUTSIDE_FN,
"`recur` is not inside a function",
Label::new(
span,
"there is no function here for `recur` to jump back to",
),
)
.with_help("write `recur` inside a `fn` body")
}
pub fn macro_result(found: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MACRO_RESULT_NOT_FORM,
format!("this macro returned {found} instead of code"),
Self::macro_call(span),
)
}
pub fn macro_call(span: Span) -> Label {
Label::at_macro_call(span, "in this macro call")
}
pub fn expansion_too_deep(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::EXPANSION_TOO_DEEP,
"macro expansion did not finish",
Label::new(span, "expanding this form keeps producing more macro calls"),
)
.with_help("check that every recursive macro has a case that stops")
}
pub fn expansion_too_large(name: &str, limit: usize, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::EXPANSION_TOO_LARGE,
"macro expansion did not finish",
Label::new(
span,
format!("expanding this call of `{name}` passes {limit} forms"),
),
)
.with_note(format!(
"the macro calls in one top-level form expand to at most {limit} forms in all"
))
.with_help(
"make each recursive macro stop, or split a large expansion across several top-level forms",
)
}
pub fn expansion_nests_too_deep(
span: Span,
limit: usize,
expansion: Option<&str>,
) -> Diagnostic {
let diagnostic = Diagnostic::error(
DiagnosticCode::EXPANSION_NESTS_TOO_DEEP,
"this expression nests too deeply",
Label::new(span, format!("this expression sits inside {limit} others")),
)
.with_note(format!("expressions nest at most {limit} deep"));
match expansion {
Some(name) => diagnostic.with_note(format!("in this expansion of `{name}`")),
None => diagnostic,
}
.with_help("move the inner expressions into a function of their own")
}
pub fn compile_time_cycle(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::COMPILE_TIME_CYCLE,
format!("`{name}` depends on itself at compile time"),
Label::new(span, "needed here while it is still being computed"),
)
}
pub fn not_a_type(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::NOT_A_TYPE,
"expected a type",
Label::new(span, "this is not a type"),
)
.with_help(
"a type is a name such as `i64`, or a form such as `(fn (i64) i64)` or `(ref str)`",
)
}
pub fn malformed_type(constructor: KernelType, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MALFORMED_TYPE,
format!("malformed `{}` type", constructor.name()),
Label::new(span, "this does not fit the shape of the type"),
)
.with_help(format!("write it as `{}`", constructor.shape()))
}
pub fn malformed_associated_value(name: Option<&str>, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MALFORMED_TYPE,
"malformed associated value",
Label::new(span, "this does not fit the shape of a value clause"),
)
.with_help(format!(
"write each value as `({} type)`",
name.unwrap_or("name")
))
}
pub fn parameters_on_name(kind: &str, replacement: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::TYPE_PARAMETERS_ON_NAME,
format!("`{kind}` names its type parameters on its name"),
Label::new(span, "this `where` names them in the body"),
)
.with_help(format!("write `{replacement}`"))
}
pub fn mixed_variant_payload(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MIXED_VARIANT_PAYLOAD,
"a variant's payload is either types or one `struct` body",
Label::new(span, "this mixes the two"),
)
.with_help(
"write the payload as types, as in `(circle f64)`, or as one struct body, as in `(labeled (struct (text string)))`",
)
}
pub fn not_a_name(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::NOT_A_NAME,
"expected a name",
Label::new(span, "this is not a name"),
)
}
pub fn extra_field_keyword_value(keyword: &str, span: Span) -> Diagnostic {
Self::field_keyword_arity(keyword, Label::new(span, "this value is extra"))
}
pub fn missing_field_keyword_value(keyword: &str, span: Span) -> Diagnostic {
Self::field_keyword_arity(keyword, Label::new(span, "this call has no value"))
}
fn field_keyword_arity(keyword: &str, label: Label) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::FIELD_KEYWORD_ARITY,
"a field keyword takes one value",
label,
)
.with_help(format!("write ({keyword} value)"))
}
pub fn label_without_value(head: &Form, fields: &[Rc<str>], span: Span) -> Diagnostic {
Self::partly_labeled_call(head, fields, Label::new(span, "this label has no value"))
}
pub fn value_without_label(head: &Form, fields: &[Rc<str>], span: Span) -> Diagnostic {
Self::partly_labeled_call(head, fields, Label::new(span, "this argument has no label"))
}
fn partly_labeled_call(head: &Form, fields: &[Rc<str>], label: Label) -> Diagnostic {
let marker = Keyword::MARKER;
let labeled_values: Vec<String> = fields
.iter()
.map(|field| format!("{marker}{field} value"))
.collect();
Diagnostic::error(
DiagnosticCode::PARTLY_LABELED_CALL,
format!("`{head}` takes a label on every argument or on none"),
label,
)
.with_help(format!(
"a keyword argument is always a label: write `{}`",
labeled_values.join(" ")
))
}
pub fn keyword_binder(span: Span) -> Diagnostic {
let marker = Keyword::MARKER;
Diagnostic::error(
DiagnosticCode::KEYWORD_BINDER,
format!("a name cannot start with `{marker}`"),
Label::new(span, "this is a field keyword"),
)
.with_help(format!(
"names starting with `{marker}` read struct fields; choose another name"
))
}
pub fn ignored_binding_read(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::IGNORED_BINDING_READ,
"`_` binds no value to read",
Label::new(span, "this reads a binding named `_`"),
)
.with_help("give the binding a name, and read that name here")
}
pub fn not_a_pattern(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::NOT_A_PATTERN,
"this is not a pattern",
Label::new(span, "a pattern names a binding, a literal or a variant"),
)
.with_help("write a binding name, `_`, a literal, or `(variant pattern ...)`")
}
pub fn variant_as_value(name: &str, arity: usize, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::VARIANT_AS_VALUE,
format!("`{name}` is a variant constructor, not a value"),
Label::new(
span,
format!(
"this constructor takes {} when it is called",
Counted::noun(arity, "argument")
),
),
)
.with_help(format!("write `({name} value ...)`"))
}
pub fn signature_target(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::SIGNATURE_TARGET,
format!("`{name}` cannot have a type signature here"),
Label::new(span, "not a definition of this module"),
)
.with_help("a `type` signature must sit in the module whose `def` it describes")
}
pub fn derive_target(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::DERIVE_TARGET,
format!("`{name}` cannot have a `derive` here"),
Label::new(span, "not a struct of this module"),
)
.with_help("a `derive` must sit in the module whose `struct` it describes")
}
pub fn derive_trait(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::DERIVE_TRAIT,
format!("`{name}` is not a trait"),
Label::new(span, "a `derive` lists traits"),
)
.with_help(format!("list a trait's name, not `{name}`"))
}
pub fn missing_method(
method: &str,
trait_name: &str,
type_name: &str,
span: Span,
) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::MISSING_METHOD,
format!("`{type_name}` is missing `{method}` from `{trait_name}`"),
Label::new(span, format!("this implementation is missing `{method}`")),
)
.with_help(format!("add the `{method}` method"))
}
pub fn missing_associated_type(
trait_name: &str,
associated: &str,
type_name: &str,
span: Span,
) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::ASSOCIATED_TYPE,
format!("`{type_name}` does not define the associated type `{associated}`"),
Label::new(
span,
format!("`{trait_name}` requires the associated type `{associated}`"),
),
)
.with_help(format!("add `(type {associated} ...)` to the impl"))
}
pub fn unknown_associated_type(trait_name: &str, associated: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::ASSOCIATED_TYPE,
format!("`{trait_name}` does not declare the associated type `{associated}`"),
Label::new(span, "not an associated type of this trait"),
)
.with_help("declare it in the trait, or remove it from the impl")
}
pub fn duplicate_associated_type(associated: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::ASSOCIATED_TYPE,
format!("the associated type `{associated}` is defined more than once"),
Label::new(span, "defined again here"),
)
.with_help("remove one of the definitions")
}
pub fn extra_method(method: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::EXTRA_METHOD,
format!("`{method}` is implemented more than once"),
Label::new(span, "defined again here"),
)
.with_help("remove one of the definitions")
}
pub fn undeclared_method(method: &str, trait_name: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::UNDECLARED_METHOD,
format!("`{trait_name}` does not declare `{method}`"),
Label::new(span, format!("not a method of `{trait_name}`")),
)
.with_help("declare it in the trait, or remove it")
}
pub fn generic_implementor(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::GENERIC_IMPLEMENTOR,
"an `impl` cannot name only a type variable",
Label::new(span, format!("`{name}` is a type variable")),
)
.with_help("apply the variable to a type, such as `(vec a)`")
}
pub fn broken_definition(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::BROKEN_DEFINITION,
format!("`{name}` has errors, so macro code cannot use it"),
Label::new(span, "used by macro code here"),
)
}
pub fn program_only(name: &str, used: Span, defined: Span) -> Diagnostic {
let diagnostic = Diagnostic::error(
DiagnosticCode::PROGRAM_ONLY,
format!("`{name}` belongs to the program, so macro code cannot run it"),
Label::new(used, "macro code runs this"),
)
.with_label(Label::new(
defined,
format!("`{name}` is Rust code that runs in the program"),
))
.with_help(InterpretProblems::RETURN_AS_CODE_HELP);
match Self::code_replacement(name) {
Some((replacement, module)) => diagnostic.with_help(format!(
"in macro code, use `{replacement}` from `{module}` instead"
)),
None => diagnostic,
}
}
pub fn macro_program_only(name: &str, used: Span, defined: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::PROGRAM_ONLY,
format!("`{name}` belongs to the program, so macro code cannot run it"),
Label::new(used, "macro code runs this"),
)
.with_label(Label::new(
defined,
format!("`{name}` expands to Rust code that runs in the program"),
))
.with_help(InterpretProblems::RETURN_AS_CODE_HELP)
}
fn code_replacement(name: &str) -> Option<(&'static str, &'static str)> {
Self::CODE_REPLACEMENTS
.iter()
.find(|(program, _, _)| *program == name)
.map(|(_, replacement, module)| (*replacement, *module))
}
pub fn broken_macro(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::BROKEN_MACRO,
format!("the macro `{name}` has errors, so this call cannot expand"),
Label::new(span, "this call needs the macro"),
)
}
pub fn not_at_compile_time(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::NOT_AT_COMPILE_TIME,
format!("`{name}` is not available at compile time"),
Label::new(span, "used by macro code here"),
)
}
pub fn second_trait_path(name: &str, existing: &str, path: &str, span: Span) -> Diagnostic {
let title = if name == existing {
format!("`{name}` binds `{path}` again")
} else {
format!("`{name}` binds a Rust path that `{existing}` already binds")
};
let help = if name == existing {
format!("remove the second `{name}`, or give it a different Rust path")
} else {
format!("use `{existing}`, or give `{name}` a different Rust path")
};
Diagnostic::warning(
DiagnosticCode::SECOND_TRAIT_PATH,
title,
Label::new(span, format!("this also binds `{path}`")),
)
.with_note("two traits that bind one Rust path can disagree about its implementors")
.with_help(help)
}
fn describe(namespace: Namespace) -> &'static str {
match namespace {
Namespace::Value => "",
Namespace::Type => "type ",
}
}
}
pub struct SetAsideInput<'a> {
names: &'a [&'a str],
source: &'a str,
setter: &'a str,
}
impl<'a> SetAsideInput<'a> {
const LISTED_NAMES: usize = 3;
pub fn new(names: &'a [&'a str], source: &'a str, setter: &'a str) -> Self {
Self {
names,
source,
setter,
}
}
fn title(&self) -> String {
format!("set aside {} after `{}`", self.listed_names(), self.setter)
}
fn enter_again(&self) -> String {
let pronoun = if self.names.len() > 1 { "them" } else { "it" };
format!("enter {} again to use {pronoun}", self.listed_names())
}
fn listed_names(&self) -> String {
let quoted: Vec<String> = self.names.iter().map(|name| format!("`{name}`")).collect();
match quoted.as_slice() {
[] => format!("`{}`", self.source),
[only] => only.clone(),
shown if shown.len() <= Self::LISTED_NAMES => {
let (last, rest) = shown.split_last().expect("two names or more");
format!("{} and {last}", rest.join(", "))
}
more => format!(
"{} and {} more",
more[..Self::LISTED_NAMES].join(", "),
more.len() - Self::LISTED_NAMES
),
}
}
}
#[cfg(test)]
mod tests {
use super::{ExpandProblems, SetAsideInput};
use crate::core::Namespace;
use crate::diagnostic::MissingName;
use crate::source::{SourceId, Span};
struct SetAsideFixture;
impl SetAsideFixture {
fn title(names: &[&str]) -> String {
SetAsideInput::new(names, "<input 2>", "<input 4>").title()
}
}
#[test]
fn an_input_that_bound_no_name_is_named_by_its_source() {
assert_eq!(
SetAsideFixture::title(&[]),
"set aside `<input 2>` after `<input 4>`"
);
}
#[test]
fn the_title_lists_one_name() {
assert_eq!(
SetAsideFixture::title(&["karl"]),
"set aside `karl` after `<input 4>`"
);
}
#[test]
fn the_title_lists_three_names() {
assert_eq!(
SetAsideFixture::title(&["a", "b", "c"]),
"set aside `a`, `b` and `c` after `<input 4>`"
);
}
#[test]
fn the_title_counts_the_names_past_three() {
assert_eq!(
SetAsideFixture::title(&["a", "b", "c", "d", "e"]),
"set aside `a`, `b`, `c` and 2 more after `<input 4>`"
);
}
#[test]
fn an_unbound_name_carries_the_name_and_its_namespace() {
let span = Span::new(SourceId::new(0), 0..4);
let diagnostic = ExpandProblems::unbound("user", Namespace::Type, span, None);
assert_eq!(
diagnostic.missing_name(),
Some(&MissingName {
name: "user".into(),
namespace: Namespace::Type,
})
);
}
}