mirror of
https://github.com/rust-lang/rust.git
synced 2025-02-26 13:54:06 +00:00
Merge #9252
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:
commit
60ca03e8aa
@ -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,
|
||||
|
@ -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) => (),
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
30
crates/ide/src/diagnostics/break_outside_of_loop.rs
Normal file
30
crates/ide/src/diagnostics/break_outside_of_loop.rs
Normal 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
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
272
crates/ide/src/diagnostics/mismatched_arg_count.rs
Normal file
272
crates/ide/src/diagnostics/mismatched_arg_count.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
101
crates/ide/src/diagnostics/missing_unsafe.rs
Normal file
101
crates/ide/src/diagnostics/missing_unsafe.rs
Normal 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
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
286
crates/ide/src/diagnostics/no_such_field.rs
Normal file
286
crates/ide/src/diagnostics/no_such_field.rs
Normal 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
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
19
crates/ide/src/diagnostics/unimplemented_builtin_macro.rs
Normal file
19
crates/ide/src/diagnostics/unimplemented_builtin_macro.rs
Normal 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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user