use std::rc::Rc;
use super::functions::ParamMode;
use crate::core::ReferenceKind;
use crate::diagnostic::{Diagnostic, DiagnosticCode, Help, Label, Suggestion};
use crate::source::Span;
use crate::types::ClosureKind;
pub struct ChangingFunctionValue<'a> {
pub function: &'a str,
pub param: &'a str,
pub expected: String,
pub found: String,
pub by_value: String,
}
pub struct ChangeSite {
pub function: Rc<str>,
pub span: Span,
}
#[derive(Clone)]
pub enum FunctionStorage {
Field(Rc<str>),
Payload(Rc<str>),
}
pub enum DroppedValue {
Local(Span),
Temporary(Span),
FunctionParameter(Span),
ClosureParameter(Span),
}
impl DroppedValue {
fn span(&self) -> Span {
match self {
Self::Local(span)
| Self::Temporary(span)
| Self::FunctionParameter(span)
| Self::ClosureParameter(span) => *span,
}
}
}
pub enum BorrowedStorage {
Field {
field: String,
owner: String,
owned: String,
declared: Span,
},
Payload {
variant: String,
owner: String,
owned: String,
declared: Span,
},
NamedPayload {
field: String,
variant: String,
owner: String,
owned: String,
declared: Span,
},
}
impl BorrowedStorage {
fn declared(&self) -> Span {
match self {
Self::Field { declared, .. }
| Self::Payload { declared, .. }
| Self::NamedPayload { declared, .. } => *declared,
}
}
}
struct MoveLabels {
used: &'static str,
moved: &'static str,
}
impl MoveLabels {
const SAME_PASS: Self = Self {
used: "value used here after it moved",
moved: "value moved here",
};
const EARLIER_PASS: Self = Self {
used: "value used here after an earlier pass of the loop moved it",
moved: "value moved here, in an earlier pass of the loop",
};
}
pub struct LentParameter<'n> {
pub span: Span,
pub closure: &'n str,
}
pub struct LentCall<'n> {
pub called: &'n str,
pub aliases: Vec<AliasLabel<'n>>,
}
pub struct AliasLabel<'n> {
pub span: Span,
pub alias: &'n str,
pub target: &'n str,
}
pub struct MacroFieldRead<'n> {
pub macro_name: Rc<str>,
pub field: Rc<str>,
pub field_span: Span,
pub field_type: String,
pub callee: Option<&'n str>,
}
pub struct OwnershipProblems;
impl OwnershipProblems {
const BORROWED_ARGUMENT_TITLE: &'static str =
"this value is borrowed, but the call needs to own it";
pub fn use_after_move(name: &str, used: Span, moved: Span) -> Diagnostic {
Self::moved_value(
DiagnosticCode::USE_AFTER_MOVE,
name,
used,
moved,
&MoveLabels::SAME_PASS,
)
}
pub fn use_after_earlier_pass_move(name: &str, used: Span, moved: Span) -> Diagnostic {
Self::moved_value(
DiagnosticCode::USE_AFTER_MOVE,
name,
used,
moved,
&MoveLabels::EARLIER_PASS,
)
}
fn moved_value(
code: DiagnosticCode,
name: &str,
used: Span,
moved: Span,
labels: &MoveLabels,
) -> Diagnostic {
let diagnostic = Diagnostic::error(
code,
format!("use of moved value `{name}`"),
Label::new(used, labels.used),
);
if used == moved {
return diagnostic;
}
diagnostic.with_label(Label::new(moved, labels.moved))
}
pub fn clone_suggestion(diagnostic: Diagnostic, name: &str, moved: Span) -> Diagnostic {
diagnostic.with_suggestion(
"clone the value if both places need it",
Suggestion::new(moved, format!("(clone {name})")),
)
}
pub fn pipeline_help(diagnostic: Diagnostic, name: &str) -> Diagnostic {
Self::pipeline_wording(diagnostic, &format!("`{name}`"), name)
}
fn pipeline_wording(diagnostic: Diagnostic, subject: &str, collected: &str) -> Diagnostic {
diagnostic
.with_note(format!("{subject} is a lazy pipeline, so it runs once"))
.with_help(format!(
"collect it if both places need its items, as in `(collect {collected})`, or write a function that rebuilds it"
))
}
pub fn mut_parameter_move_help(diagnostic: Diagnostic) -> Diagnostic {
diagnostic
.with_help("use the value before it moves, because a `mut` borrow cannot be cloned")
}
pub fn changed_value_help(diagnostic: Diagnostic) -> Diagnostic {
diagnostic.with_help(
"change the value in place instead of moving it, so both uses see the change",
)
}
pub fn recursive_parameter_help(diagnostic: Diagnostic) -> Diagnostic {
diagnostic.with_help("write the parameter as a borrow in a `type` signature: `(ref ...)`")
}
pub fn missing_copy_bound(
name: &str,
parameter: &str,
copy_trait: &str,
used: Span,
moved: Span,
) -> Diagnostic {
Self::moved_value(
DiagnosticCode::MISSING_COPY_BOUND,
name,
used,
moved,
&MoveLabels::SAME_PASS,
)
.with_note(format!(
"the signature writes `{parameter}` without `{copy_trait}`, and a written signature is exact"
))
.with_help(format!(
"add a `{copy_trait}` bound to the signature: `({parameter} {copy_trait})`"
))
}
pub fn written_mode_mismatch(
parameter: &str,
kind: ReferenceKind,
mode: ParamMode,
span: Span,
) -> Diagnostic {
let taken = match mode {
ParamMode::Copy | ParamMode::Own => "a copy",
ParamMode::Ref => "a reference",
ParamMode::Mut => "a mutable reference",
};
let (title, passed, help) = match kind {
ReferenceKind::Shared => (
format!("`{parameter}` is taken as {taken}, so this borrow cannot pass"),
"this passes a borrow",
"pass the value without `ref`, or write the parameter's type",
),
ReferenceKind::Mutable => (
format!("`{parameter}` is taken as {taken}, so this mutable borrow cannot pass"),
"this passes a mutable borrow",
"pass the value without `mut`, or write the parameter's type",
),
};
Diagnostic::error(
DiagnosticCode::WRITTEN_MODE_MISMATCH,
title,
Label::new(span, passed),
)
.with_note("the callee's body decides whether the parameter takes a borrow")
.with_help(help)
}
pub fn closure_move_help(diagnostic: Diagnostic) -> Diagnostic {
diagnostic.with_help("this closure moves a captured value, so it cannot be copied")
}
pub fn capturing_function_field(
closure: Span,
capture: &str,
storage: &FunctionStorage,
declared: Span,
) -> Diagnostic {
let (place, holder) = match storage {
FunctionStorage::Field(field) => (format!("the field `{field}`"), "field"),
FunctionStorage::Payload(variant) => (format!("the `{variant}` payload"), "payload"),
};
Diagnostic::error(
DiagnosticCode::CAPTURING_FUNCTION_FIELD,
format!("cannot store a capturing closure in {place}"),
Label::new(closure, format!("this closure captures `{capture}`")),
)
.with_label(Label::new(declared, "declared with a function type here"))
.with_help(format!(
"a {holder} declared with a function type holds only a named function or a closure that captures nothing"
))
}
pub fn closure_kind_mismatch(
actual: ClosureKind,
required: ClosureKind,
capture: Option<&str>,
callee: Option<&str>,
passed: Span,
capture_span: Option<Span>,
) -> Diagnostic {
let (primary, secondary) = Self::closure_kind_labels(actual, capture, passed, capture_span);
Self::closure_kind_error(actual, required, primary, secondary)
.with_help(Self::closure_kind_help(actual, required, capture, callee))
}
pub fn pipeline_capture_mismatch(
actual: ClosureKind,
required: ClosureKind,
capture: &str,
passed: Span,
capture_span: Option<Span>,
) -> Diagnostic {
let (primary, secondary) =
Self::closure_kind_labels(actual, Some(capture), passed, capture_span);
let diagnostic = Self::closure_kind_error(actual, required, primary, secondary);
Self::pipeline_help(diagnostic, capture)
}
fn closure_kind_error(
actual: ClosureKind,
required: ClosureKind,
primary: Label,
secondary: Option<Label>,
) -> Diagnostic {
let diagnostic = Diagnostic::error(
DiagnosticCode::CLOSURE_KIND_MISMATCH,
format!(
"this closure is `{}`, but the parameter accepts `{}`",
actual.keyword(),
required.keyword()
),
primary,
);
match secondary {
Some(label) => diagnostic.with_label(label),
None => diagnostic,
}
}
pub fn closure_kind_mismatch_call(
actual: ClosureKind,
required: ClosureKind,
name: &str,
kind: ClosureKind,
callee: Option<&str>,
passed: Span,
capture_span: Span,
) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::CLOSURE_KIND_MISMATCH,
format!(
"this closure is `{}`, but the parameter accepts `{}`",
actual.keyword(),
required.keyword()
),
Label::new(capture_span, format!("calls `{name}` here")),
)
.with_label(Label::new(passed, "this closure is passed here"))
.with_help(Self::called_capture_help(required, name, kind, callee))
}
fn called_capture_help(
required: ClosureKind,
name: &str,
kind: ClosureKind,
callee: Option<&str>,
) -> String {
let callee = callee.unwrap_or("this function");
format!(
"`{callee}` calls this closure as `{}`, but it calls `{name}`, which is `{}`",
required.keyword(),
kind.keyword()
)
}
fn closure_kind_labels(
actual: ClosureKind,
capture: Option<&str>,
passed: Span,
capture_span: Option<Span>,
) -> (Label, Option<Label>) {
let action = if actual == ClosureKind::FnOnce {
"moves"
} else {
"changes"
};
match (capture, capture_span) {
(Some(name), Some(span)) => (
Label::new(span, format!("`{name}` {action} here")),
Some(Label::new(passed, "this closure is passed here")),
),
(Some(name), None) => (Label::new(passed, format!("`{name}` {action} here")), None),
(None, _) => (
Label::new(
passed,
"this closure is stronger than the parameter accepts",
),
None,
),
}
}
fn closure_kind_help(
actual: ClosureKind,
required: ClosureKind,
capture: Option<&str>,
callee: Option<&str>,
) -> String {
if actual == ClosureKind::FnMut {
let callee = callee.unwrap_or("this function");
return match capture {
Some(name) => format!(
"`{callee}` calls this closure as `{}`, but it changes `{name}`",
required.keyword()
),
None => format!(
"`{callee}` calls this closure as `{}`, but it changes a captured value",
required.keyword()
),
};
}
match capture {
Some(name) => format!("clone `{name}`, or borrow it with `(ref {name})`"),
None => "clone the closure's captures, or borrow them instead".to_string(),
}
}
pub fn local_borrow(borrowed: Span, dropped: &DroppedValue) -> Diagnostic {
Self::returned_local_borrow(borrowed, dropped).with_help(match dropped {
DroppedValue::Local(_) => {
"return an owned value with `(clone ...)`, or borrow from a parameter"
}
DroppedValue::Temporary(_) => {
"return an owned value, or borrow from a parameter or a top-level `def`"
}
DroppedValue::FunctionParameter(_) => {
"return an owned value with `(clone ...)`, or give this parameter a borrowed type"
}
DroppedValue::ClosureParameter(_) => {
"return an owned value with `(clone ...)`, or capture the value instead of passing it"
}
})
}
pub fn stored_local_borrow(
borrowed: Span,
dropped: &DroppedValue,
storage: &BorrowedStorage,
) -> Diagnostic {
Self::returned_local_borrow(borrowed, dropped)
.with_help(Self::owned_storage(storage))
.with_label(Label::new(storage.declared(), "declared as a borrow here"))
}
fn owned_storage(storage: &BorrowedStorage) -> String {
match storage {
BorrowedStorage::Field {
field,
owner,
owned,
..
} => format!(
"the field `{field}` of `{owner}` borrows its data, so give it the owned type `{owned}`"
),
BorrowedStorage::Payload {
variant,
owner,
owned,
..
} => format!(
"the `{variant}` payload of `{owner}` borrows its data, so give it the owned type `{owned}`"
),
BorrowedStorage::NamedPayload {
field,
variant,
owner,
owned,
..
} => format!(
"the field `{field}` of `{owner}/{variant}` borrows its data, so give it the owned type `{owned}`"
),
}
}
pub fn lent_local_borrow(
borrowed: Span,
dropped: &DroppedValue,
lent: LentParameter,
call: &LentCall,
) -> Diagnostic {
let LentParameter { span, closure } = lent;
let called = call.called;
let (primary, dropped_label) =
Self::borrow_labels(borrowed, dropped, Self::lent_dropped_label(dropped));
let diagnostic = Diagnostic::error(
DiagnosticCode::LENT_LOCAL_BORROW,
format!(
"cannot pass a borrow of {} to `{called}`",
Self::dropped_noun(dropped)
),
primary,
)
.with_label(Label::new(
span,
format!("`{closure}` ties this parameter to its result, so it needs a borrow that outlives the function"),
));
dropped_label
.into_iter()
.chain(call.aliases.iter().map(Self::alias_label))
.fold(diagnostic, Diagnostic::with_label)
.with_help(format!(
"define `{closure}` with `defn`, where its result can borrow from its argument"
))
}
fn alias_label(alias: &AliasLabel) -> Label {
Label::new(
alias.span,
format!("`{}` is `{}`", alias.alias, alias.target),
)
}
fn borrow_labels(
borrowed: Span,
dropped: &DroppedValue,
dropped_label: Label,
) -> (Label, Option<Label>) {
if dropped.span() == borrowed {
return (dropped_label, None);
}
let primary = Label::new(
borrowed,
format!("this borrows {}", Self::dropped_noun(dropped)),
);
(primary, Some(dropped_label))
}
fn lent_dropped_label(dropped: &DroppedValue) -> Label {
match dropped {
DroppedValue::Temporary(built) => Label::new(
*built,
"this temporary value is dropped at the end of the statement",
),
DroppedValue::Local(_)
| DroppedValue::FunctionParameter(_)
| DroppedValue::ClosureParameter(_) => Self::dropped_label(dropped),
}
}
fn dropped_noun(dropped: &DroppedValue) -> &'static str {
match dropped {
DroppedValue::Temporary(_) => "a temporary value",
DroppedValue::Local(_)
| DroppedValue::FunctionParameter(_)
| DroppedValue::ClosureParameter(_) => "a local value",
}
}
fn returned_local_borrow(borrowed: Span, dropped: &DroppedValue) -> Diagnostic {
let (primary, dropped_label) =
Self::borrow_labels(borrowed, dropped, Self::dropped_label(dropped));
let diagnostic = Diagnostic::error(
DiagnosticCode::LOCAL_BORROW,
format!("cannot return a borrow of {}", Self::dropped_noun(dropped)),
primary,
);
dropped_label
.into_iter()
.fold(diagnostic, Diagnostic::with_label)
}
fn dropped_label(dropped: &DroppedValue) -> Label {
match dropped {
DroppedValue::Local(defined) => {
Label::new(*defined, "this local is dropped when the function returns")
}
DroppedValue::Temporary(built) => Label::new(
*built,
"this temporary value is dropped when the function returns",
),
DroppedValue::FunctionParameter(defined) | DroppedValue::ClosureParameter(defined) => {
Label::new(
*defined,
"this parameter owns its value, which is dropped when the function returns",
)
}
}
}
pub fn moved_definition(name: &str, span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::NEEDS_OWNERSHIP,
format!("cannot move `{name}` out of a top-level definition"),
Label::new(span, "a top-level definition can only be borrowed"),
)
.with_suggestion(
"clone the value to own a copy",
Suggestion::new(span, format!("(clone {name})")),
)
}
pub fn needs_ownership(span: Span) -> Diagnostic {
Self::borrowed_argument(span).with_help("pass a copy with `(clone ...)`")
}
pub fn macro_field_needs_ownership(read: &MacroFieldRead<'_>) -> Diagnostic {
let MacroFieldRead {
macro_name, field, ..
} = read;
let borrowed = format!("(ref {})", read.field_type);
Diagnostic::error(
DiagnosticCode::NEEDS_OWNERSHIP,
Self::BORROWED_ARGUMENT_TITLE,
Label::new(
read.field_span,
format!("`{macro_name}` passes this field as a borrow"),
),
)
.with_help(format!(
"`{macro_name}` passes `:{field}` as a `{borrowed}`; {}",
Self::borrowed_field_fix(read, &borrowed)
))
}
fn borrowed_field_fix(read: &MacroFieldRead<'_>, borrowed: &str) -> String {
let field = &read.field;
match read.callee {
Some(callee) => format!(
"take a borrow and clone it, as in `(fn (({field} {borrowed})) ({callee} (clone {field})))`"
),
None => format!(
"annotate the parameter as `{borrowed}` and clone it before the owning call"
),
}
}
pub fn needs_pipeline_ownership(span: Span) -> Diagnostic {
Self::pipeline_wording(Self::borrowed_argument(span), "this value", "...")
}
fn borrowed_argument(span: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::NEEDS_OWNERSHIP,
Self::BORROWED_ARGUMENT_TITLE,
Label::new(span, "only a borrow is available here"),
)
}
pub fn function_value_mismatch(
expected: &str,
found: &str,
span: Span,
parameter: usize,
) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::FUNCTION_VALUE_MISMATCH,
"mismatched function type",
Label::new(span, format!("expected `{expected}`, found `{found}`")),
)
.with_note(format!("no adjustment makes parameter {parameter} match"))
.with_help(
"change the parameter's ownership in a `type` signature, or pass a function that matches",
)
}
pub fn changing_function_argument(
value: Span,
change: Option<Span>,
problem: &ChangingFunctionValue,
) -> Diagnostic {
Self::changing_function_expected(value, change, problem, "this call", "this parameter")
}
pub fn changing_function_field(
value: Span,
change: Option<Span>,
problem: &ChangingFunctionValue,
field: &str,
) -> Diagnostic {
Self::changing_function_expected(
value,
change,
problem,
&format!("the field `{field}`"),
&format!("the function in `{field}`"),
)
}
fn changing_function_expected(
value: Span,
change: Option<Span>,
problem: &ChangingFunctionValue,
receiver: &str,
slot: &str,
) -> Diagnostic {
let ChangingFunctionValue {
function,
param,
expected,
found,
by_value,
..
} = problem;
let diagnostic = Diagnostic::error(
DiagnosticCode::CHANGING_FUNCTION_VALUE,
format!("`{function}` changes its argument, but {receiver} passes it a copy"),
Label::new(
value,
format!("{slot} expects `{expected}`, and `{function}` takes `{found}`"),
),
)
.with_help(format!(
"to change a copy on purpose, write `(type {function} {by_value})`, or start the body with `(local {param} {param})`"
));
Self::with_lost_change(diagnostic, change)
}
pub fn changing_function_local(
value: Span,
change: Option<Span>,
problem: &ChangingFunctionValue,
local: &str,
) -> Diagnostic {
let ChangingFunctionValue {
function,
param,
by_value,
..
} = problem;
let diagnostic = Diagnostic::error(
DiagnosticCode::CHANGING_FUNCTION_VALUE,
format!("`{function}` changes its argument, but `{local}` passes it a copy"),
Label::new(
value,
format!("this binding takes `{function}` as `{by_value}`"),
),
)
.with_help(format!(
"to change the caller's value, call `{function}` by name; to change a copy on purpose, start its body with `(local {param} {param})`"
));
Self::with_lost_change(diagnostic, change)
}
fn with_lost_change(diagnostic: Diagnostic, change: Option<Span>) -> Diagnostic {
match change {
Some(change) => {
diagnostic.with_label(Label::new(change, "this would change only the copy"))
}
None => diagnostic,
}
}
pub fn aliased_change(place: &str, first: Span, second: Span) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::ALIASED_CHANGE,
format!("this call changes `{place}` through two arguments at once"),
Label::new(second, format!("this argument changes `{place}` again")),
)
.with_label(Label::new(
first,
format!("this argument already changes `{place}`"),
))
.with_help("pass a different place to each argument that the call changes")
}
pub fn changed_parameter_in_returned_closure(
function: &str,
param: &str,
usage: Span,
) -> Diagnostic {
Diagnostic::error(
DiagnosticCode::RETURNED_CLOSURE_USES_CHANGED_PARAMETER,
format!("a returned closure cannot use `{param}`, which `{function}` changes"),
Label::new(usage, format!("the closure uses `{param}` here")),
)
.with_note(format!(
"`{param}` borrows the caller's value as `mut`, and the closure would keep that borrow for as long as it lives"
))
.with_help(format!(
"copy it first with `(local value {param})` and use `value` in the closure"
))
}
pub fn inferred_mut_of_copy(argument: Span, change: Option<ChangeSite>) -> Diagnostic {
Self::mut_of_copy(argument, change).with_quoting_help(
format!(
"bind it to a local and pass the local: `(local value {})`",
Help::QUOTED_FORM
),
argument,
)
}
pub fn inferred_mut_of_shared_borrow(argument: Span, change: Option<ChangeSite>) -> Diagnostic {
let diagnostic = Diagnostic::error(
DiagnosticCode::INFERRED_MUT_OF_COPY,
"a call cannot change a value behind a shared borrow, such as an element, yet",
Label::new(argument, "this value is borrowed, not passed as a place"),
);
Self::with_change_site(diagnostic, change).with_help("change it in place with `set!`")
}
pub fn inferred_mut_of_copy_definition(
argument: Span,
change: Option<ChangeSite>,
name: &str,
) -> Diagnostic {
Self::mut_of_definition(argument, change, name)
}
pub fn inferred_mut_of_cloned_definition(
argument: Span,
change: Option<ChangeSite>,
name: &str,
) -> Diagnostic {
Self::mut_of_definition(argument, change, &format!("(clone {name})"))
}
fn mut_of_definition(argument: Span, change: Option<ChangeSite>, value: &str) -> Diagnostic {
Self::mut_of_copy(argument, change)
.with_help(format!("a `def` never changes: `(local value {value})`"))
}
fn mut_of_copy(argument: Span, change: Option<ChangeSite>) -> Diagnostic {
let diagnostic = Diagnostic::error(
DiagnosticCode::INFERRED_MUT_OF_COPY,
"this argument is a copy, so changing it changes nothing",
Label::new(argument, "this is a copy, not a place"),
);
Self::with_change_site(diagnostic, change)
}
fn with_change_site(diagnostic: Diagnostic, change: Option<ChangeSite>) -> Diagnostic {
match change {
Some(change) => diagnostic.with_label(Label::new(
change.span,
format!("`{}` changes its parameter here", change.function),
)),
None => diagnostic,
}
}
pub fn with_copied_parameter_change(
warning: Diagnostic,
site: Span,
param: &str,
) -> Diagnostic {
Self::with_parameter_change(warning, site, &format!("(local {param} {param})"))
}
pub fn with_cloned_parameter_change(
warning: Diagnostic,
site: Span,
param: &str,
) -> Diagnostic {
Self::with_parameter_change(warning, site, &format!("(local {param} (clone {param}))"))
}
fn with_parameter_change(warning: Diagnostic, site: Span, local: &str) -> Diagnostic {
warning
.with_label(Label::new(site, "this changes the caller's value"))
.with_help(format!(
"to change only this function's copy, start the body with `{local}`"
))
}
pub fn missing_bang(name: &str, span: Span) -> Diagnostic {
Diagnostic::warning(
DiagnosticCode::MISSING_BANG,
format!("`{name}` changes an argument, so its name should end with `!`"),
Label::new(span, "this function changes an argument"),
)
.with_help(format!("rename it `{name}!`"))
}
}