Skip to content
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

feat: implement method calls #577

Merged
merged 11 commits into from
Jan 7, 2025
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 @@
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")

Check warning on line 238 in crates/mun_codegen/src/ir/body.rs

View check run for this annotation

Codecov / codecov/patch

crates/mun_codegen/src/ir/body.rs#L238

Added line #L238 was not covered by tests
}
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 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 @@
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
}

Check warning on line 890 in crates/mun_hir/src/diagnostics.rs

View check run for this annotation

Codecov / codecov/patch

crates/mun_hir/src/diagnostics.rs#L888-L890

Added lines #L888 - L890 were not covered by tests
}

/// 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
}

Check warning on line 914 in crates/mun_hir/src/diagnostics.rs

View check run for this annotation

Codecov / codecov/patch

crates/mun_hir/src/diagnostics.rs#L912-L914

Added lines #L912 - L914 were not covered by tests
}
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 @@
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 @@
f(*arg);
}
}
Expr::MethodCall { receiver, args, .. } => {
f(*receiver);
for arg in args {
f(*arg);
}

Check warning on line 411 in crates/mun_hir/src/expr.rs

View check run for this annotation

Codecov / codecov/patch

crates/mun_hir/src/expr.rs#L410-L411

Added lines #L410 - L411 were not covered by tests
}
Expr::BinaryOp { lhs, rhs, .. } => {
f(*lhs);
f(*rhs);
Expand Down Expand Up @@ -877,6 +888,24 @@
};
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 @@
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);
}

Check warning on line 60 in crates/mun_hir/src/expr/validator/uninitialized_access.rs

View check run for this annotation

Codecov / codecov/patch

crates/mun_hir/src/expr/validator/uninitialized_access.rs#L59-L60

Added lines #L59 - L60 were not covered by tests
}
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 @@

/// 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 @@
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
}
}

Check warning on line 283 in crates/mun_hir/src/method_resolution.rs

View check run for this annotation

Codecov / codecov/patch

crates/mun_hir/src/method_resolution.rs#L278-L283

Added lines #L278 - L283 were not covered by tests

/// Filter methods based on the specified lookup mode.
pub fn with_association_opt(self, association: Option<AssociationMode>) -> Self {
Wodann marked this conversation as resolved.
Show resolved Hide resolved
Self {
association_mode: association,
..self
}
}

Expand Down Expand Up @@ -351,6 +382,16 @@
}
}

// 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 @@
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 @@
&fixture.foo_ty,
fixture.root_module.id,
&Name::new("bar"),
None,
)
.is_ok());
}
Expand All @@ -573,6 +617,7 @@
&fixture.foo_ty,
fixture.root_module.id,
&Name::new("not_found"),
None,
)
.unwrap_err()
.is_none());
Expand All @@ -586,6 +631,7 @@
&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
Loading