Skip to content

Commit

Permalink
Add diagnostic for _ expressions (typed holes)
Browse files Browse the repository at this point in the history
  • Loading branch information
Veykril committed May 28, 2023
1 parent 150082b commit 8bc826d
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 61 deletions.
7 changes: 7 additions & 0 deletions crates/base-db/src/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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)>,
Expand All @@ -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();
Expand Down
6 changes: 6 additions & 0 deletions crates/hir-def/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
13 changes: 13 additions & 0 deletions crates/hir-ty/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
45 changes: 25 additions & 20 deletions crates/hir-ty/src/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
});
Expand Down
25 changes: 16 additions & 9 deletions crates/hir-ty/src/infer/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions crates/hir/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ diagnostics![
PrivateAssocItem,
PrivateField,
ReplaceFilterMapNextWithFindMap,
TypedHole,
TypeMismatch,
UndeclaredLabel,
UnimplementedBuiltinMacro,
Expand All @@ -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>>,
Expand Down
92 changes: 60 additions & 32 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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},
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 8bc826d

Please sign in to comment.