Skip to content

Commit

Permalink
feat: implement method calls (#577)
Browse files Browse the repository at this point in the history
Co-authored-by: Wodann <[email protected]>
  • Loading branch information
baszalmstra and Wodann authored Jan 7, 2025
1 parent ba2a7b2 commit 3d70e0e
Show file tree
Hide file tree
Showing 30 changed files with 880 additions and 53 deletions.
3 changes: 3 additions & 0 deletions crates/mun_codegen/src/ir/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@ impl<'db, 'ink, 't> BodyIrGenerator<'db, 'ink, 't> {
self.gen_binary_op(expr, *lhs, *rhs, op.expect("missing op"))
}
Expr::UnaryOp { expr, op } => self.gen_unary_op(*expr, *op),
Expr::MethodCall { .. } => {
unimplemented!("Method calls are not yet implemented in the IR generator")
}
Expr::Call {
ref callee,
ref args,
Expand Down
15 changes: 15 additions & 0 deletions crates/mun_hir/src/code_model/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,21 @@ impl FunctionData {
pub fn is_extern(&self) -> bool {
self.flags.is_extern()
}

/// Returns true if the first param is `self`. This is relevant to decide
/// whether this can be called as a method as opposed to an associated
/// function.
///
/// An associated function is a function that is associated with a type but
/// doesn't "act" on an instance. E.g. in Rust terms you can call
/// `String::from("foo")` but you can't call `String::len()`.
///
/// A method on the other hand is a function that is associated with a type
/// and does "act" on an instance. E.g. in Rust terms you can call
/// `foo.len()` but you can't call `foo.new()`.
pub fn has_self_param(&self) -> bool {
self.flags.has_self_param()
}
}

impl Function {
Expand Down
7 changes: 7 additions & 0 deletions crates/mun_hir/src/code_model/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,13 @@ impl StructData {
pub fn type_ref_map(&self) -> &TypeRefMap {
&self.type_ref_map
}

/// Returns the index of the field with the specified name.
pub fn find_field(&self, name: &Name) -> Option<LocalFieldId> {
self.fields
.iter()
.find_map(|(idx, data)| (data.name == *name).then_some(idx))
}
}

impl HasVisibility for Struct {
Expand Down
8 changes: 6 additions & 2 deletions crates/mun_hir/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

use std::sync::Arc;

use la_arena::ArenaMap;
use mun_db::Upcast;
use mun_hir_input::{FileId, PackageId, SourceDatabase};
use mun_syntax::{ast, Parse, SourceFile};
use mun_target::{abi, spec::Target};

use crate::{
code_model::{FunctionData, ImplData, StructData, TypeAliasData},
code_model::{r#struct::LocalFieldId, FunctionData, ImplData, StructData, TypeAliasData},
expr::BodySourceMap,
ids,
ids::{DefWithBodyId, FunctionId, ImplId},
ids::{DefWithBodyId, FunctionId, ImplId, VariantId},
item_tree::{self, ItemTree},
method_resolution::InherentImpls,
name_resolution::Namespace,
Expand Down Expand Up @@ -67,6 +68,9 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
#[salsa::invoke(visibility::function_visibility_query)]
fn function_visibility(&self, def: FunctionId) -> Visibility;

#[salsa::invoke(visibility::field_visibilities_query)]
fn field_visibilities(&self, variant_id: VariantId) -> Arc<ArenaMap<LocalFieldId, Visibility>>;

/// Returns the `PackageDefs` for the specified `PackageId`. The
/// `PackageDefs` contains all resolved items defined for every module
/// in the package.
Expand Down
50 changes: 49 additions & 1 deletion crates/mun_hir/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::{any::Any, fmt};
use mun_hir_input::FileId;
use mun_syntax::{ast, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange};

use crate::{code_model::StructKind, in_file::InFile, HirDatabase, IntTy, Name, Ty};
use crate::{
code_model::StructKind, ids::FunctionId, in_file::InFile, HirDatabase, IntTy, Name, Ty,
};

/// Diagnostic defines `mun_hir` API for errors and warnings.
///
Expand Down Expand Up @@ -865,3 +867,49 @@ impl Diagnostic for InvalidSelfTyImpl {
self
}
}

/// An error that is emitted if a method is called that is not visible from the
/// current scope
#[derive(Debug)]
pub struct MethodNotInScope {
pub method_call: InFile<AstPtr<ast::MethodCallExpr>>,
pub receiver_ty: Ty,
}

impl Diagnostic for MethodNotInScope {
fn message(&self) -> String {
"method not in scope for type".to_string()
}

fn source(&self) -> InFile<SyntaxNodePtr> {
self.method_call.clone().map(Into::into)
}

fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}

/// An error that is emitted if an unknown method is called
#[derive(Debug)]
pub struct MethodNotFound {
pub method_call: InFile<AstPtr<ast::MethodCallExpr>>,
pub receiver_ty: Ty,
pub method_name: Name,
pub field_with_same_name: Option<Ty>,
pub associated_function_with_same_name: Option<FunctionId>,
}

impl Diagnostic for MethodNotFound {
fn message(&self) -> String {
format!("method `{}` does not exist", &self.method_name)
}

fn source(&self) -> InFile<SyntaxNodePtr> {
self.method_call.clone().map(Into::into)
}

fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
29 changes: 29 additions & 0 deletions crates/mun_hir/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,11 @@ pub enum Expr {
callee: ExprId,
args: Vec<ExprId>,
},
MethodCall {
receiver: ExprId,
method_name: Name,
args: Vec<ExprId>,
},
Path(Path),
If {
condition: ExprId,
Expand Down Expand Up @@ -399,6 +404,12 @@ impl Expr {
f(*arg);
}
}
Expr::MethodCall { receiver, args, .. } => {
f(*receiver);
for arg in args {
f(*arg);
}
}
Expr::BinaryOp { lhs, rhs, .. } => {
f(*lhs);
f(*rhs);
Expand Down Expand Up @@ -877,6 +888,24 @@ impl<'a> ExprCollector<'a> {
};
self.alloc_expr(Expr::Call { callee, args }, syntax_ptr)
}
ast::ExprKind::MethodCallExpr(e) => {
let receiver = self.collect_expr_opt(e.expr());
let args = e
.arg_list()
.into_iter()
.flat_map(|arg_list| arg_list.args())
.map(|e| self.collect_expr(e))
.collect();
let method_name = e.name_ref().map_or_else(Name::missing, |nr| nr.as_name());
self.alloc_expr(
Expr::MethodCall {
receiver,
method_name,
args,
},
syntax_ptr,
)
}
ast::ExprKind::ArrayExpr(e) => {
let exprs = e.exprs().map(|expr| self.collect_expr(expr)).collect();
self.alloc_expr(Expr::Array(exprs), syntax_ptr)
Expand Down
6 changes: 6 additions & 0 deletions crates/mun_hir/src/expr/validator/uninitialized_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ impl ExprValidator<'_> {
self.validate_expr_access(sink, initialized_patterns, *arg, expr_side);
}
}
Expr::MethodCall { receiver, args, .. } => {
self.validate_expr_access(sink, initialized_patterns, *receiver, expr_side);
for arg in args.iter() {
self.validate_expr_access(sink, initialized_patterns, *arg, expr_side);
}
}
Expr::Path(p) => {
let resolver = resolver_for_expr(self.db.upcast(), self.body.owner(), expr);
self.validate_path_access(
Expand Down
10 changes: 9 additions & 1 deletion crates/mun_hir/src/has_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use mun_hir_input::ModuleId;
use crate::{
ids::{
AssocItemId, AssocItemLoc, FunctionId, ImplId, ItemContainerId, Lookup, StructId,
TypeAliasId,
TypeAliasId, VariantId,
},
item_tree::ItemTreeNode,
DefDatabase,
Expand Down Expand Up @@ -60,3 +60,11 @@ impl HasModule for AssocItemId {
}
}
}

impl HasModule for VariantId {
fn module(&self, db: &dyn DefDatabase) -> ModuleId {
match self {
VariantId::StructId(it) => it.module(db),
}
}
}
10 changes: 10 additions & 0 deletions crates/mun_hir/src/ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,13 @@ impl From<FunctionId> for DefWithBodyId {
DefWithBodyId::FunctionId(id)
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VariantId {
StructId(StructId),
}
impl From<StructId> for VariantId {
fn from(value: StructId) -> Self {
VariantId::StructId(value)
}
}
46 changes: 46 additions & 0 deletions crates/mun_hir/src/method_resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ pub struct MethodResolutionCtx<'db> {

/// Filter based on visibility from this module
visible_from: Option<ModuleId>,

/// Whether to look up methods or associated functions.
association_mode: Option<AssociationMode>,
}

enum IsValidCandidate {
Expand All @@ -249,13 +252,41 @@ enum IsValidCandidate {
NotVisible,
}

/// Whether to look up methods or associated functions.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum AssociationMode {
/// Method call e.g. a method that takes self as the first argument.
WithSelf,

/// Associated function e.g. a method that does not take self as the first
/// argument.
WithoutSelf,
}

impl<'db> MethodResolutionCtx<'db> {
pub fn new(db: &'db dyn HirDatabase, ty: Ty) -> Self {
Self {
db,
ty,
name: None,
visible_from: None,
association_mode: None,
}
}

/// Filter methods based on the specified lookup mode.
pub fn with_association(self, association: AssociationMode) -> Self {
Self {
association_mode: Some(association),
..self
}
}

/// Filter methods based on the specified lookup mode.
pub fn with_association_opt(self, association: Option<AssociationMode>) -> Self {
Self {
association_mode: association,
..self
}
}

Expand Down Expand Up @@ -351,6 +382,16 @@ impl<'db> MethodResolutionCtx<'db> {
}
}

// Check the association mode
if let Some(association_mode) = self.association_mode {
if matches!(
(association_mode, data.has_self_param()),
(AssociationMode::WithSelf, false) | (AssociationMode::WithoutSelf, true)
) {
return IsValidCandidate::No;
}
}

// Check if the function is visible from the selected module
if let Some(visible_from) = self.visible_from {
if !self
Expand All @@ -376,9 +417,11 @@ pub(crate) fn lookup_method(
ty: &Ty,
visible_from_module: ModuleId,
name: &Name,
association_mode: Option<AssociationMode>,
) -> Result<FunctionId, Option<FunctionId>> {
let mut not_visible = None;
MethodResolutionCtx::new(db, ty.clone())
.with_association_opt(association_mode)
.visible_from(visible_from_module)
.with_name(name.clone())
.collect(|item, visible| match item {
Expand Down Expand Up @@ -561,6 +604,7 @@ mod tests {
&fixture.foo_ty,
fixture.root_module.id,
&Name::new("bar"),
None,
)
.is_ok());
}
Expand All @@ -573,6 +617,7 @@ mod tests {
&fixture.foo_ty,
fixture.root_module.id,
&Name::new("not_found"),
None,
)
.unwrap_err()
.is_none());
Expand All @@ -586,6 +631,7 @@ mod tests {
&fixture.foo_ty,
fixture.root_module.id,
&Name::new("baz"),
None,
)
.unwrap_err()
.is_some());
Expand Down
5 changes: 5 additions & 0 deletions crates/mun_hir/src/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ impl Name {
Repr::TupleField(_) => None,
}
}

/// Returns true if this name represents a missing name.
pub fn is_missing(&self) -> bool {
self == &Self::missing()
}
}

pub(crate) trait AsName {
Expand Down
2 changes: 1 addition & 1 deletion crates/mun_hir/src/package_defs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ fn use_() {
r#"
//- /bar.mun
use package::Foo;
pub struct Bar(Foo);
pub struct Bar(pub Foo);
//- /mod.mun
pub use foo::Foo; // Re-export a child's definition
Expand Down
Loading

0 comments on commit 3d70e0e

Please sign in to comment.