mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-10 23:24:29 +00:00
Add diagnostic for _
expressions (typed holes)
This commit is contained in:
parent
150082b0e3
commit
8bc826dd53
9 changed files with 368 additions and 61 deletions
|
@ -21,6 +21,7 @@ use crate::{
|
|||
pub const WORKSPACE: SourceRootId = SourceRootId(0);
|
||||
|
||||
pub trait WithFixture: Default + SourceDatabaseExt + 'static {
|
||||
#[track_caller]
|
||||
fn with_single_file(ra_fixture: &str) -> (Self, FileId) {
|
||||
let fixture = ChangeFixture::parse(ra_fixture);
|
||||
let mut db = Self::default();
|
||||
|
@ -29,6 +30,7 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
|
|||
(db, fixture.files[0])
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn with_many_files(ra_fixture: &str) -> (Self, Vec<FileId>) {
|
||||
let fixture = ChangeFixture::parse(ra_fixture);
|
||||
let mut db = Self::default();
|
||||
|
@ -37,6 +39,7 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
|
|||
(db, fixture.files)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn with_files(ra_fixture: &str) -> Self {
|
||||
let fixture = ChangeFixture::parse(ra_fixture);
|
||||
let mut db = Self::default();
|
||||
|
@ -45,6 +48,7 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
|
|||
db
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn with_files_extra_proc_macros(
|
||||
ra_fixture: &str,
|
||||
proc_macros: Vec<(String, ProcMacro)>,
|
||||
|
@ -56,18 +60,21 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
|
|||
db
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
|
||||
let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
|
||||
let offset = range_or_offset.expect_offset();
|
||||
(db, FilePosition { file_id, offset })
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn with_range(ra_fixture: &str) -> (Self, FileRange) {
|
||||
let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
|
||||
let range = range_or_offset.expect_range();
|
||||
(db, FileRange { file_id, range })
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset) {
|
||||
let fixture = ChangeFixture::parse(ra_fixture);
|
||||
let mut db = Self::default();
|
||||
|
|
|
@ -1046,6 +1046,12 @@ impl HasResolver for GenericDefId {
|
|||
}
|
||||
}
|
||||
|
||||
impl HasResolver for EnumVariantId {
|
||||
fn resolver(self, db: &dyn DefDatabase) -> Resolver {
|
||||
self.parent.resolver(db)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasResolver for VariantId {
|
||||
fn resolver(self, db: &dyn DefDatabase) -> Resolver {
|
||||
match self {
|
||||
|
|
|
@ -194,6 +194,19 @@ impl TyBuilder<()> {
|
|||
params.placeholder_subst(db)
|
||||
}
|
||||
|
||||
pub fn unknown_subst(db: &dyn HirDatabase, def: impl Into<GenericDefId>) -> Substitution {
|
||||
let params = generics(db.upcast(), def.into());
|
||||
Substitution::from_iter(
|
||||
Interner,
|
||||
params.iter_id().map(|id| match id {
|
||||
either::Either::Left(_) => TyKind::Error.intern(Interner).cast(Interner),
|
||||
either::Either::Right(id) => {
|
||||
unknown_const_as_generic(db.const_param_ty(id)).cast(Interner)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn subst_for_def(
|
||||
db: &dyn HirDatabase,
|
||||
def: impl Into<GenericDefId>,
|
||||
|
|
|
@ -215,6 +215,10 @@ pub enum InferenceDiagnostic {
|
|||
call_expr: ExprId,
|
||||
found: Ty,
|
||||
},
|
||||
TypedHole {
|
||||
expr: ExprId,
|
||||
expected: Ty,
|
||||
},
|
||||
}
|
||||
|
||||
/// A mismatch between an expected and an inferred type.
|
||||
|
@ -600,29 +604,30 @@ impl<'a> InferenceContext<'a> {
|
|||
mismatch.actual = table.resolve_completely(mismatch.actual.clone());
|
||||
}
|
||||
result.diagnostics.retain_mut(|diagnostic| {
|
||||
if let InferenceDiagnostic::ExpectedFunction { found: ty, .. }
|
||||
| InferenceDiagnostic::UnresolvedField { receiver: ty, .. }
|
||||
| InferenceDiagnostic::UnresolvedMethodCall { receiver: ty, .. } = diagnostic
|
||||
{
|
||||
*ty = table.resolve_completely(ty.clone());
|
||||
// FIXME: Remove this when we are on par with rustc in terms of inference
|
||||
if ty.contains_unknown() {
|
||||
return false;
|
||||
}
|
||||
use InferenceDiagnostic::*;
|
||||
match diagnostic {
|
||||
ExpectedFunction { found: ty, .. }
|
||||
| UnresolvedField { receiver: ty, .. }
|
||||
| UnresolvedMethodCall { receiver: ty, .. } => {
|
||||
*ty = table.resolve_completely(ty.clone());
|
||||
// FIXME: Remove this when we are on par with rustc in terms of inference
|
||||
if ty.contains_unknown() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let InferenceDiagnostic::UnresolvedMethodCall { field_with_same_name, .. } =
|
||||
diagnostic
|
||||
{
|
||||
let clear = if let Some(ty) = field_with_same_name {
|
||||
*ty = table.resolve_completely(ty.clone());
|
||||
ty.contains_unknown()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if clear {
|
||||
*field_with_same_name = None;
|
||||
if let UnresolvedMethodCall { field_with_same_name, .. } = diagnostic {
|
||||
if let Some(ty) = field_with_same_name {
|
||||
*ty = table.resolve_completely(ty.clone());
|
||||
if ty.contains_unknown() {
|
||||
*field_with_same_name = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TypedHole { expected: ty, .. } => {
|
||||
*ty = table.resolve_completely(ty.clone());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
true
|
||||
});
|
||||
|
|
|
@ -874,9 +874,15 @@ impl<'a> InferenceContext<'a> {
|
|||
},
|
||||
Expr::Underscore => {
|
||||
// Underscore expressions may only appear in assignee expressions,
|
||||
// which are handled by `infer_assignee_expr()`, so any underscore
|
||||
// expression reaching this branch is an error.
|
||||
self.err_ty()
|
||||
// which are handled by `infer_assignee_expr()`.
|
||||
// Any other underscore expression is an error, we render a specialized diagnostic
|
||||
// to let the user know what type is expected though.
|
||||
let expected = expected.to_option(&mut self.table).unwrap_or_else(|| self.err_ty());
|
||||
self.push_diagnostic(InferenceDiagnostic::TypedHole {
|
||||
expr: tgt_expr,
|
||||
expected: expected.clone(),
|
||||
});
|
||||
expected
|
||||
}
|
||||
};
|
||||
// use a new type variable if we got unknown here
|
||||
|
@ -1001,12 +1007,13 @@ impl<'a> InferenceContext<'a> {
|
|||
}
|
||||
&Array::Repeat { initializer, repeat } => {
|
||||
self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty.clone()));
|
||||
self.infer_expr(
|
||||
repeat,
|
||||
&Expectation::HasType(
|
||||
TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner),
|
||||
),
|
||||
);
|
||||
let usize = TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner);
|
||||
match self.body[repeat] {
|
||||
Expr::Underscore => {
|
||||
self.write_expr_ty(repeat, usize);
|
||||
}
|
||||
_ => _ = self.infer_expr(repeat, &Expectation::HasType(usize)),
|
||||
}
|
||||
|
||||
(
|
||||
elem_ty,
|
||||
|
|
|
@ -52,6 +52,7 @@ diagnostics![
|
|||
PrivateAssocItem,
|
||||
PrivateField,
|
||||
ReplaceFilterMapNextWithFindMap,
|
||||
TypedHole,
|
||||
TypeMismatch,
|
||||
UndeclaredLabel,
|
||||
UnimplementedBuiltinMacro,
|
||||
|
@ -73,6 +74,12 @@ pub struct BreakOutsideOfLoop {
|
|||
pub bad_value_break: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TypedHole {
|
||||
pub expr: InFile<AstPtr<ast::Expr>>,
|
||||
pub expected: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnresolvedModule {
|
||||
pub decl: InFile<AstPtr<ast::Module>>,
|
||||
|
|
|
@ -69,7 +69,8 @@ use hir_ty::{
|
|||
traits::FnTrait,
|
||||
AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId,
|
||||
GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution,
|
||||
TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, WhereClause,
|
||||
TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, ValueTyDefId,
|
||||
WhereClause,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use nameres::diagnostics::DefDiagnosticKind;
|
||||
|
@ -91,10 +92,10 @@ pub use crate::{
|
|||
IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError, MacroExpansionParseError,
|
||||
MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, MissingUnsafe,
|
||||
MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem, PrivateField,
|
||||
ReplaceFilterMapNextWithFindMap, TypeMismatch, UndeclaredLabel, UnimplementedBuiltinMacro,
|
||||
UnreachableLabel, UnresolvedExternCrate, UnresolvedField, UnresolvedImport,
|
||||
UnresolvedMacroCall, UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro,
|
||||
UnusedMut,
|
||||
ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel,
|
||||
UnimplementedBuiltinMacro, UnreachableLabel, UnresolvedExternCrate, UnresolvedField,
|
||||
UnresolvedImport, UnresolvedMacroCall, UnresolvedMethodCall, UnresolvedModule,
|
||||
UnresolvedProcMacro, UnusedMut,
|
||||
},
|
||||
has_source::HasSource,
|
||||
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
|
||||
|
@ -1005,6 +1006,10 @@ impl Struct {
|
|||
Type::from_def(db, self.id)
|
||||
}
|
||||
|
||||
pub fn constructor_ty(self, db: &dyn HirDatabase) -> Type {
|
||||
Type::from_value_def(db, self.id)
|
||||
}
|
||||
|
||||
pub fn repr(self, db: &dyn HirDatabase) -> Option<ReprOptions> {
|
||||
db.struct_data(self.id).repr
|
||||
}
|
||||
|
@ -1042,6 +1047,10 @@ impl Union {
|
|||
Type::from_def(db, self.id)
|
||||
}
|
||||
|
||||
pub fn constructor_ty(self, db: &dyn HirDatabase) -> Type {
|
||||
Type::from_value_def(db, self.id)
|
||||
}
|
||||
|
||||
pub fn fields(self, db: &dyn HirDatabase) -> Vec<Field> {
|
||||
db.union_data(self.id)
|
||||
.variant_data
|
||||
|
@ -1173,6 +1182,10 @@ impl Variant {
|
|||
self.parent
|
||||
}
|
||||
|
||||
pub fn constructor_ty(self, db: &dyn HirDatabase) -> Type {
|
||||
Type::from_value_def(db, EnumVariantId { parent: self.parent.id, local_id: self.id })
|
||||
}
|
||||
|
||||
pub fn name(self, db: &dyn HirDatabase) -> Name {
|
||||
db.enum_data(self.parent.id).variants[self.id].name.clone()
|
||||
}
|
||||
|
@ -1574,6 +1587,16 @@ impl DefWithBody {
|
|||
let expr = expr_syntax(expr);
|
||||
acc.push(BreakOutsideOfLoop { expr, is_break, bad_value_break }.into())
|
||||
}
|
||||
hir_ty::InferenceDiagnostic::TypedHole { expr, expected } => {
|
||||
let expr = expr_syntax(*expr);
|
||||
acc.push(
|
||||
TypedHole {
|
||||
expr,
|
||||
expected: Type::new(db, DefWithBodyId::from(self), expected.clone()),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (pat_or_expr, mismatch) in infer.type_mismatches() {
|
||||
|
@ -1806,6 +1829,10 @@ impl Function {
|
|||
db.function_data(self.id).name.clone()
|
||||
}
|
||||
|
||||
pub fn ty(self, db: &dyn HirDatabase) -> Type {
|
||||
Type::from_value_def(db, self.id)
|
||||
}
|
||||
|
||||
/// Get this function's return type
|
||||
pub fn ret_type(self, db: &dyn HirDatabase) -> Type {
|
||||
let resolver = self.id.resolver(db.upcast());
|
||||
|
@ -2085,11 +2112,7 @@ impl Const {
|
|||
}
|
||||
|
||||
pub fn ty(self, db: &dyn HirDatabase) -> Type {
|
||||
let data = db.const_data(self.id);
|
||||
let resolver = self.id.resolver(db.upcast());
|
||||
let ctx = hir_ty::TyLoweringContext::new(db, &resolver);
|
||||
let ty = ctx.lower_ty(&data.type_ref);
|
||||
Type::new_with_resolver_inner(db, &resolver, ty)
|
||||
Type::from_value_def(db, self.id)
|
||||
}
|
||||
|
||||
pub fn render_eval(self, db: &dyn HirDatabase) -> Result<String, ConstEvalError> {
|
||||
|
@ -2136,11 +2159,7 @@ impl Static {
|
|||
}
|
||||
|
||||
pub fn ty(self, db: &dyn HirDatabase) -> Type {
|
||||
let data = db.static_data(self.id);
|
||||
let resolver = self.id.resolver(db.upcast());
|
||||
let ctx = hir_ty::TyLoweringContext::new(db, &resolver);
|
||||
let ty = ctx.lower_ty(&data.type_ref);
|
||||
Type::new_with_resolver_inner(db, &resolver, ty)
|
||||
Type::from_value_def(db, self.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3409,24 +3428,33 @@ impl Type {
|
|||
Type { env: environment, ty }
|
||||
}
|
||||
|
||||
fn from_def(db: &dyn HirDatabase, def: impl HasResolver + Into<TyDefId>) -> Type {
|
||||
let ty_def = def.into();
|
||||
let parent_subst = match ty_def {
|
||||
TyDefId::TypeAliasId(id) => match id.lookup(db.upcast()).container {
|
||||
ItemContainerId::TraitId(id) => {
|
||||
let subst = TyBuilder::subst_for_def(db, id, None).fill_with_unknown().build();
|
||||
Some(subst)
|
||||
}
|
||||
ItemContainerId::ImplId(id) => {
|
||||
let subst = TyBuilder::subst_for_def(db, id, None).fill_with_unknown().build();
|
||||
Some(subst)
|
||||
}
|
||||
_ => None,
|
||||
fn from_def(db: &dyn HirDatabase, def: impl Into<TyDefId> + HasResolver) -> Type {
|
||||
let ty = db.ty(def.into());
|
||||
let substs = TyBuilder::unknown_subst(
|
||||
db,
|
||||
match def.into() {
|
||||
TyDefId::AdtId(it) => GenericDefId::AdtId(it),
|
||||
TyDefId::TypeAliasId(it) => GenericDefId::TypeAliasId(it),
|
||||
TyDefId::BuiltinType(_) => return Type::new(db, def, ty.skip_binders().clone()),
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
let ty = TyBuilder::def_ty(db, ty_def, parent_subst).fill_with_unknown().build();
|
||||
Type::new(db, def, ty)
|
||||
);
|
||||
Type::new(db, def, ty.substitute(Interner, &substs))
|
||||
}
|
||||
|
||||
fn from_value_def(db: &dyn HirDatabase, def: impl Into<ValueTyDefId> + HasResolver) -> Type {
|
||||
let ty = db.value_ty(def.into());
|
||||
let substs = TyBuilder::unknown_subst(
|
||||
db,
|
||||
match def.into() {
|
||||
ValueTyDefId::ConstId(it) => GenericDefId::ConstId(it),
|
||||
ValueTyDefId::FunctionId(it) => GenericDefId::FunctionId(it),
|
||||
ValueTyDefId::StructId(it) => GenericDefId::AdtId(AdtId::StructId(it)),
|
||||
ValueTyDefId::UnionId(it) => GenericDefId::AdtId(AdtId::UnionId(it)),
|
||||
ValueTyDefId::EnumVariantId(it) => GenericDefId::EnumVariantId(it),
|
||||
ValueTyDefId::StaticId(_) => return Type::new(db, def, ty.skip_binders().clone()),
|
||||
},
|
||||
);
|
||||
Type::new(db, def, ty.substitute(Interner, &substs))
|
||||
}
|
||||
|
||||
pub fn new_slice(ty: Type) -> Type {
|
||||
|
|
232
crates/ide-diagnostics/src/handlers/typed_hole.rs
Normal file
232
crates/ide-diagnostics/src/handlers/typed_hole.rs
Normal file
|
@ -0,0 +1,232 @@
|
|||
use hir::{db::ExpandDatabase, ClosureStyle, HirDisplay, StructKind};
|
||||
use ide_db::{
|
||||
assists::{Assist, AssistId, AssistKind, GroupLabel},
|
||||
label::Label,
|
||||
source_change::SourceChange,
|
||||
};
|
||||
use syntax::AstNode;
|
||||
use text_edit::TextEdit;
|
||||
|
||||
use crate::{Diagnostic, DiagnosticsContext};
|
||||
|
||||
// Diagnostic: typed-hole
|
||||
//
|
||||
// This diagnostic is triggered when an underscore expression is used in an invalid position.
|
||||
pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Diagnostic {
|
||||
let display_range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into()));
|
||||
let (message, fixes) = if d.expected.is_unknown() {
|
||||
("`_` expressions may only appear on the left-hand side of an assignment".to_owned(), None)
|
||||
} else {
|
||||
(
|
||||
format!(
|
||||
"invalid `_` expression, expected type `{}`",
|
||||
d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId),
|
||||
),
|
||||
fixes(ctx, d),
|
||||
)
|
||||
};
|
||||
|
||||
Diagnostic::new("typed-hole", message, display_range.range).with_fixes(fixes)
|
||||
}
|
||||
|
||||
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>> {
|
||||
let db = ctx.sema.db;
|
||||
let root = db.parse_or_expand(d.expr.file_id);
|
||||
let original_range =
|
||||
d.expr.as_ref().map(|it| it.to_node(&root)).syntax().original_file_range_opt(db)?;
|
||||
let scope = ctx.sema.scope(d.expr.value.to_node(&root).syntax())?;
|
||||
let mut assists = vec![];
|
||||
scope.process_all_names(&mut |name, def| {
|
||||
let ty = match def {
|
||||
hir::ScopeDef::ModuleDef(it) => match it {
|
||||
hir::ModuleDef::Function(it) => it.ty(db),
|
||||
hir::ModuleDef::Adt(hir::Adt::Struct(it)) if it.kind(db) != StructKind::Record => {
|
||||
it.constructor_ty(db)
|
||||
}
|
||||
hir::ModuleDef::Variant(it) if it.kind(db) != StructKind::Record => {
|
||||
it.constructor_ty(db)
|
||||
}
|
||||
hir::ModuleDef::Const(it) => it.ty(db),
|
||||
hir::ModuleDef::Static(it) => it.ty(db),
|
||||
_ => return,
|
||||
},
|
||||
hir::ScopeDef::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db),
|
||||
hir::ScopeDef::Local(it) => it.ty(db),
|
||||
_ => return,
|
||||
};
|
||||
// FIXME: should also check coercions if it is at a coercion site
|
||||
if !ty.contains_unknown() && ty.could_unify_with(db, &d.expected) {
|
||||
assists.push(Assist {
|
||||
id: AssistId("typed-hole", AssistKind::QuickFix),
|
||||
label: Label::new(format!("Replace `_` with `{}`", name.display(db))),
|
||||
group: Some(GroupLabel("Replace `_` with a matching entity in scope".to_owned())),
|
||||
target: original_range.range,
|
||||
source_change: Some(SourceChange::from_text_edit(
|
||||
original_range.file_id,
|
||||
TextEdit::replace(original_range.range, name.display(db).to_string()),
|
||||
)),
|
||||
trigger_signature_help: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
if assists.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(assists)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_diagnostics, check_fixes};
|
||||
|
||||
#[test]
|
||||
fn unknown() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn main() {
|
||||
_;
|
||||
//^ error: `_` expressions may only appear on the left-hand side of an assignment
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concrete_expectation() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn main() {
|
||||
if _ {}
|
||||
//^ error: invalid `_` expression, expected type `bool`
|
||||
let _: fn() -> i32 = _;
|
||||
//^ error: invalid `_` expression, expected type `fn() -> i32`
|
||||
let _: fn() -> () = _; // FIXME: This should trigger an assist because `main` matches via *coercion*
|
||||
//^ error: invalid `_` expression, expected type `fn()`
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integer_ty_var() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn main() {
|
||||
let mut x = 3;
|
||||
x = _;
|
||||
//^ 💡 error: invalid `_` expression, expected type `i32`
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ty_var_resolved() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn main() {
|
||||
let mut x = t();
|
||||
x = _;
|
||||
//^ 💡 error: invalid `_` expression, expected type `&str`
|
||||
x = "";
|
||||
}
|
||||
fn t<T>() -> T { loop {} }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_positions() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
fn main() {
|
||||
let x = [(); _];
|
||||
let y: [(); 10] = [(); _];
|
||||
_ = 0;
|
||||
(_,) = (1,);
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_quick_fix() {
|
||||
check_fixes(
|
||||
r#"
|
||||
enum Foo {
|
||||
Bar
|
||||
}
|
||||
use Foo::Bar;
|
||||
const C: Foo = Foo::Bar;
|
||||
fn main<const CP: Foo>(param: Foo) {
|
||||
let local = Foo::Bar;
|
||||
let _: Foo = _$0;
|
||||
//^ error: invalid `_` expression, expected type `fn()`
|
||||
}
|
||||
"#,
|
||||
vec![
|
||||
r#"
|
||||
enum Foo {
|
||||
Bar
|
||||
}
|
||||
use Foo::Bar;
|
||||
const C: Foo = Foo::Bar;
|
||||
fn main<const CP: Foo>(param: Foo) {
|
||||
let local = Foo::Bar;
|
||||
let _: Foo = local;
|
||||
//^ error: invalid `_` expression, expected type `fn()`
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Foo {
|
||||
Bar
|
||||
}
|
||||
use Foo::Bar;
|
||||
const C: Foo = Foo::Bar;
|
||||
fn main<const CP: Foo>(param: Foo) {
|
||||
let local = Foo::Bar;
|
||||
let _: Foo = param;
|
||||
//^ error: invalid `_` expression, expected type `fn()`
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Foo {
|
||||
Bar
|
||||
}
|
||||
use Foo::Bar;
|
||||
const C: Foo = Foo::Bar;
|
||||
fn main<const CP: Foo>(param: Foo) {
|
||||
let local = Foo::Bar;
|
||||
let _: Foo = CP;
|
||||
//^ error: invalid `_` expression, expected type `fn()`
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Foo {
|
||||
Bar
|
||||
}
|
||||
use Foo::Bar;
|
||||
const C: Foo = Foo::Bar;
|
||||
fn main<const CP: Foo>(param: Foo) {
|
||||
let local = Foo::Bar;
|
||||
let _: Foo = Bar;
|
||||
//^ error: invalid `_` expression, expected type `fn()`
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Foo {
|
||||
Bar
|
||||
}
|
||||
use Foo::Bar;
|
||||
const C: Foo = Foo::Bar;
|
||||
fn main<const CP: Foo>(param: Foo) {
|
||||
let local = Foo::Bar;
|
||||
let _: Foo = C;
|
||||
//^ error: invalid `_` expression, expected type `fn()`
|
||||
}
|
||||
"#,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -44,6 +44,7 @@ mod handlers {
|
|||
pub(crate) mod private_assoc_item;
|
||||
pub(crate) mod private_field;
|
||||
pub(crate) mod replace_filter_map_next_with_find_map;
|
||||
pub(crate) mod typed_hole;
|
||||
pub(crate) mod type_mismatch;
|
||||
pub(crate) mod unimplemented_builtin_macro;
|
||||
pub(crate) mod unresolved_extern_crate;
|
||||
|
@ -290,6 +291,7 @@ pub fn diagnostics(
|
|||
AnyDiagnostic::PrivateAssocItem(d) => handlers::private_assoc_item::private_assoc_item(&ctx, &d),
|
||||
AnyDiagnostic::PrivateField(d) => handlers::private_field::private_field(&ctx, &d),
|
||||
AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => handlers::replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d),
|
||||
AnyDiagnostic::TypedHole(d) => handlers::typed_hole::typed_hole(&ctx, &d),
|
||||
AnyDiagnostic::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d),
|
||||
AnyDiagnostic::UndeclaredLabel(d) => handlers::undeclared_label::undeclared_label(&ctx, &d),
|
||||
AnyDiagnostic::UnimplementedBuiltinMacro(d) => handlers::unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d),
|
||||
|
|
Loading…
Reference in a new issue