Skip to content

Commit

Permalink
feat: parsing & hir of impl blocks (#551)
Browse files Browse the repository at this point in the history
This is the first PR to support `impl` blocks for structs. Features
included in this PR are:

- Parsing of `impl` blocks. (`ast::Impl` and friends)
- Support `impl` blocks in `ItemTree`.
- Resolve self type of `impl`
- Collect all `impl`s from a package, or for a specific (struct) type.
- Several diagnostics related to `impl` blocks. (See tests in
`method_resolution.rs`)
- `Impl` code model to interact with impl blocks

Notable features that are still missing and will be addressed in
followup PRs:

- `self` type.
- Code generation.
- Only functions are support in impls. (type alias is missing)
- Language server support
- Docs
  • Loading branch information
baszalmstra authored Jan 7, 2024
1 parent 513dc7a commit 7a99ba6
Show file tree
Hide file tree
Showing 42 changed files with 1,365 additions and 137 deletions.
33 changes: 21 additions & 12 deletions crates/mun_diagnostics/src/hir/duplicate_definition_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ fn syntax_node_signature_range(
ast::StructDef::cast(syntax_node_ptr.to_node(parse.tree().syntax()))
.map_or_else(|| syntax_node_ptr.range(), |s| s.signature_range())
}
SyntaxKind::TYPE_ALIAS_DEF => {
ast::TypeAliasDef::cast(syntax_node_ptr.to_node(parse.tree().syntax()))
.map_or_else(|| syntax_node_ptr.range(), |s| s.signature_range())
}
_ => syntax_node_ptr.range(),
}
}
Expand All @@ -58,11 +62,13 @@ fn syntax_node_identifier_range(
parse: &Parse<SourceFile>,
) -> TextRange {
match syntax_node_ptr.kind() {
SyntaxKind::FUNCTION_DEF | SyntaxKind::STRUCT_DEF => syntax_node_ptr
.to_node(parse.tree().syntax())
.children()
.find(|n| n.kind() == SyntaxKind::NAME)
.map_or_else(|| syntax_node_ptr.range(), |name| name.text_range()),
SyntaxKind::FUNCTION_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::TYPE_ALIAS_DEF => {
syntax_node_ptr
.to_node(parse.tree().syntax())
.children()
.find(|n| n.kind() == SyntaxKind::NAME)
.map_or_else(|| syntax_node_ptr.range(), |name| name.text_range())
}
_ => syntax_node_ptr.range(),
}
}
Expand All @@ -85,7 +91,10 @@ pub struct DuplicateDefinition<'db, 'diag, DB: mun_hir::HirDatabase> {

impl<'db, 'diag, DB: mun_hir::HirDatabase> Diagnostic for DuplicateDefinition<'db, 'diag, DB> {
fn range(&self) -> TextRange {
syntax_node_identifier_range(&self.diag.definition, &self.db.parse(self.diag.file))
syntax_node_identifier_range(
&self.diag.definition.value,
&self.db.parse(self.diag.definition.file_id),
)
}

fn title(&self) -> String {
Expand All @@ -99,8 +108,8 @@ impl<'db, 'diag, DB: mun_hir::HirDatabase> Diagnostic for DuplicateDefinition<'d
fn primary_annotation(&self) -> Option<SourceAnnotation> {
Some(SourceAnnotation {
range: syntax_node_signature_range(
&self.diag.definition,
&self.db.parse(self.diag.file),
&self.diag.definition.value,
&self.db.parse(self.diag.definition.file_id),
),
message: format!("`{}` redefined here", self.diag.name),
})
Expand All @@ -109,10 +118,10 @@ impl<'db, 'diag, DB: mun_hir::HirDatabase> Diagnostic for DuplicateDefinition<'d
fn secondary_annotations(&self) -> Vec<SecondaryAnnotation> {
vec![SecondaryAnnotation {
range: InFile::new(
self.diag.file,
self.diag.first_definition.file_id,
syntax_node_signature_range(
&self.diag.first_definition,
&self.db.parse(self.diag.file),
&self.diag.first_definition.value,
&self.db.parse(self.diag.first_definition.file_id),
),
),
message: format!(
Expand All @@ -135,7 +144,7 @@ impl<'db, 'diag, DB: mun_hir::HirDatabase> Diagnostic for DuplicateDefinition<'d
impl<'db, 'diag, DB: mun_hir::HirDatabase> DuplicateDefinition<'db, 'diag, DB> {
/// Returns either `type` or `value` definition on the type of definition.
fn value_or_type_string(&self) -> &'static str {
if self.diag.definition.kind() == SyntaxKind::STRUCT_DEF {
if self.diag.definition.value.kind() == SyntaxKind::STRUCT_DEF {
"type"
} else {
"value"
Expand Down
2 changes: 2 additions & 0 deletions crates/mun_hir/src/code_model.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod function;
mod r#impl;
mod module;
mod package;
pub(crate) mod src;
Expand All @@ -12,6 +13,7 @@ pub use self::{
function::Function,
module::{Module, ModuleDef},
package::Package,
r#impl::{Impl, ImplData},
r#struct::{Field, LocalFieldId, Struct, StructKind, StructMemoryKind},
src::HasSource,
type_alias::TypeAlias,
Expand Down
5 changes: 2 additions & 3 deletions crates/mun_hir/src/code_model/function.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::Module;
use crate::expr::validator::ExprValidator;
use crate::expr::BodySourceMap;
use crate::has_module::HasModule;
use crate::ids::{FunctionId, Lookup};
use crate::name_resolution::Namespace;
use crate::resolve::HasResolver;
Expand Down Expand Up @@ -99,9 +100,7 @@ impl FunctionData {

impl Function {
pub fn module(self, db: &dyn HirDatabase) -> Module {
Module {
id: self.id.lookup(db.upcast()).module,
}
self.id.module(db.upcast()).into()
}

/// Returns the full name of the function including all module specifiers (e.g: `foo::bar`).
Expand Down
118 changes: 118 additions & 0 deletions crates/mun_hir/src/code_model/impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use crate::has_module::HasModule;
use crate::ids::{AssocItemId, FunctionLoc, ImplId, Intern, ItemContainerId, Lookup};
use crate::item_tree::{AssociatedItem, ItemTreeId};
use crate::type_ref::{LocalTypeRefId, TypeRefMap, TypeRefMapBuilder, TypeRefSourceMap};
use crate::{DefDatabase, FileId, Function, HirDatabase, ItemLoc, Module, Package, Ty};
use std::sync::Arc;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct Impl {
pub(crate) id: ImplId,
}

impl Impl {
/// Returns all the implementations defined in the specified `package`.
pub fn all_in_package(db: &dyn HirDatabase, package: Package) -> Vec<Impl> {
let inherent_impls = db.inherent_impls_in_package(package.id);
inherent_impls.all_impls().map(Self::from).collect()
}

/// The module in which the `impl` was defined.
///
/// Note that this is not necessarily the module in which the self type was defined. `impl`s
/// can be defined in any module from where the self type is visibile.
pub fn module(self, db: &dyn HirDatabase) -> Module {
self.id.module(db.upcast()).into()
}

/// Returns the file in which the implementation was defined
pub fn file_id(self, db: &dyn HirDatabase) -> FileId {
self.id.lookup(db.upcast()).id.file_id
}

/// Returns the type for which this is an implementation
pub fn self_ty(self, db: &dyn HirDatabase) -> Ty {
let data = db.impl_data(self.id);
let lowered = db.lower_impl(self.id);
lowered[data.self_ty].clone()
}

/// Returns all the items in the implementation
pub fn items(self, db: &dyn HirDatabase) -> Vec<AssocItem> {
db.impl_data(self.id)
.items
.iter()
.copied()
.map(Into::into)
.collect()
}
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum AssocItem {
Function(Function),
}

impl From<AssocItemId> for AssocItem {
fn from(value: AssocItemId) -> Self {
match value {
AssocItemId::FunctionId(it) => AssocItem::Function(it.into()),
}
}
}

impl From<ImplId> for Impl {
fn from(value: ImplId) -> Self {
Impl { id: value }
}
}

#[derive(Debug, PartialEq, Eq)]
pub struct ImplData {
pub items: Vec<AssocItemId>,
pub self_ty: LocalTypeRefId,
pub type_ref_map: TypeRefMap,
pub type_ref_source_map: TypeRefSourceMap,
}

impl ImplData {
pub(crate) fn impl_data_query(db: &dyn DefDatabase, id: ImplId) -> Arc<ImplData> {
let ItemLoc {
module: _,
id: tree_id,
} = id.lookup(db);

let item_tree = db.item_tree(tree_id.file_id);
let impl_def = &item_tree[tree_id.value];
let src = item_tree.source(db, tree_id.value);

// Associate the self type
let mut type_builder = TypeRefMapBuilder::default();
let self_ty = type_builder.alloc_from_node_opt(src.type_ref().as_ref());
let (type_ref_map, type_ref_source_map) = type_builder.finish();

// Add all the associated items
let container = ItemContainerId::ImplId(id);
let items = impl_def
.items
.iter()
.map(|it| match it {
AssociatedItem::Function(id) => {
let loc = FunctionLoc {
container,
id: ItemTreeId::new(tree_id.file_id, *id),
};
let func_id = loc.intern(db);
AssocItemId::FunctionId(func_id)
}
})
.collect();

Arc::new(ImplData {
items,
self_ty,
type_ref_map,
type_ref_source_map,
})
}
}
11 changes: 11 additions & 0 deletions crates/mun_hir/src/code_model/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ impl From<ModuleId> for Module {
}

impl Module {
/// Returns the package associated with this module
pub fn package(self) -> Package {
Package {
id: self.id.package,
}
}

/// Returns the module that corresponds to the given file
pub fn from_file(db: &dyn HirDatabase, file: FileId) -> Option<Module> {
Package::all(db)
Expand Down Expand Up @@ -71,6 +78,10 @@ impl Module {
let package_defs = db.package_defs(self.id.package);
package_defs.add_diagnostics(db.upcast(), self.id.local_id, sink);

// Add diagnostics from impls
let inherent_impls = db.inherent_impls_in_package(self.id.package);
inherent_impls.add_module_diagnostics(db, self.id.local_id, sink);

// Add diagnostics from the item tree
if let Some(file_id) = self.file_id(db) {
let item_tree = db.item_tree(file_id);
Expand Down
5 changes: 2 additions & 3 deletions crates/mun_hir/src/code_model/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use mun_syntax::{
};
use std::{fmt, sync::Arc};

use crate::has_module::HasModule;
use crate::resolve::HasResolver;
use crate::visibility::RawVisibility;
pub use ast::StructMemoryKind;
Expand Down Expand Up @@ -66,9 +67,7 @@ impl Field {

impl Struct {
pub fn module(self, db: &dyn HirDatabase) -> Module {
Module {
id: self.id.lookup(db.upcast()).module,
}
self.id.module(db.upcast()).into()
}

pub fn file_id(self, db: &dyn HirDatabase) -> FileId {
Expand Down
6 changes: 3 additions & 3 deletions crates/mun_hir/src/code_model/struct/validator/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::utils::tests::*;
fn test_private_leak_struct_fields() {
insta::assert_snapshot!(diagnostics(
r#"
struct Foo(usize);
pub struct Bar(usize);
Expand All @@ -27,7 +27,7 @@ fn test_private_leak_struct_fields() {
pub bar: Bar,
}
pub(crate) struct BarBaz;
pub(package) struct BarBaz;
// invalid, exporting pub(crate) to pub
pub struct FooBarBaz {
Expand All @@ -37,6 +37,6 @@ fn test_private_leak_struct_fields() {
"#),
@r###"
180..183: can't leak private type
392..395: can't leak private type
394..397: can't leak private type
"###);
}
5 changes: 2 additions & 3 deletions crates/mun_hir/src/code_model/type_alias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{

use super::Module;
use crate::expr::validator::TypeAliasValidator;
use crate::has_module::HasModule;
use crate::resolve::HasResolver;
use crate::ty::lower::LowerTyMap;

Expand All @@ -26,9 +27,7 @@ impl From<TypeAliasId> for TypeAlias {

impl TypeAlias {
pub fn module(self, db: &dyn HirDatabase) -> Module {
Module {
id: self.id.lookup(db.upcast()).module,
}
self.id.module(db.upcast()).into()
}

pub fn file_id(self, db: &dyn HirDatabase) -> FileId {
Expand Down
15 changes: 14 additions & 1 deletion crates/mun_hir/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#![allow(clippy::type_repetition_in_bounds)]

use crate::code_model::ImplData;
use crate::expr::BodySourceMap;
use crate::ids::{DefWithBodyId, FunctionId};
use crate::ids::{DefWithBodyId, FunctionId, ImplId};
use crate::input::{SourceRoot, SourceRootId};
use crate::item_tree::{self, ItemTree};
use crate::method_resolution::InherentImpls;
use crate::module_tree::ModuleTree;
use crate::name_resolution::Namespace;
use crate::package_defs::PackageDefs;
Expand Down Expand Up @@ -82,6 +84,8 @@ pub trait InternDatabase: SourceDatabase {
fn intern_struct(&self, loc: ids::StructLoc) -> ids::StructId;
#[salsa::interned]
fn intern_type_alias(&self, loc: ids::TypeAliasLoc) -> ids::TypeAliasId;
#[salsa::interned]
fn intern_impl(self, loc: ids::ImplLoc) -> ids::ImplId;
}

#[salsa::query_group(DefDatabaseStorage)]
Expand Down Expand Up @@ -113,6 +117,9 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {

#[salsa::invoke(ExprScopes::expr_scopes_query)]
fn expr_scopes(&self, def: DefWithBodyId) -> Arc<ExprScopes>;

#[salsa::invoke(ImplData::impl_data_query)]
fn impl_data(&self, def: ImplId) -> Arc<ImplData>;
}

#[salsa::query_group(HirDatabaseStorage)]
Expand All @@ -137,8 +144,14 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
#[salsa::invoke(crate::ty::callable_item_sig)]
fn callable_sig(&self, def: CallableDef) -> FnSig;

#[salsa::invoke(crate::ty::lower::lower_impl_query)]
fn lower_impl(&self, def: ImplId) -> Arc<LowerTyMap>;

#[salsa::invoke(crate::ty::type_for_def)]
fn type_for_def(&self, def: TypableDef, ns: Namespace) -> Ty;

#[salsa::invoke(InherentImpls::inherent_impls_in_package_query)]
fn inherent_impls_in_package(&self, package: PackageId) -> Arc<InherentImpls>;
}

fn parse_query(db: &dyn AstDatabase, file_id: FileId) -> Parse<SourceFile> {
Expand Down
Loading

0 comments on commit 7a99ba6

Please sign in to comment.