2021-04-09 12:39:07 +00:00
|
|
|
//! Type inference-based diagnostics.
|
2020-07-14 08:52:18 +00:00
|
|
|
mod expr;
|
2020-07-14 08:18:08 +00:00
|
|
|
mod match_check;
|
2020-07-14 08:52:18 +00:00
|
|
|
mod unsafe_check;
|
2020-10-03 09:48:02 +00:00
|
|
|
mod decl_check;
|
2019-11-27 14:46:02 +00:00
|
|
|
|
2020-10-03 09:48:02 +00:00
|
|
|
use std::{any::Any, fmt};
|
2019-11-27 14:46:02 +00:00
|
|
|
|
2020-12-17 00:19:56 +00:00
|
|
|
use base_db::CrateId;
|
2020-10-03 09:48:02 +00:00
|
|
|
use hir_def::{DefWithBodyId, ModuleDefId};
|
2020-08-10 21:37:23 +00:00
|
|
|
use hir_expand::{name::Name, HirFileId, InFile};
|
2020-03-28 10:20:34 +00:00
|
|
|
use stdx::format_to;
|
2020-08-12 16:26:51 +00:00
|
|
|
use syntax::{ast, AstPtr, SyntaxNodePtr};
|
2019-11-27 14:46:02 +00:00
|
|
|
|
internal: move diagnostics to hir
The idea here is to eventually get rid of `dyn Diagnostic` and
`DiagnosticSink` infrastructure altogether, and just have a `enum
hir::Diagnostic` instead.
The problem with `dyn Diagnostic` is that it is defined in the lowest
level of the stack (hir_expand), but is used by the highest level (ide).
As a first step, we free hir_expand and hir_def from `dyn Diagnostic`
and kick the can up to `hir_ty`, as an intermediate state. The plan is
then to move DiagnosticSink similarly to the hir crate, and, as final
third step, remove its usage from the ide.
One currently unsolved problem is testing. You can notice that the test
which checks precise diagnostic ranges, unresolved_import_in_use_tree,
was moved to the ide layer. Logically, only IDE should have the infra to
render a specific range.
At the same time, the range is determined with the data produced in
hir_def and hir crates, so this layering is rather unfortunate. Working
on hir_def shouldn't require compiling `ide` for testing.
2021-05-23 20:31:59 +00:00
|
|
|
use crate::{
|
|
|
|
db::HirDatabase,
|
|
|
|
diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink},
|
|
|
|
};
|
2020-07-14 08:28:55 +00:00
|
|
|
|
2020-07-14 08:52:18 +00:00
|
|
|
pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields};
|
|
|
|
|
2020-10-03 09:48:02 +00:00
|
|
|
pub fn validate_module_item(
|
|
|
|
db: &dyn HirDatabase,
|
2020-12-17 00:19:56 +00:00
|
|
|
krate: CrateId,
|
2020-10-03 09:48:02 +00:00
|
|
|
owner: ModuleDefId,
|
|
|
|
sink: &mut DiagnosticSink<'_>,
|
|
|
|
) {
|
2020-10-04 06:26:39 +00:00
|
|
|
let _p = profile::span("validate_module_item");
|
2020-12-17 00:19:56 +00:00
|
|
|
let mut validator = decl_check::DeclValidator::new(db, krate, sink);
|
|
|
|
validator.validate_item(owner);
|
2020-10-03 09:48:02 +00:00
|
|
|
}
|
|
|
|
|
2020-07-14 08:28:55 +00:00
|
|
|
pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
|
2020-08-12 14:32:36 +00:00
|
|
|
let _p = profile::span("validate_body");
|
2020-07-14 08:28:55 +00:00
|
|
|
let infer = db.infer(owner);
|
|
|
|
infer.add_diagnostics(db, owner, sink);
|
|
|
|
let mut validator = expr::ExprValidator::new(owner, infer.clone(), sink);
|
|
|
|
validator.validate_body(db);
|
|
|
|
let mut validator = unsafe_check::UnsafeValidator::new(owner, infer, sink);
|
|
|
|
validator.validate_body(db);
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:29:06 +00:00
|
|
|
// Diagnostic: no-such-field
|
|
|
|
//
|
|
|
|
// This diagnostic is triggered if created structure does not have field provided in record.
|
2019-11-27 14:46:02 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct NoSuchField {
|
|
|
|
pub file: HirFileId,
|
2020-07-30 14:21:30 +00:00
|
|
|
pub field: AstPtr<ast::RecordExprField>,
|
2019-11-27 14:46:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Diagnostic for NoSuchField {
|
2020-08-18 16:39:43 +00:00
|
|
|
fn code(&self) -> DiagnosticCode {
|
|
|
|
DiagnosticCode("no-such-field")
|
2020-08-07 10:18:47 +00:00
|
|
|
}
|
|
|
|
|
2019-11-27 14:46:02 +00:00
|
|
|
fn message(&self) -> String {
|
|
|
|
"no such field".to_string()
|
|
|
|
}
|
|
|
|
|
2020-08-11 14:15:11 +00:00
|
|
|
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
2020-04-17 11:06:02 +00:00
|
|
|
InFile::new(self.file, self.field.clone().into())
|
2019-11-27 14:46:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:29:06 +00:00
|
|
|
// Diagnostic: missing-structure-fields
|
|
|
|
//
|
|
|
|
// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
|
|
|
|
//
|
|
|
|
// Example:
|
|
|
|
//
|
|
|
|
// ```rust
|
|
|
|
// struct A { a: u8, b: u8 }
|
|
|
|
//
|
|
|
|
// let a = A { a: 10 };
|
|
|
|
// ```
|
2019-11-27 14:46:02 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MissingFields {
|
|
|
|
pub file: HirFileId,
|
2020-08-08 22:59:26 +00:00
|
|
|
pub field_list_parent: AstPtr<ast::RecordExpr>,
|
2020-07-27 20:32:16 +00:00
|
|
|
pub field_list_parent_path: Option<AstPtr<ast::Path>>,
|
2019-11-27 14:46:02 +00:00
|
|
|
pub missed_fields: Vec<Name>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Diagnostic for MissingFields {
|
2020-08-18 16:39:43 +00:00
|
|
|
fn code(&self) -> DiagnosticCode {
|
|
|
|
DiagnosticCode("missing-structure-fields")
|
2020-08-07 10:18:47 +00:00
|
|
|
}
|
2019-11-27 14:46:02 +00:00
|
|
|
fn message(&self) -> String {
|
2020-03-28 10:20:34 +00:00
|
|
|
let mut buf = String::from("Missing structure fields:\n");
|
2019-11-27 14:46:02 +00:00
|
|
|
for field in &self.missed_fields {
|
2020-05-27 16:15:19 +00:00
|
|
|
format_to!(buf, "- {}\n", field);
|
2019-11-27 14:46:02 +00:00
|
|
|
}
|
2020-03-28 10:20:34 +00:00
|
|
|
buf
|
2019-11-27 14:46:02 +00:00
|
|
|
}
|
2020-07-27 19:30:55 +00:00
|
|
|
|
2020-08-11 14:15:11 +00:00
|
|
|
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
2020-08-08 22:59:26 +00:00
|
|
|
InFile {
|
|
|
|
file_id: self.file,
|
|
|
|
value: self
|
|
|
|
.field_list_parent_path
|
|
|
|
.clone()
|
|
|
|
.map(SyntaxNodePtr::from)
|
|
|
|
.unwrap_or_else(|| self.field_list_parent.clone().into()),
|
|
|
|
}
|
2019-11-27 14:46:02 +00:00
|
|
|
}
|
2020-07-27 19:46:25 +00:00
|
|
|
|
2019-11-27 14:46:02 +00:00
|
|
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:29:06 +00:00
|
|
|
// Diagnostic: missing-pat-fields
|
|
|
|
//
|
|
|
|
// This diagnostic is triggered if pattern lacks some fields that exist in the corresponding structure.
|
|
|
|
//
|
|
|
|
// Example:
|
|
|
|
//
|
|
|
|
// ```rust
|
|
|
|
// struct A { a: u8, b: u8 }
|
|
|
|
//
|
|
|
|
// let a = A { a: 10, b: 20 };
|
|
|
|
//
|
|
|
|
// if let A { a } = a {
|
|
|
|
// // ...
|
|
|
|
// }
|
|
|
|
// ```
|
2020-04-09 03:23:51 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MissingPatFields {
|
|
|
|
pub file: HirFileId,
|
2020-08-08 22:59:26 +00:00
|
|
|
pub field_list_parent: AstPtr<ast::RecordPat>,
|
2020-07-27 20:32:16 +00:00
|
|
|
pub field_list_parent_path: Option<AstPtr<ast::Path>>,
|
2020-04-09 03:23:51 +00:00
|
|
|
pub missed_fields: Vec<Name>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Diagnostic for MissingPatFields {
|
2020-08-18 16:39:43 +00:00
|
|
|
fn code(&self) -> DiagnosticCode {
|
|
|
|
DiagnosticCode("missing-pat-fields")
|
2020-08-07 10:18:47 +00:00
|
|
|
}
|
2020-04-09 03:23:51 +00:00
|
|
|
fn message(&self) -> String {
|
|
|
|
let mut buf = String::from("Missing structure fields:\n");
|
|
|
|
for field in &self.missed_fields {
|
2020-05-27 16:15:19 +00:00
|
|
|
format_to!(buf, "- {}\n", field);
|
2020-04-09 03:23:51 +00:00
|
|
|
}
|
|
|
|
buf
|
|
|
|
}
|
2020-08-11 14:15:11 +00:00
|
|
|
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
|
|
|
InFile {
|
|
|
|
file_id: self.file,
|
|
|
|
value: self
|
|
|
|
.field_list_parent_path
|
|
|
|
.clone()
|
|
|
|
.map(SyntaxNodePtr::from)
|
|
|
|
.unwrap_or_else(|| self.field_list_parent.clone().into()),
|
|
|
|
}
|
2020-04-09 03:23:51 +00:00
|
|
|
}
|
|
|
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:29:06 +00:00
|
|
|
// Diagnostic: missing-match-arm
|
|
|
|
//
|
|
|
|
// This diagnostic is triggered if `match` block is missing one or more match arms.
|
2020-03-24 11:40:58 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MissingMatchArms {
|
|
|
|
pub file: HirFileId,
|
2020-04-06 13:55:25 +00:00
|
|
|
pub match_expr: AstPtr<ast::Expr>,
|
2020-03-24 11:40:58 +00:00
|
|
|
pub arms: AstPtr<ast::MatchArmList>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Diagnostic for MissingMatchArms {
|
2020-08-18 16:39:43 +00:00
|
|
|
fn code(&self) -> DiagnosticCode {
|
|
|
|
DiagnosticCode("missing-match-arm")
|
2020-08-07 10:18:47 +00:00
|
|
|
}
|
2020-03-24 11:40:58 +00:00
|
|
|
fn message(&self) -> String {
|
|
|
|
String::from("Missing match arm")
|
|
|
|
}
|
2020-08-11 14:15:11 +00:00
|
|
|
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
2020-04-10 22:27:00 +00:00
|
|
|
InFile { file_id: self.file, value: self.match_expr.clone().into() }
|
2020-03-24 11:40:58 +00:00
|
|
|
}
|
|
|
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-30 17:23:00 +00:00
|
|
|
// Diagnostic: missing-ok-or-some-in-tail-expr
|
2020-10-17 14:29:06 +00:00
|
|
|
//
|
2020-12-30 17:23:00 +00:00
|
|
|
// This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`,
|
|
|
|
// or if a block that should return `Option` returns a value not wrapped in `Some`.
|
2020-10-17 14:29:06 +00:00
|
|
|
//
|
|
|
|
// Example:
|
|
|
|
//
|
|
|
|
// ```rust
|
|
|
|
// fn foo() -> Result<u8, ()> {
|
|
|
|
// 10
|
|
|
|
// }
|
|
|
|
// ```
|
2019-11-27 14:46:02 +00:00
|
|
|
#[derive(Debug)]
|
2020-12-30 17:23:00 +00:00
|
|
|
pub struct MissingOkOrSomeInTailExpr {
|
2019-11-27 14:46:02 +00:00
|
|
|
pub file: HirFileId,
|
|
|
|
pub expr: AstPtr<ast::Expr>,
|
2020-12-30 17:23:00 +00:00
|
|
|
// `Some` or `Ok` depending on whether the return type is Result or Option
|
|
|
|
pub required: String,
|
2019-11-27 14:46:02 +00:00
|
|
|
}
|
|
|
|
|
2020-12-30 17:23:00 +00:00
|
|
|
impl Diagnostic for MissingOkOrSomeInTailExpr {
|
2020-08-18 16:39:43 +00:00
|
|
|
fn code(&self) -> DiagnosticCode {
|
2020-12-30 17:23:00 +00:00
|
|
|
DiagnosticCode("missing-ok-or-some-in-tail-expr")
|
2020-08-07 10:18:47 +00:00
|
|
|
}
|
2019-11-27 14:46:02 +00:00
|
|
|
fn message(&self) -> String {
|
2020-12-30 17:23:00 +00:00
|
|
|
format!("wrap return expression in {}", self.required)
|
2019-11-27 14:46:02 +00:00
|
|
|
}
|
2020-08-11 14:15:11 +00:00
|
|
|
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
2020-04-10 22:27:00 +00:00
|
|
|
InFile { file_id: self.file, value: self.expr.clone().into() }
|
2019-11-27 14:46:02 +00:00
|
|
|
}
|
|
|
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-08 18:47:20 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct RemoveThisSemicolon {
|
|
|
|
pub file: HirFileId,
|
|
|
|
pub expr: AstPtr<ast::Expr>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Diagnostic for RemoveThisSemicolon {
|
|
|
|
fn code(&self) -> DiagnosticCode {
|
|
|
|
DiagnosticCode("remove-this-semicolon")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn message(&self) -> String {
|
|
|
|
"Remove this semicolon".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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:29:06 +00:00
|
|
|
// Diagnostic: break-outside-of-loop
|
|
|
|
//
|
2020-12-28 13:41:15 +00:00
|
|
|
// This diagnostic is triggered if the `break` keyword is used outside of a loop.
|
2020-05-08 17:48:03 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct BreakOutsideOfLoop {
|
|
|
|
pub file: HirFileId,
|
|
|
|
pub expr: AstPtr<ast::Expr>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Diagnostic for BreakOutsideOfLoop {
|
2020-08-18 16:39:43 +00:00
|
|
|
fn code(&self) -> DiagnosticCode {
|
|
|
|
DiagnosticCode("break-outside-of-loop")
|
2020-08-07 10:18:47 +00:00
|
|
|
}
|
2020-05-08 17:48:03 +00:00
|
|
|
fn message(&self) -> String {
|
|
|
|
"break outside of loop".to_string()
|
|
|
|
}
|
2020-08-11 14:15:11 +00:00
|
|
|
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
2020-05-08 17:48:03 +00:00
|
|
|
InFile { file_id: self.file, value: self.expr.clone().into() }
|
|
|
|
}
|
|
|
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:29:06 +00:00
|
|
|
// Diagnostic: missing-unsafe
|
|
|
|
//
|
2020-12-28 13:41:15 +00:00
|
|
|
// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
|
2020-05-23 21:49:53 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MissingUnsafe {
|
|
|
|
pub file: HirFileId,
|
2020-05-27 12:51:08 +00:00
|
|
|
pub expr: AstPtr<ast::Expr>,
|
2020-05-23 21:49:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Diagnostic for MissingUnsafe {
|
2020-08-18 16:39:43 +00:00
|
|
|
fn code(&self) -> DiagnosticCode {
|
|
|
|
DiagnosticCode("missing-unsafe")
|
2020-08-07 10:18:47 +00:00
|
|
|
}
|
2020-05-23 21:49:53 +00:00
|
|
|
fn message(&self) -> String {
|
2020-05-27 12:51:08 +00:00
|
|
|
format!("This operation is unsafe and requires an unsafe function or block")
|
2020-05-23 21:49:53 +00:00
|
|
|
}
|
2020-08-11 14:15:11 +00:00
|
|
|
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
2020-05-27 12:51:08 +00:00
|
|
|
InFile { file_id: self.file, value: self.expr.clone().into() }
|
2020-05-23 21:49:53 +00:00
|
|
|
}
|
|
|
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:29:06 +00:00
|
|
|
// Diagnostic: mismatched-arg-count
|
|
|
|
//
|
2020-12-28 13:41:15 +00:00
|
|
|
// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments.
|
2020-07-08 17:58:45 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MismatchedArgCount {
|
|
|
|
pub file: HirFileId,
|
|
|
|
pub call_expr: AstPtr<ast::Expr>,
|
|
|
|
pub expected: usize,
|
|
|
|
pub found: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Diagnostic for MismatchedArgCount {
|
2020-08-18 16:39:43 +00:00
|
|
|
fn code(&self) -> DiagnosticCode {
|
|
|
|
DiagnosticCode("mismatched-arg-count")
|
2020-08-07 10:18:47 +00:00
|
|
|
}
|
2020-07-08 17:58:45 +00:00
|
|
|
fn message(&self) -> String {
|
2020-07-09 13:50:53 +00:00
|
|
|
let s = if self.expected == 1 { "" } else { "s" };
|
|
|
|
format!("Expected {} argument{}, found {}", self.expected, s, self.found)
|
2020-07-08 17:58:45 +00:00
|
|
|
}
|
2020-08-11 14:15:11 +00:00
|
|
|
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
2020-07-08 17:58:45 +00:00
|
|
|
InFile { file_id: self.file, value: self.call_expr.clone().into() }
|
|
|
|
}
|
|
|
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|
|
|
self
|
|
|
|
}
|
2020-07-24 15:38:33 +00:00
|
|
|
fn is_experimental(&self) -> bool {
|
|
|
|
true
|
|
|
|
}
|
2020-07-08 17:58:45 +00:00
|
|
|
}
|
|
|
|
|
2020-10-03 09:48:02 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum CaseType {
|
|
|
|
// `some_var`
|
|
|
|
LowerSnakeCase,
|
|
|
|
// `SOME_CONST`
|
|
|
|
UpperSnakeCase,
|
|
|
|
// `SomeStruct`
|
|
|
|
UpperCamelCase,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for CaseType {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
let repr = match self {
|
|
|
|
CaseType::LowerSnakeCase => "snake_case",
|
|
|
|
CaseType::UpperSnakeCase => "UPPER_SNAKE_CASE",
|
2020-10-03 11:47:46 +00:00
|
|
|
CaseType::UpperCamelCase => "CamelCase",
|
2020-10-03 09:48:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
write!(f, "{}", repr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-05 15:09:45 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum IdentType {
|
|
|
|
Argument,
|
|
|
|
Constant,
|
|
|
|
Enum,
|
|
|
|
Field,
|
|
|
|
Function,
|
|
|
|
StaticVariable,
|
|
|
|
Structure,
|
|
|
|
Variable,
|
|
|
|
Variant,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for IdentType {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
let repr = match self {
|
|
|
|
IdentType::Argument => "Argument",
|
|
|
|
IdentType::Constant => "Constant",
|
|
|
|
IdentType::Enum => "Enum",
|
|
|
|
IdentType::Field => "Field",
|
|
|
|
IdentType::Function => "Function",
|
|
|
|
IdentType::StaticVariable => "Static variable",
|
|
|
|
IdentType::Structure => "Structure",
|
|
|
|
IdentType::Variable => "Variable",
|
|
|
|
IdentType::Variant => "Variant",
|
|
|
|
};
|
|
|
|
|
|
|
|
write!(f, "{}", repr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:29:06 +00:00
|
|
|
// Diagnostic: incorrect-ident-case
|
|
|
|
//
|
2020-12-28 13:41:15 +00:00
|
|
|
// This diagnostic is triggered if an item name doesn't follow https://doc.rust-lang.org/1.0.0/style/style/naming/README.html[Rust naming convention].
|
2020-10-03 09:48:02 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct IncorrectCase {
|
|
|
|
pub file: HirFileId,
|
2020-10-08 06:27:38 +00:00
|
|
|
pub ident: AstPtr<ast::Name>,
|
2020-10-03 09:48:02 +00:00
|
|
|
pub expected_case: CaseType,
|
2021-02-05 15:09:45 +00:00
|
|
|
pub ident_type: IdentType,
|
2020-10-03 09:48:02 +00:00
|
|
|
pub ident_text: String,
|
|
|
|
pub suggested_text: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Diagnostic for IncorrectCase {
|
|
|
|
fn code(&self) -> DiagnosticCode {
|
|
|
|
DiagnosticCode("incorrect-ident-case")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn message(&self) -> String {
|
|
|
|
format!(
|
2020-10-04 04:39:35 +00:00
|
|
|
"{} `{}` should have {} name, e.g. `{}`",
|
2020-10-03 10:39:10 +00:00
|
|
|
self.ident_type,
|
2020-10-03 09:48:02 +00:00
|
|
|
self.ident_text,
|
|
|
|
self.expected_case.to_string(),
|
|
|
|
self.suggested_text
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
2020-10-08 06:27:38 +00:00
|
|
|
InFile::new(self.file, self.ident.clone().into())
|
2020-10-03 09:48:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_experimental(&self) -> bool {
|
2020-10-03 15:01:25 +00:00
|
|
|
true
|
2020-10-03 09:48:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-28 13:41:15 +00:00
|
|
|
// Diagnostic: replace-filter-map-next-with-find-map
|
|
|
|
//
|
|
|
|
// This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct ReplaceFilterMapNextWithFindMap {
|
|
|
|
pub file: HirFileId,
|
2020-12-30 15:46:05 +00:00
|
|
|
/// This expression is the whole method chain up to and including `.filter_map(..).next()`.
|
2020-12-28 13:41:15 +00:00
|
|
|
pub next_expr: AstPtr<ast::Expr>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Diagnostic for ReplaceFilterMapNextWithFindMap {
|
|
|
|
fn code(&self) -> DiagnosticCode {
|
|
|
|
DiagnosticCode("replace-filter-map-next-with-find-map")
|
|
|
|
}
|
|
|
|
fn message(&self) -> String {
|
|
|
|
"replace filter_map(..).next() with find_map(..)".to_string()
|
|
|
|
}
|
|
|
|
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
2020-12-30 15:46:05 +00:00
|
|
|
InFile { file_id: self.file, value: self.next_expr.clone().into() }
|
2020-12-28 13:41:15 +00:00
|
|
|
}
|
|
|
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-14 10:05:50 +00:00
|
|
|
#[cfg(test)]
|
2020-07-14 14:43:39 +00:00
|
|
|
mod tests {
|
2020-08-13 14:25:38 +00:00
|
|
|
use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt};
|
2020-07-14 14:43:39 +00:00
|
|
|
use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId};
|
internal: move diagnostics to hir
The idea here is to eventually get rid of `dyn Diagnostic` and
`DiagnosticSink` infrastructure altogether, and just have a `enum
hir::Diagnostic` instead.
The problem with `dyn Diagnostic` is that it is defined in the lowest
level of the stack (hir_expand), but is used by the highest level (ide).
As a first step, we free hir_expand and hir_def from `dyn Diagnostic`
and kick the can up to `hir_ty`, as an intermediate state. The plan is
then to move DiagnosticSink similarly to the hir crate, and, as final
third step, remove its usage from the ide.
One currently unsolved problem is testing. You can notice that the test
which checks precise diagnostic ranges, unresolved_import_in_use_tree,
was moved to the ide layer. Logically, only IDE should have the infra to
render a specific range.
At the same time, the range is determined with the data produced in
hir_def and hir crates, so this layering is rather unfortunate. Working
on hir_def shouldn't require compiling `ide` for testing.
2021-05-23 20:31:59 +00:00
|
|
|
use hir_expand::db::AstDatabase;
|
2020-07-14 10:05:50 +00:00
|
|
|
use rustc_hash::FxHashMap;
|
2020-08-12 16:26:51 +00:00
|
|
|
use syntax::{TextRange, TextSize};
|
2020-07-14 10:05:50 +00:00
|
|
|
|
2020-10-03 09:48:02 +00:00
|
|
|
use crate::{
|
|
|
|
diagnostics::{validate_body, validate_module_item},
|
internal: move diagnostics to hir
The idea here is to eventually get rid of `dyn Diagnostic` and
`DiagnosticSink` infrastructure altogether, and just have a `enum
hir::Diagnostic` instead.
The problem with `dyn Diagnostic` is that it is defined in the lowest
level of the stack (hir_expand), but is used by the highest level (ide).
As a first step, we free hir_expand and hir_def from `dyn Diagnostic`
and kick the can up to `hir_ty`, as an intermediate state. The plan is
then to move DiagnosticSink similarly to the hir crate, and, as final
third step, remove its usage from the ide.
One currently unsolved problem is testing. You can notice that the test
which checks precise diagnostic ranges, unresolved_import_in_use_tree,
was moved to the ide layer. Logically, only IDE should have the infra to
render a specific range.
At the same time, the range is determined with the data produced in
hir_def and hir crates, so this layering is rather unfortunate. Working
on hir_def shouldn't require compiling `ide` for testing.
2021-05-23 20:31:59 +00:00
|
|
|
diagnostics_sink::{Diagnostic, DiagnosticSinkBuilder},
|
2020-10-03 09:48:02 +00:00
|
|
|
test_db::TestDB,
|
|
|
|
};
|
2020-07-14 14:43:39 +00:00
|
|
|
|
|
|
|
impl TestDB {
|
|
|
|
fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) {
|
|
|
|
let crate_graph = self.crate_graph();
|
|
|
|
for krate in crate_graph.iter() {
|
|
|
|
let crate_def_map = self.crate_def_map(krate);
|
|
|
|
|
|
|
|
let mut fns = Vec::new();
|
2021-01-20 14:41:18 +00:00
|
|
|
for (module_id, _) in crate_def_map.modules() {
|
2020-07-14 14:43:39 +00:00
|
|
|
for decl in crate_def_map[module_id].scope.declarations() {
|
2020-10-03 09:48:02 +00:00
|
|
|
let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
|
2020-12-17 00:19:56 +00:00
|
|
|
validate_module_item(self, krate, decl, &mut sink);
|
2020-10-03 09:48:02 +00:00
|
|
|
|
2020-07-14 14:43:39 +00:00
|
|
|
if let ModuleDefId::FunctionId(f) = decl {
|
|
|
|
fns.push(f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for impl_id in crate_def_map[module_id].scope.impls() {
|
|
|
|
let impl_data = self.impl_data(impl_id);
|
|
|
|
for item in impl_data.items.iter() {
|
|
|
|
if let AssocItemId::FunctionId(f) = item {
|
2020-10-04 04:39:35 +00:00
|
|
|
let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
|
2020-12-17 00:19:56 +00:00
|
|
|
validate_module_item(
|
|
|
|
self,
|
|
|
|
krate,
|
|
|
|
ModuleDefId::FunctionId(*f),
|
|
|
|
&mut sink,
|
|
|
|
);
|
2020-07-14 14:43:39 +00:00
|
|
|
fns.push(*f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for f in fns {
|
2020-07-24 14:30:12 +00:00
|
|
|
let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
|
2020-07-14 14:43:39 +00:00
|
|
|
validate_body(self, f.into(), &mut sink);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn check_diagnostics(ra_fixture: &str) {
|
|
|
|
let db = TestDB::with_files(ra_fixture);
|
|
|
|
let annotations = db.extract_annotations();
|
|
|
|
|
|
|
|
let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
|
|
|
|
db.diagnostics(|d| {
|
2020-08-11 14:15:11 +00:00
|
|
|
let src = d.display_source();
|
|
|
|
let root = db.parse_or_expand(src.file_id).unwrap();
|
2020-07-27 20:56:57 +00:00
|
|
|
// FIXME: macros...
|
2020-08-11 14:15:11 +00:00
|
|
|
let file_id = src.file_id.original_file(&db);
|
|
|
|
let range = src.value.to_node(&root).text_range();
|
2021-02-05 15:57:26 +00:00
|
|
|
let message = d.message();
|
2020-07-14 14:43:39 +00:00
|
|
|
actual.entry(file_id).or_default().push((range, message));
|
|
|
|
});
|
|
|
|
|
|
|
|
for (file_id, diags) in actual.iter_mut() {
|
|
|
|
diags.sort_by_key(|it| it.0.start());
|
|
|
|
let text = db.file_text(*file_id);
|
|
|
|
// For multiline spans, place them on line start
|
|
|
|
for (range, content) in diags {
|
|
|
|
if text[*range].contains('\n') {
|
|
|
|
*range = TextRange::new(range.start(), range.start() + TextSize::from(1));
|
|
|
|
*content = format!("... {}", content);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(annotations, actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn no_such_field_diagnostics() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
struct S { foo: i32, bar: () }
|
|
|
|
impl S {
|
|
|
|
fn new() -> S {
|
|
|
|
S {
|
2020-07-27 19:30:55 +00:00
|
|
|
//^ Missing structure fields:
|
|
|
|
//| - bar
|
2020-07-14 14:43:39 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
2020-07-14 10:05:50 +00:00
|
|
|
|
2020-07-14 14:43:39 +00:00
|
|
|
#[test]
|
|
|
|
fn no_such_field_with_type_macro() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
macro_rules! Type { () => { u32 }; }
|
|
|
|
struct Foo { bar: Type![] }
|
2020-07-14 10:05:50 +00:00
|
|
|
|
2020-07-14 14:43:39 +00:00
|
|
|
impl Foo {
|
|
|
|
fn new() -> Self {
|
|
|
|
Foo { bar: 0 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn missing_record_pat_field_diagnostic() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
struct S { foo: i32, bar: () }
|
|
|
|
fn baz(s: S) {
|
|
|
|
let S { foo: _ } = s;
|
2020-07-27 20:32:16 +00:00
|
|
|
//^ Missing structure fields:
|
|
|
|
//| - bar
|
2020-07-14 14:43:39 +00:00
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
2020-07-14 10:05:50 +00:00
|
|
|
|
2020-07-14 14:43:39 +00:00
|
|
|
#[test]
|
|
|
|
fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
|
|
|
|
check_diagnostics(
|
|
|
|
r"
|
|
|
|
struct S { foo: i32, bar: () }
|
|
|
|
fn baz(s: S) -> i32 {
|
|
|
|
match s {
|
|
|
|
S { foo, .. } => foo,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-02-20 10:43:52 +00:00
|
|
|
#[test]
|
|
|
|
fn missing_record_pat_field_box() {
|
|
|
|
check_diagnostics(
|
|
|
|
r"
|
|
|
|
struct S { s: Box<u32> }
|
|
|
|
fn x(a: S) {
|
|
|
|
let S { box s } = a;
|
|
|
|
}
|
|
|
|
",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn missing_record_pat_field_ref() {
|
|
|
|
check_diagnostics(
|
|
|
|
r"
|
|
|
|
struct S { s: u32 }
|
|
|
|
fn x(a: S) {
|
|
|
|
let S { ref s } = a;
|
|
|
|
}
|
|
|
|
",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-03-10 15:33:18 +00:00
|
|
|
#[test]
|
|
|
|
fn import_extern_crate_clash_with_inner_item() {
|
|
|
|
// This is more of a resolver test, but doesn't really work with the hir_def testsuite.
|
|
|
|
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
//- /lib.rs crate:lib deps:jwt
|
|
|
|
mod permissions;
|
|
|
|
|
|
|
|
use permissions::jwt;
|
|
|
|
|
|
|
|
fn f() {
|
|
|
|
fn inner() {}
|
|
|
|
jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic
|
|
|
|
}
|
|
|
|
|
|
|
|
//- /permissions.rs
|
|
|
|
pub mod jwt {
|
|
|
|
pub struct Claims {}
|
|
|
|
}
|
|
|
|
|
|
|
|
//- /jwt/lib.rs crate:jwt
|
|
|
|
pub struct Claims {
|
|
|
|
field: u8,
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-07-14 14:43:39 +00:00
|
|
|
#[test]
|
|
|
|
fn break_outside_of_loop() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
fn foo() { break; }
|
|
|
|
//^^^^^ break outside of loop
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
2020-12-12 11:50:11 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn missing_semicolon() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
fn test() -> i32 { 123; }
|
|
|
|
//^^^ Remove this semicolon
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
2020-12-28 13:41:15 +00:00
|
|
|
|
2021-01-01 21:40:11 +00:00
|
|
|
// 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#"
|
|
|
|
//- /main.rs crate:main deps:core
|
|
|
|
use core::iter::Iterator;
|
|
|
|
use core::option::Option::{self, Some, None};
|
|
|
|
"#;
|
|
|
|
let suffix = r#"
|
|
|
|
//- /core/lib.rs crate:core
|
|
|
|
pub mod option {
|
|
|
|
pub enum Option<T> { Some(T), None }
|
|
|
|
}
|
|
|
|
pub mod iter {
|
|
|
|
pub trait Iterator {
|
|
|
|
type Item;
|
|
|
|
fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
|
|
|
|
fn next(&mut self) -> Option<Self::Item>;
|
|
|
|
}
|
|
|
|
pub struct FilterMap {}
|
|
|
|
impl Iterator for FilterMap {
|
|
|
|
type Item = i32;
|
|
|
|
fn next(&mut self) -> i32 { 7 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#;
|
|
|
|
format!("{}{}{}", prefix, body, suffix)
|
|
|
|
}
|
|
|
|
|
2020-12-28 13:41:15 +00:00
|
|
|
#[test]
|
2021-01-01 21:40:11 +00:00
|
|
|
fn replace_filter_map_next_with_find_map2() {
|
|
|
|
check_diagnostics(&add_filter_map_with_find_next_boilerplate(
|
2020-12-28 13:41:15 +00:00
|
|
|
r#"
|
|
|
|
fn foo() {
|
2020-12-30 15:46:05 +00:00
|
|
|
let m = [1, 2, 3].iter().filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
|
|
|
|
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ replace filter_map(..).next() with find_map(..)
|
|
|
|
}
|
2021-01-01 21:40:11 +00:00
|
|
|
"#,
|
|
|
|
));
|
2020-12-30 15:46:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-01-01 21:17:54 +00:00
|
|
|
fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() {
|
2021-01-01 21:40:11 +00:00
|
|
|
check_diagnostics(&add_filter_map_with_find_next_boilerplate(
|
2020-12-30 15:46:05 +00:00
|
|
|
r#"
|
|
|
|
fn foo() {
|
|
|
|
let m = [1, 2, 3]
|
|
|
|
.iter()
|
|
|
|
.filter_map(|x| if *x == 2 { Some (4) } else { None })
|
|
|
|
.len();
|
|
|
|
}
|
|
|
|
"#,
|
2021-01-01 21:40:11 +00:00
|
|
|
));
|
2020-12-30 15:46:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-01-01 21:17:54 +00:00
|
|
|
fn replace_filter_map_next_with_find_map_no_diagnostic_with_intervening_methods() {
|
2021-01-01 21:40:11 +00:00
|
|
|
check_diagnostics(&add_filter_map_with_find_next_boilerplate(
|
2020-12-30 15:46:05 +00:00
|
|
|
r#"
|
|
|
|
fn foo() {
|
|
|
|
let m = [1, 2, 3]
|
|
|
|
.iter()
|
|
|
|
.filter_map(|x| if *x == 2 { Some (4) } else { None })
|
|
|
|
.map(|x| x + 2)
|
|
|
|
.len();
|
|
|
|
}
|
|
|
|
"#,
|
2021-01-01 21:40:11 +00:00
|
|
|
));
|
2020-12-30 15:46:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-01-01 21:17:54 +00:00
|
|
|
fn replace_filter_map_next_with_find_map_no_diagnostic_if_not_in_chain() {
|
2021-01-01 21:40:11 +00:00
|
|
|
check_diagnostics(&add_filter_map_with_find_next_boilerplate(
|
2020-12-30 15:46:05 +00:00
|
|
|
r#"
|
|
|
|
fn foo() {
|
|
|
|
let m = [1, 2, 3]
|
|
|
|
.iter()
|
|
|
|
.filter_map(|x| if *x == 2 { Some (4) } else { None });
|
|
|
|
let n = m.next();
|
2020-12-28 13:41:15 +00:00
|
|
|
}
|
|
|
|
"#,
|
2021-01-01 21:40:11 +00:00
|
|
|
));
|
2020-12-28 13:41:15 +00:00
|
|
|
}
|
2020-07-14 10:05:50 +00:00
|
|
|
}
|