Skip to content

Commit

Permalink
ADT recursion
Browse files Browse the repository at this point in the history
  • Loading branch information
Grant Wuerker committed Feb 7, 2024
1 parent fd274c9 commit a8e12ab
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 57 deletions.
1 change: 1 addition & 0 deletions crates/hir-analysis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub struct Jar(
ty::diagnostics::ImplTraitDefDiagAccumulator,
ty::diagnostics::ImplDefDiagAccumulator,
ty::diagnostics::FuncDefDiagAccumulator,
ty::diagnostics::AdtRecursionConstituentAccumulator,
);

pub trait HirAnalysisDb: salsa::DbWithJar<Jar> + HirDb {
Expand Down
29 changes: 18 additions & 11 deletions crates/hir-analysis/src/ty/def_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ use super::{
collect_impl_block_constraints, collect_super_traits, AssumptionListId, SuperTraitCycle,
},
constraint_solver::{is_goal_satisfiable, GoalSatisfiability},
diagnostics::{ImplDiag, TraitConstraintDiag, TraitLowerDiag, TyDiagCollection, TyLowerDiag},
diagnostics::{
AdtRecursionConstituent, ImplDiag, TraitConstraintDiag, TraitLowerDiag, TyDiagCollection,
TyLowerDiag,
},
trait_def::{ingot_trait_env, Implementor, TraitDef, TraitMethod},
trait_lower::{lower_trait, lower_trait_ref, TraitRefLowerError},
ty_def::{AdtDef, AdtRef, AdtRefId, FuncDef, InvalidCause, TyData, TyId},
Expand All @@ -32,8 +35,9 @@ use crate::{
name_resolution::{resolve_path_early, EarlyResolvedPath, NameDomain, NameResKind},
ty::{
diagnostics::{
AdtDefDiagAccumulator, FuncDefDiagAccumulator, ImplDefDiagAccumulator,
ImplTraitDefDiagAccumulator, TraitDefDiagAccumulator, TypeAliasDefDiagAccumulator,
AdtDefDiagAccumulator, AdtRecursionConstituentAccumulator, FuncDefDiagAccumulator,
ImplDefDiagAccumulator, ImplTraitDefDiagAccumulator, TraitDefDiagAccumulator,
TypeAliasDefDiagAccumulator,
},
method_table::collect_methods,
trait_lower::lower_impl_trait,
Expand Down Expand Up @@ -62,8 +66,8 @@ pub fn analyze_adt(db: &dyn HirAnalysisDb, adt_ref: AdtRefId) {
AdtDefDiagAccumulator::push(db, diag);
}

if let Some(diag) = check_recursive_adt(db, adt_ref) {
AdtDefDiagAccumulator::push(db, diag);
if let Some(constituent) = check_recursive_adt(db, adt_ref) {
AdtRecursionConstituentAccumulator::push(db, constituent);
}
}

Expand Down Expand Up @@ -764,7 +768,7 @@ impl<'db> Visitor for DefAnalyzer<'db> {
pub(crate) fn check_recursive_adt(
db: &dyn HirAnalysisDb,
adt: AdtRefId,
) -> Option<TyDiagCollection> {
) -> Option<AdtRecursionConstituent> {
let adt_def = lower_adt(db, adt);
for field in adt_def.fields(db) {
for ty in field.iter_types(db) {
Expand All @@ -781,7 +785,7 @@ fn check_recursive_adt_impl(
db: &dyn HirAnalysisDb,
cycle: &salsa::Cycle,
adt: AdtRefId,
) -> Option<TyDiagCollection> {
) -> Option<AdtRecursionConstituent> {
let participants: FxHashSet<_> = cycle
.participant_keys()
.map(|key| check_recursive_adt::key_from_id(key.key_index()))
Expand All @@ -792,11 +796,14 @@ fn check_recursive_adt_impl(
for (ty_idx, ty) in field.iter_types(db).enumerate() {
for field_adt_ref in ty.collect_direct_adts(db) {
if participants.contains(&field_adt_ref) && participants.contains(&adt) {
let diag = TyLowerDiag::recursive_type(
adt.name_span(db),
adt_def.variant_ty_span(db, field_idx, ty_idx),
let constituent = AdtRecursionConstituent::new(
(adt, adt.name_span(db)),
(
field_adt_ref,
adt_def.variant_ty_span(db, field_idx, ty_idx),
),
);
return Some(diag.into());
return Some(constituent);
}
}
}
Expand Down
110 changes: 85 additions & 25 deletions crates/hir-analysis/src/ty/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ use hir::{
HirDb, SpannedHirDb,
};
use itertools::Itertools;
use rustc_hash::FxHashSet;

use crate::HirAnalysisDb;

use super::{
constraint::PredicateId,
ty_def::{Kind, TyId},
ty_def::{AdtRefId, Kind, TyId},
};
use crate::HirAnalysisDb;

#[salsa::accumulator]
pub struct AdtDefDiagAccumulator(pub(super) TyDiagCollection);
Expand All @@ -29,6 +31,8 @@ pub struct ImplDefDiagAccumulator(pub(super) TyDiagCollection);
pub struct FuncDefDiagAccumulator(pub(super) TyDiagCollection);
#[salsa::accumulator]
pub struct TypeAliasDefDiagAccumulator(pub(super) TyDiagCollection);
#[salsa::accumulator]
pub struct AdtRecursionConstituentAccumulator(pub(super) AdtRecursionConstituent);

#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::From)]
pub enum TyDiagCollection {
Expand All @@ -53,10 +57,7 @@ impl TyDiagCollection {
pub enum TyLowerDiag {
ExpectedStarKind(DynLazySpan),
InvalidTypeArgKind(DynLazySpan, String),
RecursiveType {
primary_span: DynLazySpan,
field_span: DynLazySpan,
},
AdtRecursion(Vec<AdtRecursionConstituent>),

UnboundTypeAliasParam {
span: DynLazySpan,
Expand Down Expand Up @@ -140,11 +141,8 @@ impl TyLowerDiag {
Self::InvalidTypeArgKind(span, msg)
}

pub(super) fn recursive_type(primary_span: DynLazySpan, field_span: DynLazySpan) -> Self {
Self::RecursiveType {
primary_span,
field_span,
}
pub(super) fn adt_recursion(constituents: Vec<AdtRecursionConstituent>) -> Self {
Self::AdtRecursion(constituents)
}

pub(super) fn unbound_type_alias_param(
Expand Down Expand Up @@ -249,7 +247,7 @@ impl TyLowerDiag {
match self {
Self::ExpectedStarKind(_) => 0,
Self::InvalidTypeArgKind(_, _) => 1,
Self::RecursiveType { .. } => 2,
Self::AdtRecursion { .. } => 2,
Self::UnboundTypeAliasParam { .. } => 3,
Self::TypeAliasCycle { .. } => 4,
Self::InconsistentKindBound(_, _) => 5,
Expand All @@ -270,7 +268,7 @@ impl TyLowerDiag {
match self {
Self::ExpectedStarKind(_) => "expected `*` kind in this context".to_string(),
Self::InvalidTypeArgKind(_, _) => "invalid type argument kind".to_string(),
Self::RecursiveType { .. } => "recursive type is not allowed".to_string(),
Self::AdtRecursion { .. } => "recursive type is not allowed".to_string(),

Self::UnboundTypeAliasParam { .. } => {
"all type parameters of type alias must be given".to_string()
Expand Down Expand Up @@ -326,22 +324,23 @@ impl TyLowerDiag {
span.resolve(db),
)],

Self::RecursiveType {
primary_span,
field_span,
} => {
vec![
SubDiagnostic::new(
Self::AdtRecursion(constituents) => {
let mut diags = vec![];

for AdtRecursionConstituent { from, to } in constituents {
diags.push(SubDiagnostic::new(
LabelStyle::Primary,
"recursive type definition".to_string(),
primary_span.resolve(db),
),
SubDiagnostic::new(
from.1.resolve(db),
));
diags.push(SubDiagnostic::new(
LabelStyle::Secondary,
"recursion occurs here".to_string(),
field_span.resolve(db),
),
]
to.1.resolve(db),
));
}

diags
}

Self::UnboundTypeAliasParam {
Expand Down Expand Up @@ -1260,3 +1259,64 @@ impl DiagnosticVoucher for ImplDiag {
CompleteDiagnostic::new(severity, message, sub_diags, vec![], error_code)
}
}

/// Generates diagnostics from a list of ADT recursion constituents.
pub fn adt_recursion_diags(constituents: &[AdtRecursionConstituent]) -> Vec<TyDiagCollection> {
let mut diags = vec![];

// `unified_constituents` tracks constituents that have been included in recursions.
// Constituents in this set cannot be used to construct other recursions.
let mut unified_constituents = FxHashSet::default();

// `cur` is set to the first item in `constituents` that has not been included in another recursion.
while let Some(mut cur) =
(0..constituents.len()).find(|index| !unified_constituents.contains(index))
{
unified_constituents.insert(cur);
let mut recursion = vec![cur];

// The recursion is complete if the `from` of the first constituent is equal to the `to` of `cur`.
while constituents[recursion[0]].from.0 != constituents[cur].to.0 {
// The next constituent of the recursion is found by comparing the `to` of `cur` with `from` of the candidate constituent.
if let Some(index) = (0..constituents.len()).find(|index| {
!unified_constituents.contains(index)
&& constituents[cur].to.0 == constituents[*index].from.0
}) {
cur = index;
unified_constituents.insert(index);
recursion.push(index);
} else {
break;
};
}

diags.push(
TyLowerDiag::adt_recursion(
recursion
.iter()
.map(|index| constituents[*index].to_owned())
.collect(),
)
.into(),
);
}

diags
}

/// Constituent of an ADT recursion.
///
/// A full ADT recursion can be represented using a list of `AdtRecursionConstituent`s.
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct AdtRecursionConstituent {
/// The ADT definition from which the constituent originates and its name span.
pub from: (AdtRefId, DynLazySpan),
/// The ADT to which this recursion continues and the span where this occurs
pub to: (AdtRefId, DynLazySpan),
}

impl AdtRecursionConstituent {
pub fn new(from: (AdtRefId, DynLazySpan), to: (AdtRefId, DynLazySpan)) -> Self {
Self { from, to }
}
}
30 changes: 23 additions & 7 deletions crates/hir-analysis/src/ty/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use hir::{analysis_pass::ModuleAnalysisPass, hir_def::TopLevelMod};
use itertools::Itertools;

use self::{
def_analysis::{
analyze_adt, analyze_func, analyze_impl, analyze_impl_trait, analyze_trait,
analyze_type_alias,
},
diagnostics::{
AdtDefDiagAccumulator, FuncDefDiagAccumulator, ImplDefDiagAccumulator,
ImplTraitDefDiagAccumulator, TraitDefDiagAccumulator, TypeAliasDefDiagAccumulator,
adt_recursion_diags, AdtDefDiagAccumulator, AdtRecursionConstituentAccumulator,
FuncDefDiagAccumulator, ImplDefDiagAccumulator, ImplTraitDefDiagAccumulator,
TraitDefDiagAccumulator, TypeAliasDefDiagAccumulator,
},
ty_def::AdtRefId,
};
Expand Down Expand Up @@ -61,12 +63,26 @@ impl<'db> ModuleAnalysisPass for TypeDefAnalysisPass<'db> {
.iter()
.map(|c| AdtRefId::from_contract(self.db, *c)),
);
let (diags, recursion_constituents): (Vec<_>, Vec<_>) = adts
.map(|adt| {
(
analyze_adt::accumulated::<AdtDefDiagAccumulator>(self.db, adt),
analyze_adt::accumulated::<AdtRecursionConstituentAccumulator>(self.db, adt),
)
})
.unzip();
let recursion_constituents = recursion_constituents.into_iter().flatten().collect_vec();

adts.flat_map(|adt| {
analyze_adt::accumulated::<AdtDefDiagAccumulator>(self.db, adt).into_iter()
})
.map(|diag| diag.to_voucher())
.collect()
diags
.into_iter()
.flatten()
.map(|diag| diag.to_voucher())
.chain(
adt_recursion_diags(&recursion_constituents)
.iter()
.map(|diag| diag.to_voucher()),
)
.collect()
}
}

Expand Down
22 changes: 8 additions & 14 deletions crates/uitest/fixtures/ty/def/recursive_type.snap
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
source: crates/uitest/tests/ty.rs
expression: diags
input_file: crates/uitest/fixtures/ty/recursive_type.fe
input_file: crates/uitest/fixtures/ty/def/recursive_type.fe
---
error[3-0002]: recursive type is not allowed
┌─ recursive_type.fe:1:12
Expand All @@ -12,24 +12,18 @@ error[3-0002]: recursive type is not allowed
│ -- recursion occurs here

error[3-0002]: recursive type is not allowed
┌─ recursive_type.fe:5:12
5 │ pub struct S2 {
│ ^^ recursive type definition
6 │ s: S3
│ -- recursion occurs here

error[3-0002]: recursive type is not allowed
┌─ recursive_type.fe:9:12
┌─ recursive_type.fe:5:12
5 │ pub struct S2 {
│ ^^ recursive type definition
6 │ s: S3
│ -- recursion occurs here
·
9 │ pub struct S3 {
│ ^^ recursive type definition
10 │ s: S4
│ -- recursion occurs here

error[3-0002]: recursive type is not allowed
┌─ recursive_type.fe:13:12
·
13 │ pub struct S4 {
│ ^^ recursive type definition
14 │ s: S2
Expand Down

0 comments on commit a8e12ab

Please sign in to comment.