diff --git a/crates/hir-analysis/src/ty/constraint.rs b/crates/hir-analysis/src/ty/constraint.rs index 2f9bfa197..c19cf09d6 100644 --- a/crates/hir-analysis/src/ty/constraint.rs +++ b/crates/hir-analysis/src/ty/constraint.rs @@ -23,7 +23,7 @@ use crate::{ binder::Binder, func_def::HirFuncDefKind, trait_lower::{lower_impl_trait, lower_trait}, - ty_def::{free_inference_keys, TyVarSort}, + ty_def::{inference_keys, TyVarSort}, unify::InferenceKey, }, HirAnalysisDb, @@ -58,7 +58,7 @@ pub(crate) fn ty_constraints(db: &dyn HirAnalysisDb, ty: TyId) -> ConstraintList // If the constraint type contains unbound type parameters, we just ignore it. let mut new_constraints = BTreeSet::new(); for &pred in constraints.predicates(db) { - if free_inference_keys(db, pred.ty(db)).is_empty() { + if inference_keys(db, pred.ty(db)).is_empty() { new_constraints.insert(pred); } } diff --git a/crates/hir-analysis/src/ty/constraint_solver.rs b/crates/hir-analysis/src/ty/constraint_solver.rs index a65734658..059007ae3 100644 --- a/crates/hir-analysis/src/ty/constraint_solver.rs +++ b/crates/hir-analysis/src/ty/constraint_solver.rs @@ -15,7 +15,7 @@ use super::{ use crate::{ ty::{ constraint::{collect_super_traits, ty_constraints}, - ty_def::free_inference_keys, + ty_def::inference_keys, }, HirAnalysisDb, }; @@ -33,7 +33,7 @@ pub(crate) fn check_ty_wf( ty: TyId, assumptions: AssumptionListId, ) -> GoalSatisfiability { - assert!(free_inference_keys(db, ty).is_empty()); + assert!(inference_keys(db, ty).is_empty()); let (_, args) = ty.decompose_ty_app(db); diff --git a/crates/hir-analysis/src/ty/def_analysis.rs b/crates/hir-analysis/src/ty/def_analysis.rs index e41c16353..0c37424d8 100644 --- a/crates/hir-analysis/src/ty/def_analysis.rs +++ b/crates/hir-analysis/src/ty/def_analysis.rs @@ -781,6 +781,9 @@ impl<'db> Visitor for DefAnalyzer<'db> { self.def = def; } + fn visit_body(&mut self, _ctxt: &mut VisitorCtxt<'_, LazyBodySpan>, _body: hir::hir_def::Body) { + } + fn visit_func_param_list( &mut self, ctxt: &mut VisitorCtxt<'_, LazyFuncParamListSpan>, diff --git a/crates/hir-analysis/src/ty/diagnostics.rs b/crates/hir-analysis/src/ty/diagnostics.rs index c8ec8ce03..2eebc663c 100644 --- a/crates/hir-analysis/src/ty/diagnostics.rs +++ b/crates/hir-analysis/src/ty/diagnostics.rs @@ -693,6 +693,10 @@ pub enum BodyDiag { primary: DynLazySpan, given: Either, }, + + TypeAnnotationNeeded { + primary: DynLazySpan, + }, } impl BodyDiag { @@ -885,6 +889,7 @@ impl BodyDiag { Self::InvisibleTraitMethod { .. } => 27, Self::MethodNotFound { .. } => 28, Self::NotValue { .. } => 29, + Self::TypeAnnotationNeeded { .. } => 30, } } @@ -947,6 +952,7 @@ impl BodyDiag { } Self::NotValue { .. } => "value is expected".to_string(), + Self::TypeAnnotationNeeded { .. } => "type annotation is needed".to_string(), } } @@ -1482,6 +1488,21 @@ impl BodyDiag { primary.resolve(db), )] } + + Self::TypeAnnotationNeeded { primary } => { + vec![ + SubDiagnostic::new( + LabelStyle::Primary, + "type annotation is needed".to_string(), + primary.resolve(db), + ), + SubDiagnostic::new( + LabelStyle::Secondary, + "consider giving `: Type` here".to_string(), + primary.resolve(db), + ), + ] + } } } diff --git a/crates/hir-analysis/src/ty/ty_check/expr.rs b/crates/hir-analysis/src/ty/ty_check/expr.rs index 1ce9ada6a..ffba22ebb 100644 --- a/crates/hir-analysis/src/ty/ty_check/expr.rs +++ b/crates/hir-analysis/src/ty/ty_check/expr.rs @@ -671,13 +671,13 @@ impl<'db> TyChecker<'db> { unreachable!() }; - let expected_elem_ty = match expected.decompose_ty_app(self.db) { + let mut expected_elem_ty = match expected.decompose_ty_app(self.db) { (base, args) if base.is_array(self.db) => args[0], _ => self.fresh_ty(), }; for elem in elems { - self.check_expr(*elem, expected_elem_ty); + expected_elem_ty = self.check_expr(*elem, expected_elem_ty).ty; } let ty = TyId::array_with_elem(self.db, expected_elem_ty, elems.len()); @@ -689,12 +689,12 @@ impl<'db> TyChecker<'db> { unreachable!() }; - let expected_elem_ty = match expected.decompose_ty_app(self.db) { + let mut expected_elem_ty = match expected.decompose_ty_app(self.db) { (base, args) if base.is_array(self.db) => args[0], _ => self.fresh_ty(), }; - self.check_expr(*elem, expected_elem_ty); + expected_elem_ty = self.check_expr(*elem, expected_elem_ty).ty; let array = TyId::app(self.db, TyId::array(self.db), expected_elem_ty); let ty = if let Some(len_body) = len.to_opt() { diff --git a/crates/hir-analysis/src/ty/ty_check/mod.rs b/crates/hir-analysis/src/ty/ty_check/mod.rs index 313860013..eb4eacf0d 100644 --- a/crates/hir-analysis/src/ty/ty_check/mod.rs +++ b/crates/hir-analysis/src/ty/ty_check/mod.rs @@ -10,22 +10,27 @@ pub use env::ExprProp; use env::TyCheckEnv; pub(super) use expr::TraitOps; use hir::{ - hir_def::{Body, ExprId, Func, LitKind, PatId, TypeId as HirTyId}, - span::DynLazySpan, + hir_def::{Body, Expr, ExprId, Func, LitKind, Pat, PatId, TypeId as HirTyId}, + span::{expr::LazyExprSpan, pat::LazyPatSpan, DynLazySpan}, + visitor::{walk_expr, walk_pat, Visitor, VisitorCtxt}, }; pub(super) use path::RecordLike; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use super::{ canonical::Canonical, - diagnostics::{BodyDiag, FuncBodyDiagAccumulator, TyDiagCollection, TyLowerDiag}, + constraint::AssumptionListId, + diagnostics::{BodyDiag, FuncBodyDiag, FuncBodyDiagAccumulator, TyDiagCollection, TyLowerDiag}, fold::TyFoldable, trait_def::{TraitInstId, TraitMethod}, ty_def::{InvalidCause, Kind, TyId, TyVarSort}, ty_lower::lower_hir_ty, - unify::{UnificationError, UnificationTable}, + unify::{InferenceKey, UnificationError, UnificationTable}, +}; +use crate::{ + ty::ty_def::{inference_keys, TyFlags}, + HirAnalysisDb, }; -use crate::HirAnalysisDb; #[salsa::tracked(return_ref)] pub fn check_func_body(db: &dyn HirAnalysisDb, func: Func) -> TypedBody { @@ -67,9 +72,8 @@ impl<'db> TyChecker<'db> { self.check_expr(root_expr, self.expected); } - fn finish(mut self) -> TypedBody { - // TODO: check for untyped expressions and patterns. - self.env.finish(&mut self.table) + fn finish(self) -> TypedBody { + TyCheckerFinalizer::new(self).finish() } fn new(db: &'db dyn HirAnalysisDb, env: TyCheckEnv<'db>, expected: TyId) -> Self { @@ -247,3 +251,102 @@ impl TraitMethod { tc.table.instantiate_to_term(ty) } } + +struct TyCheckerFinalizer<'db> { + db: &'db dyn HirAnalysisDb, + body: TypedBody, + assumptions: AssumptionListId, + ty_vars: FxHashSet, + wf_reported: FxHashSet, + diags: Vec, +} + +impl<'db> TyCheckerFinalizer<'db> { + fn new(mut checker: TyChecker<'db>) -> Self { + let assumptions = checker.env.assumptions(); + let body = checker.env.finish(&mut checker.table); + Self { + db: checker.db, + body, + assumptions, + ty_vars: FxHashSet::default(), + wf_reported: FxHashSet::default(), + diags: Vec::new(), + } + } + + fn finish(mut self) -> TypedBody { + self.check_unknown_types(); + + for diag in self.diags { + FuncBodyDiagAccumulator::push(self.db, diag); + } + self.body + } + + fn check_unknown_types(&mut self) { + impl<'db> Visitor for TyCheckerFinalizer<'db> { + fn visit_pat(&mut self, ctxt: &mut VisitorCtxt<'_, LazyPatSpan>, pat: PatId, _: &Pat) { + let ty = self.body.pat_ty(self.db, pat); + let span = ctxt.span().unwrap(); + self.check_unknown(ty, span.clone().into()); + self.check_wf(ty, span.into()); + + walk_pat(self, ctxt, pat) + } + + fn visit_expr( + &mut self, + ctxt: &mut VisitorCtxt<'_, LazyExprSpan>, + expr: ExprId, + _: &Expr, + ) { + let ty = self.body.expr_ty(self.db, expr); + let span = ctxt.span().unwrap(); + self.check_unknown(ty, span.clone().into()); + self.check_wf(ty, span.into()); + + walk_expr(self, ctxt, expr) + } + } + + if let Some(body) = self.body.body { + let mut ctxt = VisitorCtxt::with_body(self.db.as_hir_db(), body); + self.visit_body(&mut ctxt, body); + } + } + + fn check_unknown(&mut self, ty: TyId, span: DynLazySpan) { + let flags = ty.flags(self.db); + if flags.contains(TyFlags::HAS_INVALID) || !flags.contains(TyFlags::HAS_VAR) { + return; + } + + let mut skip_diag = false; + for key in inference_keys(self.db, ty) { + // If at least one of the inference keys are already seen, we will skip emitting + // diagnostics. + skip_diag |= !self.ty_vars.insert(key); + } + + if !skip_diag { + let diag = BodyDiag::TypeAnnotationNeeded { primary: span }; + self.diags.push(diag.into()) + } + } + + fn check_wf(&mut self, ty: TyId, span: DynLazySpan) { + let flags = ty.flags(self.db); + if flags.contains(TyFlags::HAS_INVALID) + || flags.contains(TyFlags::HAS_VAR) + || self.wf_reported.contains(&ty) + { + return; + } + + if let Some(diag) = ty.emit_sat_diag(self.db, self.assumptions, span) { + self.wf_reported.insert(ty); + self.diags.push(diag.into()); + } + } +} diff --git a/crates/hir-analysis/src/ty/ty_def.rs b/crates/hir-analysis/src/ty/ty_def.rs index a33b75a20..bcbbcbf9e 100644 --- a/crates/hir-analysis/src/ty/ty_def.rs +++ b/crates/hir-analysis/src/ty/ty_def.rs @@ -1,6 +1,6 @@ //! This module contains the type definitions for the Fe type system. -use std::{collections::BTreeSet, fmt}; +use std::fmt; use bitflags::bitflags; use common::input::IngotKind; @@ -12,6 +12,7 @@ use hir::{ }, span::DynLazySpan, }; +use rustc_hash::FxHashSet; use super::{ adt_def::AdtDef, @@ -974,16 +975,16 @@ impl HasKind for FuncDef { } } -pub(crate) fn free_inference_keys<'db, V>( +pub(crate) fn inference_keys<'db, V>( db: &'db dyn HirAnalysisDb, visitable: V, -) -> BTreeSet +) -> FxHashSet where V: TyVisitable<'db>, { struct FreeInferenceKeyCollector<'db> { db: &'db dyn HirAnalysisDb, - keys: BTreeSet, + keys: FxHashSet, } impl<'db> TyVisitor<'db> for FreeInferenceKeyCollector<'db> { @@ -998,7 +999,7 @@ where let mut collector = FreeInferenceKeyCollector { db, - keys: BTreeSet::new(), + keys: FxHashSet::default(), }; visitable.visit_with(&mut collector); diff --git a/crates/hir-analysis/src/ty/unify.rs b/crates/hir-analysis/src/ty/unify.rs index 864186f27..f6ab634df 100644 --- a/crates/hir-analysis/src/ty/unify.rs +++ b/crates/hir-analysis/src/ty/unify.rs @@ -9,7 +9,7 @@ use super::{ binder::Binder, fold::{TyFoldable, TyFolder}, trait_def::{Implementor, TraitInstId}, - ty_def::{free_inference_keys, ApplicableTyProp, Kind, TyData, TyId, TyVar, TyVarSort}, + ty_def::{inference_keys, ApplicableTyProp, Kind, TyData, TyId, TyVar, TyVarSort}, }; use crate::{ ty::const_ty::{ConstTyData, EvaluatedConstTy}, @@ -242,7 +242,7 @@ impl<'db> UnificationTable<'db> { /// 2. Universe check: The sort of the type variable must match the sort of /// the type. fn unify_var_value(&mut self, var: &TyVar, value: TyId) -> UnificationResult { - if free_inference_keys(self.db, value).contains(&var.key) { + if inference_keys(self.db, value).contains(&var.key) { return Err(UnificationError::OccursCheckFailed); } diff --git a/crates/hir/src/hir_def/body.rs b/crates/hir/src/hir_def/body.rs index 2ceb9c02f..78df5521f 100644 --- a/crates/hir/src/hir_def/body.rs +++ b/crates/hir/src/hir_def/body.rs @@ -9,17 +9,16 @@ use cranelift_entity::{EntityRef, PrimaryMap, SecondaryMap}; use parser::ast::{self, prelude::*}; use rustc_hash::FxHashMap; +use super::{ + scope_graph::ScopeId, Expr, ExprId, Partial, Pat, PatId, Stmt, StmtId, TopLevelMod, + TrackedItemId, +}; use crate::{ span::{item::LazyBodySpan, HirOrigin}, visitor::prelude::*, HirDb, }; -use super::{ - scope_graph::ScopeId, Expr, ExprId, Partial, Pat, PatId, Stmt, StmtId, TopLevelMod, - TrackedItemId, -}; - #[salsa::tracked] pub struct Body { #[id] @@ -72,7 +71,7 @@ impl Body { /// Currently, this is only used for testing. /// When it turns out to be generally useful, we need to consider to let /// salsa track this method. - pub fn block_order(self, db: &dyn HirDb) -> FxHashMap { + pub fn iter_block(self, db: &dyn HirDb) -> FxHashMap { BlockOrderCalculator::new(db, self).calculate() } } diff --git a/crates/hir/src/hir_def/scope_graph.rs b/crates/hir/src/hir_def/scope_graph.rs index dbc1a18ed..1eee0bb32 100644 --- a/crates/hir/src/hir_def/scope_graph.rs +++ b/crates/hir/src/hir_def/scope_graph.rs @@ -368,7 +368,7 @@ impl ScopeId { } pub fn pretty_path(self, db: &dyn HirDb) -> Option { let name = match self { - ScopeId::Block(body, expr) => format!("{{block{}}}", body.block_order(db)[&expr]), + ScopeId::Block(body, expr) => format!("{{block{}}}", body.iter_block(db)[&expr]), ScopeId::Item(ItemKind::Body(body)) => match body.body_kind(db) { BodyKind::FuncBody => "{fn_body}".to_string(), BodyKind::Anonymous => "{anonymous_body}".to_string(), diff --git a/crates/hir/src/hir_def/scope_graph_viz.rs b/crates/hir/src/hir_def/scope_graph_viz.rs index 62b777ffd..989a5da91 100644 --- a/crates/hir/src/hir_def/scope_graph_viz.rs +++ b/crates/hir/src/hir_def/scope_graph_viz.rs @@ -7,9 +7,8 @@ use cranelift_entity::{entity_impl, PrimaryMap}; use dot2::label::Text; use rustc_hash::{FxHashMap, FxHashSet}; -use crate::{hir_def::ItemKind, HirDb}; - use super::scope_graph::{EdgeKind, ScopeGraph, ScopeId}; +use crate::{hir_def::ItemKind, HirDb}; type NodeId = usize; @@ -113,7 +112,7 @@ impl<'db, 'a> dot2::Labeller<'a> for ScopeGraphFormatter<'db> { } ScopeId::Block(body, expr) => { - let idx = body.block_order(self.db)[expr]; + let idx = body.iter_block(self.db)[expr]; format!( r#" {{block{block_number}}} "#, block_color = "#383A42", diff --git a/crates/uitest/fixtures/ty_check/array.fe b/crates/uitest/fixtures/ty_check/array.fe index 107f121a0..ba021bef2 100644 --- a/crates/uitest/fixtures/ty_check/array.fe +++ b/crates/uitest/fixtures/ty_check/array.fe @@ -1,7 +1,7 @@ pub fn foo() { let x: [i32; 3] = [1, 2] - let y = [1, false, 1] + let z = [1, false, 1] - let x: [i32; 5] = [1; 10] + let mut x: [i32; 5] = [1; 10] let x = [1; false] } \ No newline at end of file diff --git a/crates/uitest/fixtures/ty_check/array.snap b/crates/uitest/fixtures/ty_check/array.snap index e1bbeaa9d..2ad715545 100644 --- a/crates/uitest/fixtures/ty_check/array.snap +++ b/crates/uitest/fixtures/ty_check/array.snap @@ -18,13 +18,31 @@ error[8-0000]: type mismatch error[8-0000]: type mismatch ┌─ array.fe:3:17 │ -3 │ let y = [1, false, 1] +3 │ let z = [1, false, 1] │ ^^^^^ expected ``, but `bool` is given error[8-0000]: type mismatch - ┌─ array.fe:5:23 + ┌─ array.fe:5:27 │ -5 │ let x: [i32; 5] = [1; 10] - │ ^^^^^^^ expected `[i32; 5]`, but `[i32; 10]` is given +5 │ let mut x: [i32; 5] = [1; 10] + │ ^^^^^^^ expected `[i32; 5]`, but `[i32; 10]` is given + +error[8-0030]: type annotation is needed + ┌─ array.fe:3:14 + │ +3 │ let z = [1, false, 1] + │ ^ + │ │ + │ type annotation is needed + │ consider giving `: Type` here + +error[8-0030]: type annotation is needed + ┌─ array.fe:6:14 + │ +6 │ let x = [1; false] + │ ^ + │ │ + │ type annotation is needed + │ consider giving `: Type` here diff --git a/crates/uitest/fixtures/ty_check/assign.snap b/crates/uitest/fixtures/ty_check/assign.snap index 915f96aec..d57f4d681 100644 --- a/crates/uitest/fixtures/ty_check/assign.snap +++ b/crates/uitest/fixtures/ty_check/assign.snap @@ -45,4 +45,13 @@ error[8-0018]: left-hand side of assignment is immutable 43 │ x = 1 │ ^ immutable assignment +error[8-0030]: type annotation is needed + ┌─ assign.fe:31:5 + │ +31 │ 1 = 1 + │ ^ + │ │ + │ type annotation is needed + │ consider giving `: Type` here + diff --git a/crates/uitest/fixtures/ty_check/field_access.snap b/crates/uitest/fixtures/ty_check/field_access.snap index 534e27482..f7413ddc6 100644 --- a/crates/uitest/fixtures/ty_check/field_access.snap +++ b/crates/uitest/fixtures/ty_check/field_access.snap @@ -21,4 +21,13 @@ error[8-0015]: invalid field index 16 │ bar.v │ ^^^^^ field `v` is not found in `Bar` +error[8-0030]: type annotation is needed + ┌─ field_access.fe:6:9 + │ +6 │ let x + │ ^ + │ │ + │ type annotation is needed + │ consider giving `: Type` here + diff --git a/crates/uitest/fixtures/ty_check/tuple.snap b/crates/uitest/fixtures/ty_check/tuple.snap index 792057636..373c8e7e2 100644 --- a/crates/uitest/fixtures/ty_check/tuple.snap +++ b/crates/uitest/fixtures/ty_check/tuple.snap @@ -15,4 +15,22 @@ error[8-0000]: type mismatch 4 │ let y: (i32, bool) = (1, 2) │ ^ expected `bool`, but `` is given +error[8-0030]: type annotation is needed + ┌─ tuple.fe:2:32 + │ +2 │ let x: (i32, u32, bool) = (1, 2) + │ ^ + │ │ + │ type annotation is needed + │ consider giving `: Type` here + +error[8-0030]: type annotation is needed + ┌─ tuple.fe:2:35 + │ +2 │ let x: (i32, u32, bool) = (1, 2) + │ ^ + │ │ + │ type annotation is needed + │ consider giving `: Type` here +