mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 06:11:35 +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
9 changed files with 752 additions and 762 deletions
|
@ -32,14 +32,19 @@ macro_rules! diagnostics {
|
||||||
}
|
}
|
||||||
|
|
||||||
diagnostics![
|
diagnostics![
|
||||||
UnresolvedModule,
|
BreakOutsideOfLoop,
|
||||||
|
InactiveCode,
|
||||||
|
MacroError,
|
||||||
|
MismatchedArgCount,
|
||||||
|
MissingFields,
|
||||||
|
MissingUnsafe,
|
||||||
|
NoSuchField,
|
||||||
|
UnimplementedBuiltinMacro,
|
||||||
UnresolvedExternCrate,
|
UnresolvedExternCrate,
|
||||||
UnresolvedImport,
|
UnresolvedImport,
|
||||||
UnresolvedMacroCall,
|
UnresolvedMacroCall,
|
||||||
|
UnresolvedModule,
|
||||||
UnresolvedProcMacro,
|
UnresolvedProcMacro,
|
||||||
MacroError,
|
|
||||||
MissingFields,
|
|
||||||
InactiveCode,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -88,101 +93,22 @@ pub struct MacroError {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UnimplementedBuiltinMacro {
|
pub struct UnimplementedBuiltinMacro {
|
||||||
pub file: HirFileId,
|
pub node: InFile<SyntaxNodePtr>,
|
||||||
pub node: 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)]
|
#[derive(Debug)]
|
||||||
pub struct NoSuchField {
|
pub struct NoSuchField {
|
||||||
pub file: HirFileId,
|
pub field: InFile<AstPtr<ast::RecordExprField>>,
|
||||||
pub field: 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)]
|
#[derive(Debug)]
|
||||||
pub struct BreakOutsideOfLoop {
|
pub struct BreakOutsideOfLoop {
|
||||||
pub file: HirFileId,
|
pub expr: InFile<AstPtr<ast::Expr>>,
|
||||||
pub expr: 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)]
|
#[derive(Debug)]
|
||||||
pub struct MissingUnsafe {
|
pub struct MissingUnsafe {
|
||||||
pub file: HirFileId,
|
pub expr: InFile<AstPtr<ast::Expr>>,
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[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)]
|
#[derive(Debug)]
|
||||||
pub struct MismatchedArgCount {
|
pub struct MismatchedArgCount {
|
||||||
pub file: HirFileId,
|
pub call_expr: InFile<AstPtr<ast::Expr>>,
|
||||||
pub call_expr: AstPtr<ast::Expr>,
|
|
||||||
pub expected: usize,
|
pub expected: usize,
|
||||||
pub found: 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)]
|
#[derive(Debug)]
|
||||||
pub struct RemoveThisSemicolon {
|
pub struct RemoveThisSemicolon {
|
||||||
pub file: HirFileId,
|
pub file: HirFileId,
|
||||||
|
|
|
@ -606,8 +606,12 @@ impl Module {
|
||||||
let node = ast.to_node(db.upcast());
|
let node = ast.to_node(db.upcast());
|
||||||
// Must have a name, otherwise we wouldn't emit it.
|
// Must have a name, otherwise we wouldn't emit it.
|
||||||
let name = node.name().expect("unimplemented builtin macro with no name");
|
let name = node.name().expect("unimplemented builtin macro with no name");
|
||||||
let ptr = SyntaxNodePtr::from(AstPtr::new(&name));
|
acc.push(
|
||||||
sink.push(UnimplementedBuiltinMacro { file: ast.file_id, node: ptr });
|
UnimplementedBuiltinMacro {
|
||||||
|
node: ast.with_value(SyntaxNodePtr::from(AstPtr::new(&name))),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1073,22 +1077,20 @@ impl Function {
|
||||||
match d {
|
match d {
|
||||||
hir_ty::InferenceDiagnostic::NoSuchField { expr } => {
|
hir_ty::InferenceDiagnostic::NoSuchField { expr } => {
|
||||||
let field = source_map.field_syntax(*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 } => {
|
hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr } => {
|
||||||
let ptr = source_map
|
let expr = source_map
|
||||||
.expr_syntax(*expr)
|
.expr_syntax(*expr)
|
||||||
.expect("break outside of loop in synthetic syntax");
|
.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()) {
|
for expr in hir_ty::diagnostics::missing_unsafe(db, self.id.into()) {
|
||||||
match source_map.expr_syntax(expr) {
|
match source_map.expr_syntax(expr) {
|
||||||
Ok(in_file) => {
|
Ok(expr) => acc.push(MissingUnsafe { expr }.into()),
|
||||||
sink.push(MissingUnsafe { file: in_file.file_id, expr: in_file.value })
|
|
||||||
}
|
|
||||||
Err(SyntheticSyntax) => {
|
Err(SyntheticSyntax) => {
|
||||||
// FIXME: Here and eslwhere in this file, the `expr` was
|
// FIXME: Here and eslwhere in this file, the `expr` was
|
||||||
// desugared, report or assert that this doesn't happen.
|
// desugared, report or assert that this doesn't happen.
|
||||||
|
@ -1174,12 +1176,9 @@ impl Function {
|
||||||
}
|
}
|
||||||
BodyValidationDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
|
BodyValidationDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
|
||||||
match source_map.expr_syntax(call_expr) {
|
match source_map.expr_syntax(call_expr) {
|
||||||
Ok(source_ptr) => sink.push(MismatchedArgCount {
|
Ok(source_ptr) => acc.push(
|
||||||
file: source_ptr.file_id,
|
MismatchedArgCount { call_expr: source_ptr, expected, found }.into(),
|
||||||
call_expr: source_ptr.value,
|
),
|
||||||
expected,
|
|
||||||
found,
|
|
||||||
}),
|
|
||||||
Err(SyntheticSyntax) => (),
|
Err(SyntheticSyntax) => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,19 @@
|
||||||
//! macro-expanded files, but we need to present them to the users in terms of
|
//! macro-expanded files, but we need to present them to the users in terms of
|
||||||
//! original files. So we need to map the ranges.
|
//! 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_extern_crate;
|
||||||
mod unresolved_import;
|
mod unresolved_import;
|
||||||
mod unresolved_macro_call;
|
mod unresolved_macro_call;
|
||||||
|
mod unresolved_module;
|
||||||
mod unresolved_proc_macro;
|
mod unresolved_proc_macro;
|
||||||
mod macro_error;
|
|
||||||
mod inactive_code;
|
|
||||||
mod missing_fields;
|
|
||||||
|
|
||||||
mod fixes;
|
mod fixes;
|
||||||
mod field_shorthand;
|
mod field_shorthand;
|
||||||
|
@ -160,9 +165,6 @@ pub(crate) fn diagnostics(
|
||||||
.on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
|
.on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
|
||||||
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
|
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| {
|
.on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| {
|
||||||
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
|
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
|
||||||
})
|
})
|
||||||
|
@ -185,11 +187,6 @@ pub(crate) fn diagnostics(
|
||||||
.with_code(Some(d.code())),
|
.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.
|
// Only collect experimental diagnostics when they're enabled.
|
||||||
.filter(|diag| !(diag.is_experimental() && config.disable_experimental))
|
.filter(|diag| !(diag.is_experimental() && config.disable_experimental))
|
||||||
.filter(|diag| !config.disabled.contains(diag.code().as_str()));
|
.filter(|diag| !config.disabled.contains(diag.code().as_str()));
|
||||||
|
@ -224,13 +221,18 @@ pub(crate) fn diagnostics(
|
||||||
for diag in diags {
|
for diag in diags {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let d = match diag {
|
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::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
|
||||||
AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d),
|
AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d),
|
||||||
AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&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::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) {
|
AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) {
|
||||||
Some(it) => it,
|
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
|
// Register the required standard library types to make the tests work
|
||||||
fn add_filter_map_with_find_next_boilerplate(body: &str) -> String {
|
fn add_filter_map_with_find_next_boilerplate(body: &str) -> String {
|
||||||
let prefix = r#"
|
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]
|
#[test]
|
||||||
fn missing_semicolon() {
|
fn missing_semicolon() {
|
||||||
check_diagnostics(
|
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…
Add table
Add a link
Reference in a new issue