Skip to content

Port match statement analysis from v1 compiler #1053

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/hir-analysis/src/name_resolution/path_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ impl<'db> ResolvedVariant<'db> {
&self.enum_(db).variants(db.as_hir_db()).data(db.as_hir_db())[self.idx]
}

pub fn is_record(&self, db: &'db dyn HirAnalysisDb) -> bool {
matches!(self.variant_kind(db), VariantKind::Record(_))
}

pub fn variant_kind(&self, db: &'db dyn HirAnalysisDb) -> VariantKind<'db> {
self.variant_def(db).kind
}
Expand Down
4 changes: 0 additions & 4 deletions crates/hir-analysis/src/ty/adt_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ impl<'db> AdtDef<'db> {
self.param_set(db).explicit_params(db)
}

pub(crate) fn is_struct(self, db: &dyn HirAnalysisDb) -> bool {
matches!(self.adt_ref(db).data(db), AdtRef::Struct(_))
}

pub fn scope(self, db: &'db dyn HirAnalysisDb) -> ScopeId<'db> {
self.adt_ref(db).scope(db)
}
Expand Down
27 changes: 9 additions & 18 deletions crates/hir-analysis/src/ty/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,14 +324,11 @@ pub enum BodyDiag<'db> {
}

impl<'db> BodyDiag<'db> {
pub(super) fn unit_variant_expected<T>(
pub(super) fn unit_variant_expected(
db: &'db dyn HirAnalysisDb,
primary: DynLazySpan<'db>,
record_like: T,
) -> Self
where
T: RecordLike<'db>,
{
record_like: RecordLike<'db>,
) -> Self {
let kind_name = record_like.kind_name(db);
let hint = record_like.initializer_hint(db);
Self::UnitVariantExpected {
Expand All @@ -341,14 +338,11 @@ impl<'db> BodyDiag<'db> {
}
}

pub(super) fn tuple_variant_expected<T>(
pub(super) fn tuple_variant_expected(
db: &'db dyn HirAnalysisDb,
primary: DynLazySpan<'db>,
record_like: Option<T>,
) -> Self
where
T: RecordLike<'db>,
{
record_like: Option<RecordLike>,
) -> Self {
let (kind_name, hint) = if let Some(record_like) = record_like {
(
Some(record_like.kind_name(db)),
Expand All @@ -365,14 +359,11 @@ impl<'db> BodyDiag<'db> {
}
}

pub(super) fn record_expected<T>(
pub(super) fn record_expected(
db: &'db dyn HirAnalysisDb,
primary: DynLazySpan<'db>,
record_like: Option<T>,
) -> Self
where
T: RecordLike<'db>,
{
record_like: Option<RecordLike<'db>>,
) -> Self {
let (kind_name, hint) = if let Some(record_like) = record_like {
(
Some(record_like.kind_name(db)),
Expand Down
1 change: 1 addition & 0 deletions crates/hir-analysis/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod diagnostics;
pub mod fold;
pub mod func_def;
pub mod method_table;
pub mod pattern_matching;
pub mod trait_def;
pub mod trait_lower;
pub mod trait_resolution;
Expand Down
187 changes: 187 additions & 0 deletions crates/hir-analysis/src/ty/pattern_matching.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
use hir::hir_def::{Body, IntegerId, LitKind, Pat, PatId, RecordPatField, StringId};

use super::{ty_check::TypedBody, ty_def::TyId};
use crate::{
name_resolution::{resolve_path, PathRes, PathResError, PathResErrorKind, ResolvedVariant},
ty::ty_check::RecordLike,
HirAnalysisDb,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SimplifiedPat<'db> {
ctor: PatCtor<'db>,
args: Vec<Self>,
hir_pat: Option<PatId>,
ty: TyId<'db>,
}

impl<'db> SimplifiedPat<'db> {
pub fn new(
ctor: PatCtor<'db>,
args: Vec<SimplifiedPat<'db>>,
hir_pat: PatId,
ty: TyId<'db>,
) -> Self {
Self {
ctor,
args,
hir_pat: Some(hir_pat),
ty,
}
}

pub fn wildcard(ty: TyId<'db>) -> Self {
Self {
ctor: PatCtor::WildCard,
args: vec![],
hir_pat: None,
ty,
}
}

pub fn simplify(db: &'db dyn HirAnalysisDb, pat: PatId, typed_body: &'db TypedBody) -> Self {
let ty = typed_body.pat_ty(db, pat);
assert!(!ty.has_invalid(db));

let pat_data = pat.data(db.as_hir_db(), typed_body.body.unwrap()).unwrap();

match pat_data {
Pat::WildCard => SimplifiedPat::new(PatCtor::WildCard, vec![], pat, ty),
Pat::Rest => unreachable!(), // Not allowed as a root pattern
Pat::Lit(partial) => match partial.unwrap() {
LitKind::Int(integer_id) => {
SimplifiedPat::new(PatCtor::Int(*integer_id), vec![], pat, ty)
}
LitKind::String(string_id) => {
SimplifiedPat::new(PatCtor::String(*string_id), vec![], pat, ty)
}
LitKind::Bool(b) => SimplifiedPat::new(PatCtor::Bool(*b), vec![], pat, ty),
},
Pat::Tuple(vec) => simplify_tuple_pattern(db, pat, &vec, typed_body),
Pat::Path(partial, _) => {
let path = partial.unwrap();
let scope = typed_body.body.unwrap().scope(); // xxx use tighter scope??
match resolve_path(db, *path, scope, true) {
Ok(res) => {
if let PathRes::EnumVariant(var) = res {
// xxx use var.ty??
Self {
ctor: PatCtor::Variant(var.idx as u32), // xxx change idx to u32
args: vec![], // xxx
hir_pat: Some(pat),
ty,
}
} else {
todo!()
}
}
Err(err) => {
if path.is_bare_ident(db.as_hir_db()) {
Self {
ctor: PatCtor::WildCard,
args: vec![], // xxx
hir_pat: Some(pat),
ty,
}
} else {
todo!()
}
}
}
}
Pat::PathTuple(_, vec) => simplify_tuple_pattern(db, pat, &vec, typed_body),

Pat::Record(path, pat_fields) => {
let path = path.unwrap();
let body = typed_body.body.unwrap();
let scope = body.scope();
match resolve_path(db, *path, scope, true) {
Ok(res) => match res {
PathRes::Ty(ty_id) if ty_id.is_struct(db) => {
simplify_record_pattern(db, &ty_id.into(), pat, typed_body)
}
PathRes::EnumVariant(variant) => {
simplify_record_pattern(db, &variant.into(), pat, typed_body)
}
_ => todo!(),
},
Err(_) => {
todo!()
}
}
}
Pat::Or(pat_id, pat_id1) => {
let pat = Self::simplify(db, *pat_id, typed_body);
let pat1 = Self::simplify(db, *pat_id1, typed_body);
Self {
ctor: PatCtor::Or,
args: vec![pat, pat1],
hir_pat: Some(*pat_id),
ty,
}
}
}
}
}

fn simplify_record_pattern<'db>(
db: &'db dyn HirAnalysisDb,
record: &RecordLike<'db>,
pat: PatId,
typed_body: &'db TypedBody<'db>,
) -> SimplifiedPat<'db> {
let pat_data = pat.data(db.as_hir_db(), typed_body.body.unwrap()).unwrap();
let Pat::Record(_, fields) = pat_data else {
unreachable!()
};

let body = typed_body.body.unwrap();
let args = record
.record_labels(db)
.iter()
.map(|l| {
if let Some(pat_field) = fields
.iter()
.find(|pf| pf.label(db.as_hir_db(), body).unwrap().data(db) == l.data(db))
{
SimplifiedPat::simplify(db, pat_field.pat, typed_body)
} else {
let ty = record.record_field_ty(db, *l).unwrap();
SimplifiedPat::wildcard(ty)
}
})
.collect();

SimplifiedPat {
ctor: PatCtor::Struct,
args,
hir_pat: Some(pat),
ty: typed_body.pat_ty(db, pat),
}
}

fn simplify_tuple_pattern<'db>(
db: &'db dyn HirAnalysisDb,
pat: PatId,
elem_pats: &[PatId],
typed_body: &TypedBody<'db>,
) -> SimplifiedPat<'db> {
let body = typed_body.body.unwrap();
let ty = typed_body.pat_ty(db, pat);

// should we make a `TupleLike` trait too?
todo!()
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PatCtor<'db> {
Or,
WildCard, // xxx v1 had Option<(SmolStr, usize)>
Tuple,
Struct,
Variant(u32),
// FIXME: Extend this to `IntRange` when we add range pattern.
Int(IntegerId<'db>),
String(StringId<'db>),
Bool(bool),
}
33 changes: 21 additions & 12 deletions crates/hir-analysis/src/ty/ty_check/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,11 @@ impl<'db> TyChecker<'db> {
ExprProp::new(self.table.instantiate_to_term(const_ty_ty), true)
} else {
let diag = if ty.is_struct(self.db) {
BodyDiag::unit_variant_expected(self.db, span.into(), ty)
BodyDiag::unit_variant_expected(
self.db,
span.into(),
RecordLike::Type(ty),
)
} else {
BodyDiag::NotValue {
primary: span.into(),
Expand Down Expand Up @@ -455,7 +459,7 @@ impl<'db> TyChecker<'db> {
let diag = BodyDiag::unit_variant_expected(
self.db,
expr.lazy_span(self.body()).into(),
variant,
RecordLike::Variant(variant),
);
self.push_diag(diag);

Expand Down Expand Up @@ -492,31 +496,34 @@ impl<'db> TyChecker<'db> {
};

match reso {
PathRes::Ty(ty) if ty.is_record(self.db) => {
PathRes::Ty(ty) if ty.is_struct(self.db) => {
let ty = self.table.instantiate_to_term(ty);
self.check_record_init_fields(ty, expr);
self.check_record_init_fields(RecordLike::Type(ty), expr);
ExprProp::new(ty, true)
}

PathRes::Ty(ty) | PathRes::Func(ty) | PathRes::Const(ty) => {
let diag = BodyDiag::record_expected(self.db, span.path().into(), Some(ty));
let diag = BodyDiag::record_expected(
self.db,
span.path().into(),
Some(RecordLike::Type(ty)),
);
self.push_diag(diag);
ExprProp::invalid(self.db)
}
PathRes::TypeMemberTbd(_) | PathRes::FuncParam(..) => {
let diag = BodyDiag::record_expected::<TyId>(self.db, span.path().into(), None);
let diag = BodyDiag::record_expected(self.db, span.path().into(), None);
self.push_diag(diag);
ExprProp::invalid(self.db)
}

PathRes::EnumVariant(variant) => {
if variant.is_record(self.db) {
let ty = self.table.instantiate_to_term(variant.ty);
self.check_record_init_fields(variant, expr);
self.check_record_init_fields(RecordLike::Variant(variant), expr);
ExprProp::new(ty, true)
} else {
let diag =
BodyDiag::record_expected::<TyId<'db>>(self.db, span.path().into(), None);
let diag = BodyDiag::record_expected(self.db, span.path().into(), None);
self.push_diag(diag);

ExprProp::invalid(self.db)
Expand All @@ -541,7 +548,7 @@ impl<'db> TyChecker<'db> {
}
}

fn check_record_init_fields<T: RecordLike<'db>>(&mut self, mut record_like: T, expr: ExprId) {
fn check_record_init_fields(&mut self, mut record_like: RecordLike<'db>, expr: ExprId) {
let hir_db = self.db.as_hir_db();

let Partial::Present(Expr::RecordInit(_, fields)) = expr.data(hir_db, self.body()) else {
Expand Down Expand Up @@ -596,8 +603,10 @@ impl<'db> TyChecker<'db> {

match field {
FieldIndex::Ident(label) => {
if let Some(field_ty) = lhs_ty.record_field_ty(self.db, *label) {
if let Some(scope) = lhs_ty.record_field_scope(self.db, *label) {
if let Some(field_ty) = RecordLike::Type(lhs_ty).record_field_ty(self.db, *label) {
if let Some(scope) =
RecordLike::Type(lhs_ty).record_field_scope(self.db, *label)
{
if !is_scope_visible_from(self.db, scope, self.env.scope()) {
// Check the visibility of the field.
let diag = NameResDiag::Invisible(
Expand Down
2 changes: 1 addition & 1 deletion crates/hir-analysis/src/ty/ty_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ impl<'db> TyChecker<'db> {

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypedBody<'db> {
body: Option<Body<'db>>,
pub body: Option<Body<'db>>,
pat_ty: FxHashMap<PatId, TyId<'db>>,
expr_ty: FxHashMap<ExprId, ExprProp<'db>>,
callables: FxHashMap<ExprId, Callable<'db>>,
Expand Down
Loading
Loading