Trigger warning when destructuring records using unnamed fields
Comes with a helpful hint and a LSP quickfix, so that it's most convenient.
This commit is contained in:
parent
b25afa2d0d
commit
05264bc423
|
@ -1823,6 +1823,16 @@ pub enum Warning {
|
|||
location: Span,
|
||||
value: String,
|
||||
},
|
||||
|
||||
#[error("I tripped over a confusing constructor destructuring")]
|
||||
#[diagnostic(help("Try instead: \n\n{}", format_pattern_suggestion(suggestion)))]
|
||||
#[diagnostic(code("syntax::unused_record_fields"))]
|
||||
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#destructuring"))]
|
||||
UnusedRecordFields {
|
||||
#[label("prefer destructuring with named fields")]
|
||||
location: Span,
|
||||
suggestion: UntypedPattern,
|
||||
},
|
||||
}
|
||||
|
||||
impl ExtraData for Warning {
|
||||
|
@ -1850,6 +1860,10 @@ impl ExtraData for Warning {
|
|||
Warning::UnusedImportedValueOrType { location, .. } => {
|
||||
Some(format!("{},{}", true, location.start))
|
||||
}
|
||||
|
||||
Warning::UnusedRecordFields { suggestion, .. } => {
|
||||
Some(Formatter::new().pattern(suggestion).to_pretty_string(80))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1893,3 +1907,20 @@ pub fn format_suggestion(sample: &UntypedExpr) -> String {
|
|||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
pub fn format_pattern_suggestion(sample: &UntypedPattern) -> String {
|
||||
Formatter::new()
|
||||
.pattern(sample)
|
||||
.to_pretty_string(70)
|
||||
.lines()
|
||||
.enumerate()
|
||||
.map(|(ix, line)| {
|
||||
if ix == 0 {
|
||||
format!("╰─▶ {line}")
|
||||
} else {
|
||||
format!(" {line}")
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
|
|
@ -462,6 +462,50 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
|
|||
}
|
||||
};
|
||||
|
||||
if let Some(field_map) = cons.field_map() {
|
||||
if !is_record {
|
||||
let arguments = field_map
|
||||
.fields
|
||||
.iter()
|
||||
.sorted_by(|(a, _), (b, _)| a.cmp(b))
|
||||
.zip(pattern_args.iter())
|
||||
.filter_map(|((field, (_, _)), arg)| {
|
||||
if arg.value.is_discard() {
|
||||
None
|
||||
} else {
|
||||
Some(CallArg {
|
||||
label: Some(field.clone()),
|
||||
..arg.clone()
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let spread_location = if arguments.len() == field_map.fields.len() {
|
||||
None
|
||||
} else {
|
||||
Some(Span {
|
||||
start: location.end - 3,
|
||||
end: location.end - 1,
|
||||
})
|
||||
};
|
||||
|
||||
self.environment.warnings.push(Warning::UnusedRecordFields {
|
||||
location,
|
||||
suggestion: Pattern::Constructor {
|
||||
is_record: true,
|
||||
location,
|
||||
name: name.clone(),
|
||||
arguments,
|
||||
module: module.clone(),
|
||||
constructor: (),
|
||||
spread_location,
|
||||
tipo: (),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let instantiated_constructor_type = self.environment.instantiate(
|
||||
constructor_typ,
|
||||
&mut HashMap::new(),
|
||||
|
|
|
@ -11,6 +11,7 @@ const UNKNOWN_MODULE: &str = "aiken::check::unknown::module";
|
|||
const UNUSED_IMPORT_VALUE: &str = "aiken::check::unused:import::value";
|
||||
const UNUSED_IMPORT_MODULE: &str = "aiken::check::unused::import::module";
|
||||
const USE_LET: &str = "aiken::check::single_constructor_expect";
|
||||
const UNUSED_RECORD_FIELDS: &str = "aiken::check::syntax::unused_record_fields";
|
||||
const UTF8_BYTE_ARRAY_IS_VALID_HEX_STRING: &str =
|
||||
"aiken::check::syntax::bytearray_literal_is_hex_string";
|
||||
|
||||
|
@ -23,6 +24,7 @@ pub enum Quickfix {
|
|||
UnusedImports(Vec<lsp_types::Diagnostic>),
|
||||
Utf8ByteArrayIsValidHexString(lsp_types::Diagnostic),
|
||||
UseLet(lsp_types::Diagnostic),
|
||||
UnusedRecordFields(lsp_types::Diagnostic),
|
||||
}
|
||||
|
||||
fn match_code(
|
||||
|
@ -71,6 +73,10 @@ pub fn assert(diagnostic: lsp_types::Diagnostic) -> Option<Quickfix> {
|
|||
return Some(Quickfix::UseLet(diagnostic));
|
||||
}
|
||||
|
||||
if match_code(&diagnostic, Severity::WARNING, UNUSED_RECORD_FIELDS) {
|
||||
return Some(Quickfix::UnusedRecordFields(diagnostic));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
|
@ -128,6 +134,12 @@ pub fn quickfix(
|
|||
diagnostic,
|
||||
use_let(diagnostic),
|
||||
),
|
||||
Quickfix::UnusedRecordFields(diagnostic) => each_as_distinct_action(
|
||||
&mut actions,
|
||||
text_document,
|
||||
diagnostic,
|
||||
unused_record_fields(diagnostic),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -307,3 +319,19 @@ fn use_let(diagnostic: &lsp_types::Diagnostic) -> Vec<AnnotatedEdit> {
|
|||
},
|
||||
)]
|
||||
}
|
||||
|
||||
fn unused_record_fields(diagnostic: &lsp_types::Diagnostic) -> Vec<AnnotatedEdit> {
|
||||
let mut edits = Vec::new();
|
||||
|
||||
if let Some(serde_json::Value::String(new_text)) = diagnostic.data.as_ref() {
|
||||
edits.push((
|
||||
"Destructure using named fields".to_string(),
|
||||
lsp_types::TextEdit {
|
||||
range: diagnostic.range,
|
||||
new_text: new_text.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
edits
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue