9252: internal: refactor mismatched args count diagnostic r=matklad a=matklad

bors r+
🤖

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2021-06-13 17:07:04 +00:00 committed by GitHub
commit 60ca03e8aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 752 additions and 762 deletions

View File

@ -32,14 +32,19 @@ macro_rules! diagnostics {
}
diagnostics![
UnresolvedModule,
BreakOutsideOfLoop,
InactiveCode,
MacroError,
MismatchedArgCount,
MissingFields,
MissingUnsafe,
NoSuchField,
UnimplementedBuiltinMacro,
UnresolvedExternCrate,
UnresolvedImport,
UnresolvedMacroCall,
UnresolvedModule,
UnresolvedProcMacro,
MacroError,
MissingFields,
InactiveCode,
];
#[derive(Debug)]
@ -88,101 +93,22 @@ pub struct MacroError {
#[derive(Debug)]
pub struct UnimplementedBuiltinMacro {
pub file: HirFileId,
pub node: SyntaxNodePtr,
pub node: InFile<SyntaxNodePtr>,
}
impl Diagnostic for UnimplementedBuiltinMacro {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("unimplemented-builtin-macro")
}
fn message(&self) -> String {
"unimplemented built-in macro".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.node.clone())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: no-such-field
//
// This diagnostic is triggered if created structure does not have field provided in record.
#[derive(Debug)]
pub struct NoSuchField {
pub file: HirFileId,
pub field: AstPtr<ast::RecordExprField>,
pub field: InFile<AstPtr<ast::RecordExprField>>,
}
impl Diagnostic for NoSuchField {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("no-such-field")
}
fn message(&self) -> String {
"no such field".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.field.clone().into())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: break-outside-of-loop
//
// This diagnostic is triggered if the `break` keyword is used outside of a loop.
#[derive(Debug)]
pub struct BreakOutsideOfLoop {
pub file: HirFileId,
pub expr: AstPtr<ast::Expr>,
pub expr: InFile<AstPtr<ast::Expr>>,
}
impl Diagnostic for BreakOutsideOfLoop {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("break-outside-of-loop")
}
fn message(&self) -> String {
"break outside of loop".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile { file_id: self.file, value: self.expr.clone().into() }
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: missing-unsafe
//
// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
#[derive(Debug)]
pub struct MissingUnsafe {
pub file: HirFileId,
pub expr: AstPtr<ast::Expr>,
}
impl Diagnostic for MissingUnsafe {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("missing-unsafe")
}
fn message(&self) -> String {
format!("This operation is unsafe and requires an unsafe function or block")
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile { file_id: self.file, value: self.expr.clone().into() }
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
pub expr: InFile<AstPtr<ast::Expr>>,
}
#[derive(Debug)]
@ -218,36 +144,13 @@ impl Diagnostic for ReplaceFilterMapNextWithFindMap {
}
}
// Diagnostic: mismatched-arg-count
//
// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
#[derive(Debug)]
pub struct MismatchedArgCount {
pub file: HirFileId,
pub call_expr: AstPtr<ast::Expr>,
pub call_expr: InFile<AstPtr<ast::Expr>>,
pub expected: usize,
pub found: usize,
}
impl Diagnostic for MismatchedArgCount {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("mismatched-arg-count")
}
fn message(&self) -> String {
let s = if self.expected == 1 { "" } else { "s" };
format!("Expected {} argument{}, found {}", self.expected, s, self.found)
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile { file_id: self.file, value: self.call_expr.clone().into() }
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
fn is_experimental(&self) -> bool {
true
}
}
#[derive(Debug)]
pub struct RemoveThisSemicolon {
pub file: HirFileId,

View File

@ -606,8 +606,12 @@ impl Module {
let node = ast.to_node(db.upcast());
// Must have a name, otherwise we wouldn't emit it.
let name = node.name().expect("unimplemented builtin macro with no name");
let ptr = SyntaxNodePtr::from(AstPtr::new(&name));
sink.push(UnimplementedBuiltinMacro { file: ast.file_id, node: ptr });
acc.push(
UnimplementedBuiltinMacro {
node: ast.with_value(SyntaxNodePtr::from(AstPtr::new(&name))),
}
.into(),
);
}
}
}
@ -1073,22 +1077,20 @@ impl Function {
match d {
hir_ty::InferenceDiagnostic::NoSuchField { expr } => {
let field = source_map.field_syntax(*expr);
sink.push(NoSuchField { file: field.file_id, field: field.value })
acc.push(NoSuchField { field }.into())
}
hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr } => {
let ptr = source_map
let expr = source_map
.expr_syntax(*expr)
.expect("break outside of loop in synthetic syntax");
sink.push(BreakOutsideOfLoop { file: ptr.file_id, expr: ptr.value })
acc.push(BreakOutsideOfLoop { expr }.into())
}
}
}
for expr in hir_ty::diagnostics::missing_unsafe(db, self.id.into()) {
match source_map.expr_syntax(expr) {
Ok(in_file) => {
sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value })
}
Ok(expr) => acc.push(MissingUnsafe { expr }.into()),
Err(SyntheticSyntax) => {
// FIXME: Here and eslwhere in this file, the `expr` was
// desugared, report or assert that this doesn't happen.
@ -1174,12 +1176,9 @@ impl Function {
}
BodyValidationDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
match source_map.expr_syntax(call_expr) {
Ok(source_ptr) => sink.push(MismatchedArgCount {
file: source_ptr.file_id,
call_expr: source_ptr.value,
expected,
found,
}),
Ok(source_ptr) => acc.push(
MismatchedArgCount { call_expr: source_ptr, expected, found }.into(),
),
Err(SyntheticSyntax) => (),
}
}

View File

@ -4,14 +4,19 @@
//! macro-expanded files, but we need to present them to the users in terms of
//! original files. So we need to map the ranges.
mod unresolved_module;
mod break_outside_of_loop;
mod inactive_code;
mod macro_error;
mod mismatched_arg_count;
mod missing_fields;
mod missing_unsafe;
mod no_such_field;
mod unimplemented_builtin_macro;
mod unresolved_extern_crate;
mod unresolved_import;
mod unresolved_macro_call;
mod unresolved_module;
mod unresolved_proc_macro;
mod macro_error;
mod inactive_code;
mod missing_fields;
mod fixes;
mod field_shorthand;
@ -160,9 +165,6 @@ pub(crate) fn diagnostics(
.on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
})
.on::<hir::diagnostics::NoSuchField, _>(|d| {
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
})
.on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| {
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
})
@ -185,11 +187,6 @@ pub(crate) fn diagnostics(
.with_code(Some(d.code())),
);
})
.on::<hir::diagnostics::UnimplementedBuiltinMacro, _>(|d| {
let display_range = sema.diagnostics_display_range(d.display_source()).range;
res.borrow_mut()
.push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
})
// Only collect experimental diagnostics when they're enabled.
.filter(|diag| !(diag.is_experimental() && config.disable_experimental))
.filter(|diag| !config.disabled.contains(diag.code().as_str()));
@ -224,13 +221,18 @@ pub(crate) fn diagnostics(
for diag in diags {
#[rustfmt::skip]
let d = match diag {
AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d),
AnyDiagnostic::BreakOutsideOfLoop(d) => break_outside_of_loop::break_outside_of_loop(&ctx, &d),
AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d),
AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
AnyDiagnostic::MissingUnsafe(d) => missing_unsafe::missing_unsafe(&ctx, &d),
AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d),
AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&ctx, &d),
AnyDiagnostic::UnimplementedBuiltinMacro(d) => unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d),
AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d),
AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d),
AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d),
AnyDiagnostic::UnresolvedProcMacro(d) => unresolved_proc_macro::unresolved_proc_macro(&ctx, &d),
AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d),
AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) {
Some(it) => it,
@ -715,223 +717,6 @@ mod foo;
);
}
#[test]
fn break_outside_of_loop() {
check_diagnostics(
r#"
fn foo() { break; }
//^^^^^ break outside of loop
"#,
);
}
#[test]
fn no_such_field_diagnostics() {
check_diagnostics(
r#"
struct S { foo: i32, bar: () }
impl S {
fn new() -> S {
S {
//^ Missing structure fields:
//| - bar
foo: 92,
baz: 62,
//^^^^^^^ no such field
}
}
}
"#,
);
}
#[test]
fn no_such_field_with_feature_flag_diagnostics() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
struct MyStruct {
my_val: usize,
#[cfg(feature = "foo")]
bar: bool,
}
impl MyStruct {
#[cfg(feature = "foo")]
pub(crate) fn new(my_val: usize, bar: bool) -> Self {
Self { my_val, bar }
}
#[cfg(not(feature = "foo"))]
pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
Self { my_val }
}
}
"#,
);
}
#[test]
fn no_such_field_enum_with_feature_flag_diagnostics() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
enum Foo {
#[cfg(not(feature = "foo"))]
Buz,
#[cfg(feature = "foo")]
Bar,
Baz
}
fn test_fn(f: Foo) {
match f {
Foo::Bar => {},
Foo::Baz => {},
}
}
"#,
);
}
#[test]
fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
struct S {
#[cfg(feature = "foo")]
foo: u32,
#[cfg(not(feature = "foo"))]
bar: u32,
}
impl S {
#[cfg(feature = "foo")]
fn new(foo: u32) -> Self {
Self { foo }
}
#[cfg(not(feature = "foo"))]
fn new(bar: u32) -> Self {
Self { bar }
}
fn new2(bar: u32) -> Self {
#[cfg(feature = "foo")]
{ Self { foo: bar } }
#[cfg(not(feature = "foo"))]
{ Self { bar } }
}
fn new2(val: u32) -> Self {
Self {
#[cfg(feature = "foo")]
foo: val,
#[cfg(not(feature = "foo"))]
bar: val,
}
}
}
"#,
);
}
#[test]
fn no_such_field_with_type_macro() {
check_diagnostics(
r#"
macro_rules! Type { () => { u32 }; }
struct Foo { bar: Type![] }
impl Foo {
fn new() -> Self {
Foo { bar: 0 }
}
}
"#,
);
}
#[test]
fn missing_unsafe_diagnostic_with_raw_ptr() {
check_diagnostics(
r#"
fn main() {
let x = &5 as *const usize;
unsafe { let y = *x; }
let z = *x;
} //^^ This operation is unsafe and requires an unsafe function or block
"#,
)
}
#[test]
fn missing_unsafe_diagnostic_with_unsafe_call() {
check_diagnostics(
r#"
struct HasUnsafe;
impl HasUnsafe {
unsafe fn unsafe_fn(&self) {
let x = &5 as *const usize;
let y = *x;
}
}
unsafe fn unsafe_fn() {
let x = &5 as *const usize;
let y = *x;
}
fn main() {
unsafe_fn();
//^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
HasUnsafe.unsafe_fn();
//^^^^^^^^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
unsafe {
unsafe_fn();
HasUnsafe.unsafe_fn();
}
}
"#,
);
}
#[test]
fn missing_unsafe_diagnostic_with_static_mut() {
check_diagnostics(
r#"
struct Ty {
a: u8,
}
static mut STATIC_MUT: Ty = Ty { a: 0 };
fn main() {
let x = STATIC_MUT.a;
//^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
unsafe {
let x = STATIC_MUT.a;
}
}
"#,
);
}
#[test]
fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
check_diagnostics(
r#"
extern "rust-intrinsic" {
pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
}
fn main() {
let _ = bitreverse(12);
let _ = floorf32(12.0);
//^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
}
"#,
);
}
// Register the required standard library types to make the tests work
fn add_filter_map_with_find_next_boilerplate(body: &str) -> String {
let prefix = r#"
@ -1053,256 +838,6 @@ fn x(a: S) {
)
}
#[test]
fn simple_free_fn_zero() {
check_diagnostics(
r#"
fn zero() {}
fn f() { zero(1); }
//^^^^^^^ Expected 0 arguments, found 1
"#,
);
check_diagnostics(
r#"
fn zero() {}
fn f() { zero(); }
"#,
);
}
#[test]
fn simple_free_fn_one() {
check_diagnostics(
r#"
fn one(arg: u8) {}
fn f() { one(); }
//^^^^^ Expected 1 argument, found 0
"#,
);
check_diagnostics(
r#"
fn one(arg: u8) {}
fn f() { one(1); }
"#,
);
}
#[test]
fn method_as_fn() {
check_diagnostics(
r#"
struct S;
impl S { fn method(&self) {} }
fn f() {
S::method();
} //^^^^^^^^^^^ Expected 1 argument, found 0
"#,
);
check_diagnostics(
r#"
struct S;
impl S { fn method(&self) {} }
fn f() {
S::method(&S);
S.method();
}
"#,
);
}
#[test]
fn method_with_arg() {
check_diagnostics(
r#"
struct S;
impl S { fn method(&self, arg: u8) {} }
fn f() {
S.method();
} //^^^^^^^^^^ Expected 1 argument, found 0
"#,
);
check_diagnostics(
r#"
struct S;
impl S { fn method(&self, arg: u8) {} }
fn f() {
S::method(&S, 0);
S.method(1);
}
"#,
);
}
#[test]
fn method_unknown_receiver() {
// note: this is incorrect code, so there might be errors on this in the
// future, but we shouldn't emit an argument count diagnostic here
check_diagnostics(
r#"
trait Foo { fn method(&self, arg: usize) {} }
fn f() {
let x;
x.method();
}
"#,
);
}
#[test]
fn tuple_struct() {
check_diagnostics(
r#"
struct Tup(u8, u16);
fn f() {
Tup(0);
} //^^^^^^ Expected 2 arguments, found 1
"#,
)
}
#[test]
fn enum_variant() {
check_diagnostics(
r#"
enum En { Variant(u8, u16), }
fn f() {
En::Variant(0);
} //^^^^^^^^^^^^^^ Expected 2 arguments, found 1
"#,
)
}
#[test]
fn enum_variant_type_macro() {
check_diagnostics(
r#"
macro_rules! Type {
() => { u32 };
}
enum Foo {
Bar(Type![])
}
impl Foo {
fn new() {
Foo::Bar(0);
Foo::Bar(0, 1);
//^^^^^^^^^^^^^^ Expected 1 argument, found 2
Foo::Bar();
//^^^^^^^^^^ Expected 1 argument, found 0
}
}
"#,
);
}
#[test]
fn varargs() {
check_diagnostics(
r#"
extern "C" {
fn fixed(fixed: u8);
fn varargs(fixed: u8, ...);
fn varargs2(...);
}
fn f() {
unsafe {
fixed(0);
fixed(0, 1);
//^^^^^^^^^^^ Expected 1 argument, found 2
varargs(0);
varargs(0, 1);
varargs2();
varargs2(0);
varargs2(0, 1);
}
}
"#,
)
}
#[test]
fn arg_count_lambda() {
check_diagnostics(
r#"
fn main() {
let f = |()| ();
f();
//^^^ Expected 1 argument, found 0
f(());
f((), ());
//^^^^^^^^^ Expected 1 argument, found 2
}
"#,
)
}
#[test]
fn cfgd_out_call_arguments() {
check_diagnostics(
r#"
struct C(#[cfg(FALSE)] ());
impl C {
fn new() -> Self {
Self(
#[cfg(FALSE)]
(),
)
}
fn method(&self) {}
}
fn main() {
C::new().method(#[cfg(FALSE)] 0);
}
"#,
);
}
#[test]
fn cfgd_out_fn_params() {
check_diagnostics(
r#"
fn foo(#[cfg(NEVER)] x: ()) {}
struct S;
impl S {
fn method(#[cfg(NEVER)] self) {}
fn method2(#[cfg(NEVER)] self, arg: u8) {}
fn method3(self, #[cfg(NEVER)] arg: u8) {}
}
extern "C" {
fn fixed(fixed: u8, #[cfg(NEVER)] ...);
fn varargs(#[cfg(not(NEVER))] ...);
}
fn main() {
foo();
S::method();
S::method2(0);
S::method3(S);
S.method3();
unsafe {
fixed(0);
varargs(1, 2, 3);
}
}
"#,
)
}
#[test]
fn missing_semicolon() {
check_diagnostics(

View File

@ -0,0 +1,30 @@
use crate::diagnostics::{Diagnostic, DiagnosticsContext};
// Diagnostic: break-outside-of-loop
//
// This diagnostic is triggered if the `break` keyword is used outside of a loop.
pub(super) fn break_outside_of_loop(
ctx: &DiagnosticsContext<'_>,
d: &hir::BreakOutsideOfLoop,
) -> Diagnostic {
Diagnostic::new(
"break-outside-of-loop",
"break outside of loop",
ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
)
}
#[cfg(test)]
mod tests {
use crate::diagnostics::tests::check_diagnostics;
#[test]
fn break_outside_of_loop() {
check_diagnostics(
r#"
fn foo() { break; }
//^^^^^ break outside of loop
"#,
);
}
}

View File

@ -1,156 +1 @@
use hir::{db::AstDatabase, diagnostics::NoSuchField, HasSource, HirDisplay, Semantics};
use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
use syntax::{
ast::{self, edit::IndentLevel, make},
AstNode,
};
use text_edit::TextEdit;
use crate::{
diagnostics::{fix, DiagnosticWithFixes},
Assist, AssistResolveStrategy,
};
impl DiagnosticWithFixes for NoSuchField {
fn fixes(
&self,
sema: &Semantics<RootDatabase>,
_resolve: &AssistResolveStrategy,
) -> Option<Vec<Assist>> {
let root = sema.db.parse_or_expand(self.file)?;
missing_record_expr_field_fixes(
sema,
self.file.original_file(sema.db),
&self.field.to_node(&root),
)
}
}
fn missing_record_expr_field_fixes(
sema: &Semantics<RootDatabase>,
usage_file_id: FileId,
record_expr_field: &ast::RecordExprField,
) -> Option<Vec<Assist>> {
let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
let def_id = sema.resolve_variant(record_lit)?;
let module;
let def_file_id;
let record_fields = match def_id {
hir::VariantDef::Struct(s) => {
module = s.module(sema.db);
let source = s.source(sema.db)?;
def_file_id = source.file_id;
let fields = source.value.field_list()?;
record_field_list(fields)?
}
hir::VariantDef::Union(u) => {
module = u.module(sema.db);
let source = u.source(sema.db)?;
def_file_id = source.file_id;
source.value.record_field_list()?
}
hir::VariantDef::Variant(e) => {
module = e.module(sema.db);
let source = e.source(sema.db)?;
def_file_id = source.file_id;
let fields = source.value.field_list()?;
record_field_list(fields)?
}
};
let def_file_id = def_file_id.original_file(sema.db);
let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
if new_field_type.is_unknown() {
return None;
}
let new_field = make::record_field(
None,
make::name(&record_expr_field.field_name()?.text()),
make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
);
let last_field = record_fields.fields().last()?;
let last_field_syntax = last_field.syntax();
let indent = IndentLevel::from_node(last_field_syntax);
let mut new_field = new_field.to_string();
if usage_file_id != def_file_id {
new_field = format!("pub(crate) {}", new_field);
}
new_field = format!("\n{}{}", indent, new_field);
let needs_comma = !last_field_syntax.to_string().ends_with(',');
if needs_comma {
new_field = format!(",{}", new_field);
}
let source_change = SourceChange::from_text_edit(
def_file_id,
TextEdit::insert(last_field_syntax.text_range().end(), new_field),
);
return Some(vec![fix(
"create_field",
"Create field",
source_change,
record_expr_field.syntax().text_range(),
)]);
fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
match field_def_list {
ast::FieldList::RecordFieldList(it) => Some(it),
ast::FieldList::TupleFieldList(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use crate::diagnostics::tests::check_fix;
#[test]
fn test_add_field_from_usage() {
check_fix(
r"
fn main() {
Foo { bar: 3, baz$0: false};
}
struct Foo {
bar: i32
}
",
r"
fn main() {
Foo { bar: 3, baz: false};
}
struct Foo {
bar: i32,
baz: bool
}
",
)
}
#[test]
fn test_add_field_in_other_file_from_usage() {
check_fix(
r#"
//- /main.rs
mod foo;
fn main() {
foo::Foo { bar: 3, $0baz: false};
}
//- /foo.rs
struct Foo {
bar: i32
}
"#,
r#"
struct Foo {
bar: i32,
pub(crate) baz: bool
}
"#,
)
}
}

View File

@ -0,0 +1,272 @@
use crate::diagnostics::{Diagnostic, DiagnosticsContext};
// Diagnostic: mismatched-arg-count
//
// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
pub(super) fn mismatched_arg_count(
ctx: &DiagnosticsContext<'_>,
d: &hir::MismatchedArgCount,
) -> Diagnostic {
let s = if d.expected == 1 { "" } else { "s" };
let message = format!("expected {} argument{}, found {}", d.expected, s, d.found);
Diagnostic::new(
"mismatched-arg-count",
message,
ctx.sema.diagnostics_display_range(d.call_expr.clone().map(|it| it.into())).range,
)
}
#[cfg(test)]
mod tests {
use crate::diagnostics::tests::check_diagnostics;
#[test]
fn simple_free_fn_zero() {
check_diagnostics(
r#"
fn zero() {}
fn f() { zero(1); }
//^^^^^^^ expected 0 arguments, found 1
"#,
);
check_diagnostics(
r#"
fn zero() {}
fn f() { zero(); }
"#,
);
}
#[test]
fn simple_free_fn_one() {
check_diagnostics(
r#"
fn one(arg: u8) {}
fn f() { one(); }
//^^^^^ expected 1 argument, found 0
"#,
);
check_diagnostics(
r#"
fn one(arg: u8) {}
fn f() { one(1); }
"#,
);
}
#[test]
fn method_as_fn() {
check_diagnostics(
r#"
struct S;
impl S { fn method(&self) {} }
fn f() {
S::method();
} //^^^^^^^^^^^ expected 1 argument, found 0
"#,
);
check_diagnostics(
r#"
struct S;
impl S { fn method(&self) {} }
fn f() {
S::method(&S);
S.method();
}
"#,
);
}
#[test]
fn method_with_arg() {
check_diagnostics(
r#"
struct S;
impl S { fn method(&self, arg: u8) {} }
fn f() {
S.method();
} //^^^^^^^^^^ expected 1 argument, found 0
"#,
);
check_diagnostics(
r#"
struct S;
impl S { fn method(&self, arg: u8) {} }
fn f() {
S::method(&S, 0);
S.method(1);
}
"#,
);
}
#[test]
fn method_unknown_receiver() {
// note: this is incorrect code, so there might be errors on this in the
// future, but we shouldn't emit an argument count diagnostic here
check_diagnostics(
r#"
trait Foo { fn method(&self, arg: usize) {} }
fn f() {
let x;
x.method();
}
"#,
);
}
#[test]
fn tuple_struct() {
check_diagnostics(
r#"
struct Tup(u8, u16);
fn f() {
Tup(0);
} //^^^^^^ expected 2 arguments, found 1
"#,
)
}
#[test]
fn enum_variant() {
check_diagnostics(
r#"
enum En { Variant(u8, u16), }
fn f() {
En::Variant(0);
} //^^^^^^^^^^^^^^ expected 2 arguments, found 1
"#,
)
}
#[test]
fn enum_variant_type_macro() {
check_diagnostics(
r#"
macro_rules! Type {
() => { u32 };
}
enum Foo {
Bar(Type![])
}
impl Foo {
fn new() {
Foo::Bar(0);
Foo::Bar(0, 1);
//^^^^^^^^^^^^^^ expected 1 argument, found 2
Foo::Bar();
//^^^^^^^^^^ expected 1 argument, found 0
}
}
"#,
);
}
#[test]
fn varargs() {
check_diagnostics(
r#"
extern "C" {
fn fixed(fixed: u8);
fn varargs(fixed: u8, ...);
fn varargs2(...);
}
fn f() {
unsafe {
fixed(0);
fixed(0, 1);
//^^^^^^^^^^^ expected 1 argument, found 2
varargs(0);
varargs(0, 1);
varargs2();
varargs2(0);
varargs2(0, 1);
}
}
"#,
)
}
#[test]
fn arg_count_lambda() {
check_diagnostics(
r#"
fn main() {
let f = |()| ();
f();
//^^^ expected 1 argument, found 0
f(());
f((), ());
//^^^^^^^^^ expected 1 argument, found 2
}
"#,
)
}
#[test]
fn cfgd_out_call_arguments() {
check_diagnostics(
r#"
struct C(#[cfg(FALSE)] ());
impl C {
fn new() -> Self {
Self(
#[cfg(FALSE)]
(),
)
}
fn method(&self) {}
}
fn main() {
C::new().method(#[cfg(FALSE)] 0);
}
"#,
);
}
#[test]
fn cfgd_out_fn_params() {
check_diagnostics(
r#"
fn foo(#[cfg(NEVER)] x: ()) {}
struct S;
impl S {
fn method(#[cfg(NEVER)] self) {}
fn method2(#[cfg(NEVER)] self, arg: u8) {}
fn method3(self, #[cfg(NEVER)] arg: u8) {}
}
extern "C" {
fn fixed(fixed: u8, #[cfg(NEVER)] ...);
fn varargs(#[cfg(not(NEVER))] ...);
}
fn main() {
foo();
S::method();
S::method2(0);
S::method3(S);
S.method3();
unsafe {
fixed(0);
varargs(1, 2, 3);
}
}
"#,
)
}
}

View File

@ -0,0 +1,101 @@
use crate::diagnostics::{Diagnostic, DiagnosticsContext};
// Diagnostic: missing-unsafe
//
// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
pub(super) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Diagnostic {
Diagnostic::new(
"missing-unsafe",
"this operation is unsafe and requires an unsafe function or block",
ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
)
}
#[cfg(test)]
mod tests {
use crate::diagnostics::tests::check_diagnostics;
#[test]
fn missing_unsafe_diagnostic_with_raw_ptr() {
check_diagnostics(
r#"
fn main() {
let x = &5 as *const usize;
unsafe { let y = *x; }
let z = *x;
} //^^ this operation is unsafe and requires an unsafe function or block
"#,
)
}
#[test]
fn missing_unsafe_diagnostic_with_unsafe_call() {
check_diagnostics(
r#"
struct HasUnsafe;
impl HasUnsafe {
unsafe fn unsafe_fn(&self) {
let x = &5 as *const usize;
let y = *x;
}
}
unsafe fn unsafe_fn() {
let x = &5 as *const usize;
let y = *x;
}
fn main() {
unsafe_fn();
//^^^^^^^^^^^ this operation is unsafe and requires an unsafe function or block
HasUnsafe.unsafe_fn();
//^^^^^^^^^^^^^^^^^^^^^ this operation is unsafe and requires an unsafe function or block
unsafe {
unsafe_fn();
HasUnsafe.unsafe_fn();
}
}
"#,
);
}
#[test]
fn missing_unsafe_diagnostic_with_static_mut() {
check_diagnostics(
r#"
struct Ty {
a: u8,
}
static mut STATIC_MUT: Ty = Ty { a: 0 };
fn main() {
let x = STATIC_MUT.a;
//^^^^^^^^^^ this operation is unsafe and requires an unsafe function or block
unsafe {
let x = STATIC_MUT.a;
}
}
"#,
);
}
#[test]
fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
check_diagnostics(
r#"
extern "rust-intrinsic" {
pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
}
fn main() {
let _ = bitreverse(12);
let _ = floorf32(12.0);
//^^^^^^^^^^^^^^ this operation is unsafe and requires an unsafe function or block
}
"#,
);
}
}

View File

@ -0,0 +1,286 @@
use hir::{db::AstDatabase, HasSource, HirDisplay, Semantics};
use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
use syntax::{
ast::{self, edit::IndentLevel, make},
AstNode,
};
use text_edit::TextEdit;
use crate::{
diagnostics::{fix, Diagnostic, DiagnosticsContext},
Assist,
};
// Diagnostic: no-such-field
//
// This diagnostic is triggered if created structure does not have field provided in record.
pub(super) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
Diagnostic::new(
"no-such-field",
"no such field".to_string(),
ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range,
)
.with_fixes(fixes(ctx, d))
}
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
let root = ctx.sema.db.parse_or_expand(d.field.file_id)?;
missing_record_expr_field_fixes(
&ctx.sema,
d.field.file_id.original_file(ctx.sema.db),
&d.field.value.to_node(&root),
)
}
fn missing_record_expr_field_fixes(
sema: &Semantics<RootDatabase>,
usage_file_id: FileId,
record_expr_field: &ast::RecordExprField,
) -> Option<Vec<Assist>> {
let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
let def_id = sema.resolve_variant(record_lit)?;
let module;
let def_file_id;
let record_fields = match def_id {
hir::VariantDef::Struct(s) => {
module = s.module(sema.db);
let source = s.source(sema.db)?;
def_file_id = source.file_id;
let fields = source.value.field_list()?;
record_field_list(fields)?
}
hir::VariantDef::Union(u) => {
module = u.module(sema.db);
let source = u.source(sema.db)?;
def_file_id = source.file_id;
source.value.record_field_list()?
}
hir::VariantDef::Variant(e) => {
module = e.module(sema.db);
let source = e.source(sema.db)?;
def_file_id = source.file_id;
let fields = source.value.field_list()?;
record_field_list(fields)?
}
};
let def_file_id = def_file_id.original_file(sema.db);
let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
if new_field_type.is_unknown() {
return None;
}
let new_field = make::record_field(
None,
make::name(&record_expr_field.field_name()?.text()),
make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
);
let last_field = record_fields.fields().last()?;
let last_field_syntax = last_field.syntax();
let indent = IndentLevel::from_node(last_field_syntax);
let mut new_field = new_field.to_string();
if usage_file_id != def_file_id {
new_field = format!("pub(crate) {}", new_field);
}
new_field = format!("\n{}{}", indent, new_field);
let needs_comma = !last_field_syntax.to_string().ends_with(',');
if needs_comma {
new_field = format!(",{}", new_field);
}
let source_change = SourceChange::from_text_edit(
def_file_id,
TextEdit::insert(last_field_syntax.text_range().end(), new_field),
);
return Some(vec![fix(
"create_field",
"Create field",
source_change,
record_expr_field.syntax().text_range(),
)]);
fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
match field_def_list {
ast::FieldList::RecordFieldList(it) => Some(it),
ast::FieldList::TupleFieldList(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use crate::diagnostics::tests::{check_diagnostics, check_fix};
#[test]
fn no_such_field_diagnostics() {
check_diagnostics(
r#"
struct S { foo: i32, bar: () }
impl S {
fn new() -> S {
S {
//^ Missing structure fields:
//| - bar
foo: 92,
baz: 62,
//^^^^^^^ no such field
}
}
}
"#,
);
}
#[test]
fn no_such_field_with_feature_flag_diagnostics() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
struct MyStruct {
my_val: usize,
#[cfg(feature = "foo")]
bar: bool,
}
impl MyStruct {
#[cfg(feature = "foo")]
pub(crate) fn new(my_val: usize, bar: bool) -> Self {
Self { my_val, bar }
}
#[cfg(not(feature = "foo"))]
pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
Self { my_val }
}
}
"#,
);
}
#[test]
fn no_such_field_enum_with_feature_flag_diagnostics() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
enum Foo {
#[cfg(not(feature = "foo"))]
Buz,
#[cfg(feature = "foo")]
Bar,
Baz
}
fn test_fn(f: Foo) {
match f {
Foo::Bar => {},
Foo::Baz => {},
}
}
"#,
);
}
#[test]
fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
struct S {
#[cfg(feature = "foo")]
foo: u32,
#[cfg(not(feature = "foo"))]
bar: u32,
}
impl S {
#[cfg(feature = "foo")]
fn new(foo: u32) -> Self {
Self { foo }
}
#[cfg(not(feature = "foo"))]
fn new(bar: u32) -> Self {
Self { bar }
}
fn new2(bar: u32) -> Self {
#[cfg(feature = "foo")]
{ Self { foo: bar } }
#[cfg(not(feature = "foo"))]
{ Self { bar } }
}
fn new2(val: u32) -> Self {
Self {
#[cfg(feature = "foo")]
foo: val,
#[cfg(not(feature = "foo"))]
bar: val,
}
}
}
"#,
);
}
#[test]
fn no_such_field_with_type_macro() {
check_diagnostics(
r#"
macro_rules! Type { () => { u32 }; }
struct Foo { bar: Type![] }
impl Foo {
fn new() -> Self {
Foo { bar: 0 }
}
}
"#,
);
}
#[test]
fn test_add_field_from_usage() {
check_fix(
r"
fn main() {
Foo { bar: 3, baz$0: false};
}
struct Foo {
bar: i32
}
",
r"
fn main() {
Foo { bar: 3, baz: false};
}
struct Foo {
bar: i32,
baz: bool
}
",
)
}
#[test]
fn test_add_field_in_other_file_from_usage() {
check_fix(
r#"
//- /main.rs
mod foo;
fn main() {
foo::Foo { bar: 3, $0baz: false};
}
//- /foo.rs
struct Foo {
bar: i32
}
"#,
r#"
struct Foo {
bar: i32,
pub(crate) baz: bool
}
"#,
)
}
}

View File

@ -0,0 +1,19 @@
use crate::{
diagnostics::{Diagnostic, DiagnosticsContext},
Severity,
};
// Diagnostic: unimplemented-builtin-macro
//
// This diagnostic is shown for builtin macros which are not yet implemented by rust-analyzer
pub(super) fn unimplemented_builtin_macro(
ctx: &DiagnosticsContext<'_>,
d: &hir::UnimplementedBuiltinMacro,
) -> Diagnostic {
Diagnostic::new(
"unimplemented-builtin-macro",
"unimplemented built-in macro".to_string(),
ctx.sema.diagnostics_display_range(d.node.clone()).range,
)
.severity(Severity::WeakWarning)
}