feat(when): single when clause now emits warning

This commit is contained in:
rvcas 2023-01-24 10:22:03 -05:00 committed by Lucas
parent ad7a62d2bf
commit 7206360baa
2 changed files with 83 additions and 53 deletions

View File

@ -1,7 +1,7 @@
use super::Type; use super::Type;
use crate::{ use crate::{
ast::{Annotation, BinOp, CallArg, Span, TodoKind, UntypedPattern}, ast::{Annotation, BinOp, CallArg, Span, TodoKind, UntypedPattern},
expr, expr::{self, UntypedExpr},
format::Formatter, format::Formatter,
levenshtein, levenshtein,
pretty::Documentable, pretty::Documentable,
@ -172,20 +172,7 @@ You can use '{discard}' and numbers to distinguish between similar names.
If you really meant to return that last expression, try to replace it with the following: If you really meant to return that last expression, try to replace it with the following:
{sample}"# {sample}"#
, sample = Formatter::new() , sample = format_suggestion(expr)
.expr(expr)
.to_pretty_string(70)
.lines()
.enumerate()
.map(|(ix, line)| {
if ix == 0 {
format!("╰─▶ {}", line.yellow())
} else {
format!(" {line}").yellow().to_string()
}
})
.collect::<Vec<_>>()
.join("")
))] ))]
LastExpressionIsAssignment { LastExpressionIsAssignment {
#[label("let-binding as last expression")] #[label("let-binding as last expression")]
@ -1034,14 +1021,12 @@ fn suggest_import_constructor() -> String {
#[derive(Debug, PartialEq, Clone, thiserror::Error, Diagnostic)] #[derive(Debug, PartialEq, Clone, thiserror::Error, Diagnostic)]
pub enum Warning { pub enum Warning {
#[error("I found a todo left in the code.\n")] #[error("I found a record update using all fields; thus redundant.\n")]
#[diagnostic(help("You probably want to replace that one with real code... eventually."))] #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#record-updates"))]
#[diagnostic(code("todo"))] #[diagnostic(code("record_update::all_fields"))]
Todo { AllFieldsRecordUpdate {
kind: TodoKind,
#[label] #[label]
location: Span, location: Span,
tipo: Arc<Type>,
}, },
#[error( #[error(
@ -1056,9 +1041,10 @@ pub enum Warning {
location: Span, location: Span,
}, },
#[error("I found a literal that is unused.\n")] #[error("I found a record update with no fields; effectively updating nothing.\n")]
#[diagnostic(code("unused::literal"))] #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#record-updates"))]
UnusedLiteral { #[diagnostic(code("record_update::no_fields"))]
NoFieldsRecordUpdate {
#[label] #[label]
location: Span, location: Span,
}, },
@ -1070,29 +1056,25 @@ pub enum Warning {
location: Span, location: Span,
}, },
#[error("I found a record update with no fields; effectively updating nothing.\n")] #[error("I found a when expression with a single clause.")]
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#record-updates"))] #[diagnostic(
#[diagnostic(code("record_update::no_fields"))] code("single_when_clause"),
NoFieldsRecordUpdate { help("Prefer using a {} binding like so...\n\n{}", "let".purple(), format_suggestion(sample))
)]
SingleWhenClause {
#[label] #[label]
location: Span, location: Span,
sample: UntypedExpr,
}, },
#[error("I found a record update using all fields; thus redundant.\n")] #[error("I found a todo left in the code.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#record-updates"))] #[diagnostic(help("You probably want to replace that one with real code... eventually."))]
#[diagnostic(code("record_update::all_fields"))] #[diagnostic(code("todo"))]
AllFieldsRecordUpdate { Todo {
kind: TodoKind,
#[label] #[label]
location: Span, location: Span,
}, tipo: Arc<Type>,
#[error("I discovered an unused type: '{}'.\n", name.purple())]
#[diagnostic(code("unused::type"))]
UnusedType {
#[label]
location: Span,
imported: bool,
name: String,
}, },
#[error("I discovered an unused constructor: '{}'.\n", name.purple())] #[error("I discovered an unused constructor: '{}'.\n", name.purple())]
@ -1107,6 +1089,17 @@ pub enum Warning {
name: String, name: String,
}, },
#[error("I discovered an unused imported module: '{}'.\n", name.purple())]
#[diagnostic(help(
"No big deal, but you might want to remove it to get rid of that warning."
))]
#[diagnostic(code("unused::import::module"))]
UnusedImportedModule {
#[label]
location: Span,
name: String,
},
#[error("I discovered an unused imported value: '{}'.\n", name.purple())] #[error("I discovered an unused imported value: '{}'.\n", name.purple())]
#[diagnostic(help( #[diagnostic(help(
"No big deal, but you might want to remove it to get rid of that warning." "No big deal, but you might want to remove it to get rid of that warning."
@ -1118,12 +1111,21 @@ pub enum Warning {
name: String, name: String,
}, },
#[error("I discovered an unused imported module: '{}'.\n", name.purple())] #[error("I found a literal that is unused.\n")]
#[diagnostic(code("unused::literal"))]
UnusedLiteral {
#[label]
location: Span,
},
#[error("I found an unused private function: '{}'.\n", name.purple())]
#[diagnostic(help( #[diagnostic(help(
"No big deal, but you might want to remove it to get rid of that warning." "Perhaps your forgot to make it public using the '{keyword_pub}' keyword?\n\
Otherwise, you might want to get rid of it altogether."
, keyword_pub = "pub".bright_blue()
))] ))]
#[diagnostic(code("unused::import::module"))] #[diagnostic(code("unused::function"))]
UnusedImportedModule { UnusedPrivateFunction {
#[label] #[label]
location: Span, location: Span,
name: String, name: String,
@ -1142,16 +1144,12 @@ pub enum Warning {
name: String, name: String,
}, },
#[error("I found an unused private function: '{}'.\n", name.purple())] #[error("I discovered an unused type: '{}'.\n", name.purple())]
#[diagnostic(help( #[diagnostic(code("unused::type"))]
"Perhaps your forgot to make it public using the '{keyword_pub}' keyword?\n\ UnusedType {
Otherwise, you might want to get rid of it altogether."
, keyword_pub = "pub".bright_blue()
))]
#[diagnostic(code("unused::function"))]
UnusedPrivateFunction {
#[label] #[label]
location: Span, location: Span,
imported: bool,
name: String, name: String,
}, },
@ -1187,3 +1185,20 @@ pub enum UnknownRecordFieldSituation {
/// This unknown record field is being called as a function. i.e. `record.field()` /// This unknown record field is being called as a function. i.e. `record.field()`
FunctionCall, FunctionCall,
} }
fn format_suggestion<'a>(sample: &'a UntypedExpr) -> String {
Formatter::new()
.expr(sample)
.to_pretty_string(70)
.lines()
.enumerate()
.map(|(ix, line)| {
if ix == 0 {
format!("╰─▶ {}", line.yellow())
} else {
format!(" {line}").yellow().to_string()
}
})
.collect::<Vec<_>>()
.join("")
}

View File

@ -1930,6 +1930,21 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
clauses: Vec<UntypedClause>, clauses: Vec<UntypedClause>,
location: Span, location: Span,
) -> Result<TypedExpr, Error> { ) -> Result<TypedExpr, Error> {
// if there is only one clause we want to present a warning
// that suggests that a `let` binding should be used instead.
if clauses.len() == 1 {
self.environment.warnings.push(Warning::SingleWhenClause {
location: clauses[0].location,
sample: UntypedExpr::Assignment {
location: Span::empty(),
value: Box::new(subjects[0].clone()),
pattern: clauses[0].pattern[0].clone(),
kind: AssignmentKind::Let,
annotation: None,
},
});
}
let subjects_count = subjects.len(); let subjects_count = subjects.len();
let mut typed_subjects = Vec::with_capacity(subjects_count); let mut typed_subjects = Vec::with_capacity(subjects_count);