diff --git a/crates/mun_codegen/src/ir/body.rs b/crates/mun_codegen/src/ir/body.rs index 04c063e3..f811af2d 100644 --- a/crates/mun_codegen/src/ir/body.rs +++ b/crates/mun_codegen/src/ir/body.rs @@ -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, diff --git a/crates/mun_hir/src/code_model/function.rs b/crates/mun_hir/src/code_model/function.rs index 8035f1be..fb7d2042 100644 --- a/crates/mun_hir/src/code_model/function.rs +++ b/crates/mun_hir/src/code_model/function.rs @@ -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 { diff --git a/crates/mun_hir/src/code_model/struct.rs b/crates/mun_hir/src/code_model/struct.rs index eb34466d..b8e867df 100644 --- a/crates/mun_hir/src/code_model/struct.rs +++ b/crates/mun_hir/src/code_model/struct.rs @@ -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 { + self.fields + .iter() + .find_map(|(idx, data)| (data.name == *name).then_some(idx)) + } } impl HasVisibility for Struct { diff --git a/crates/mun_hir/src/db.rs b/crates/mun_hir/src/db.rs index 9812d002..36309476 100644 --- a/crates/mun_hir/src/db.rs +++ b/crates/mun_hir/src/db.rs @@ -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, @@ -67,6 +68,9 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast { #[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>; + /// Returns the `PackageDefs` for the specified `PackageId`. The /// `PackageDefs` contains all resolved items defined for every module /// in the package. diff --git a/crates/mun_hir/src/diagnostics.rs b/crates/mun_hir/src/diagnostics.rs index 2de9fc79..f2504bc9 100644 --- a/crates/mun_hir/src/diagnostics.rs +++ b/crates/mun_hir/src/diagnostics.rs @@ -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. /// @@ -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>, + pub receiver_ty: Ty, +} + +impl Diagnostic for MethodNotInScope { + fn message(&self) -> String { + "method not in scope for type".to_string() + } + + fn source(&self) -> InFile { + 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>, + pub receiver_ty: Ty, + pub method_name: Name, + pub field_with_same_name: Option, + pub associated_function_with_same_name: Option, +} + +impl Diagnostic for MethodNotFound { + fn message(&self) -> String { + format!("method `{}` does not exist", &self.method_name) + } + + fn source(&self) -> InFile { + self.method_call.clone().map(Into::into) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} diff --git a/crates/mun_hir/src/expr.rs b/crates/mun_hir/src/expr.rs index bf2ae654..210c7386 100644 --- a/crates/mun_hir/src/expr.rs +++ b/crates/mun_hir/src/expr.rs @@ -285,6 +285,11 @@ pub enum Expr { callee: ExprId, args: Vec, }, + MethodCall { + receiver: ExprId, + method_name: Name, + args: Vec, + }, Path(Path), If { condition: ExprId, @@ -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); @@ -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) diff --git a/crates/mun_hir/src/expr/validator/uninitialized_access.rs b/crates/mun_hir/src/expr/validator/uninitialized_access.rs index 2074df59..ee014d51 100644 --- a/crates/mun_hir/src/expr/validator/uninitialized_access.rs +++ b/crates/mun_hir/src/expr/validator/uninitialized_access.rs @@ -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( diff --git a/crates/mun_hir/src/has_module.rs b/crates/mun_hir/src/has_module.rs index a27042d4..e0abbe9b 100644 --- a/crates/mun_hir/src/has_module.rs +++ b/crates/mun_hir/src/has_module.rs @@ -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, @@ -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), + } + } +} diff --git a/crates/mun_hir/src/ids.rs b/crates/mun_hir/src/ids.rs index d5863368..62c2adac 100644 --- a/crates/mun_hir/src/ids.rs +++ b/crates/mun_hir/src/ids.rs @@ -200,3 +200,13 @@ impl From for DefWithBodyId { DefWithBodyId::FunctionId(id) } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum VariantId { + StructId(StructId), +} +impl From for VariantId { + fn from(value: StructId) -> Self { + VariantId::StructId(value) + } +} diff --git a/crates/mun_hir/src/method_resolution.rs b/crates/mun_hir/src/method_resolution.rs index 17456782..1120ea49 100644 --- a/crates/mun_hir/src/method_resolution.rs +++ b/crates/mun_hir/src/method_resolution.rs @@ -241,6 +241,9 @@ pub struct MethodResolutionCtx<'db> { /// Filter based on visibility from this module visible_from: Option, + + /// Whether to look up methods or associated functions. + association_mode: Option, } enum IsValidCandidate { @@ -249,6 +252,17 @@ 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 { @@ -256,6 +270,23 @@ impl<'db> MethodResolutionCtx<'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) -> Self { + Self { + association_mode: association, + ..self } } @@ -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 @@ -376,9 +417,11 @@ pub(crate) fn lookup_method( ty: &Ty, visible_from_module: ModuleId, name: &Name, + association_mode: Option, ) -> Result> { 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 { @@ -561,6 +604,7 @@ mod tests { &fixture.foo_ty, fixture.root_module.id, &Name::new("bar"), + None, ) .is_ok()); } @@ -573,6 +617,7 @@ mod tests { &fixture.foo_ty, fixture.root_module.id, &Name::new("not_found"), + None, ) .unwrap_err() .is_none()); @@ -586,6 +631,7 @@ mod tests { &fixture.foo_ty, fixture.root_module.id, &Name::new("baz"), + None, ) .unwrap_err() .is_some()); diff --git a/crates/mun_hir/src/name.rs b/crates/mun_hir/src/name.rs index 04073311..0c49d896 100644 --- a/crates/mun_hir/src/name.rs +++ b/crates/mun_hir/src/name.rs @@ -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 { diff --git a/crates/mun_hir/src/package_defs/tests.rs b/crates/mun_hir/src/package_defs/tests.rs index 540bba30..dd40d7e8 100644 --- a/crates/mun_hir/src/package_defs/tests.rs +++ b/crates/mun_hir/src/package_defs/tests.rs @@ -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 diff --git a/crates/mun_hir/src/ty/infer.rs b/crates/mun_hir/src/ty/infer.rs index b871fdad..2d0a8968 100644 --- a/crates/mun_hir/src/ty/infer.rs +++ b/crates/mun_hir/src/ty/infer.rs @@ -1,8 +1,8 @@ -use std::{ops::Index, sync::Arc}; +use std::{convert::identity, ops::Index, sync::Arc}; use la_arena::ArenaMap; use mun_hir_input::ModuleId; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ code_model::{Struct, StructKind}, @@ -16,7 +16,7 @@ use crate::{ op, Ty, TypableDef, }, type_ref::LocalTypeRefId, - BinaryOp, Function, HirDatabase, Name, Path, + BinaryOp, CallableDef, Function, HirDatabase, Name, Path, }; mod place_expr; @@ -26,8 +26,8 @@ mod unify; use crate::{ expr::{LiteralFloat, LiteralFloatKind, LiteralInt, LiteralIntKind}, has_module::HasModule, - ids::DefWithBodyId, - method_resolution::lookup_method, + ids::{DefWithBodyId, FunctionId}, + method_resolution::{lookup_method, AssociationMode}, resolve::{resolver_for_expr, HasResolver, ResolveValueResult}, ty::{ primitives::{FloatTy, IntTy}, @@ -59,6 +59,9 @@ pub struct InferenceResult { pub(crate) type_of_pat: ArenaMap, pub(crate) diagnostics: Vec, + /// For each method call expression, records the function it resolves to. + pub(crate) method_resolutions: FxHashMap, + /// Interned Unknown to return references to. standard_types: InternedStandardTypes, } @@ -82,6 +85,12 @@ impl Index for InferenceResult { } impl InferenceResult { + /// Find the method resolution for the given expression. Returns `None` if + /// the expression is not a method call. + pub fn method_resolution(&self, expr: ExprId) -> Option { + self.method_resolutions.get(&expr).cloned() + } + /// Adds all the `InferenceDiagnostic`s of the result to the /// `DiagnosticSink`. pub(crate) fn add_diagnostics( @@ -168,6 +177,9 @@ struct InferenceResultBuilder<'a> { /// The return type of the function being inferred. return_ty: Ty, + + /// Stores the resolution of method calls + method_resolution: FxHashMap, } impl<'a> InferenceResultBuilder<'a> { @@ -184,6 +196,7 @@ impl<'a> InferenceResultBuilder<'a> { body, resolver, return_ty: TyKind::Unknown.intern(), // set in collect_fn_signature + method_resolution: FxHashMap::default(), } } @@ -368,6 +381,11 @@ impl InferenceResultBuilder<'_> { }, Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected), Expr::Call { callee: call, args } => self.infer_call(tgt_expr, *call, args, expected), + Expr::MethodCall { + receiver, + args, + method_name, + } => self.infer_method_call(tgt_expr, *receiver, args, method_name, expected), Expr::Literal(lit) => match lit { Literal::String(_) => TyKind::Unknown.intern(), Literal::Bool(_) => TyKind::Bool.intern(), @@ -446,30 +464,24 @@ impl InferenceResultBuilder<'_> { } Expr::Field { expr, name } => { let receiver_ty = self.infer_expr(*expr, &Expectation::none()); - #[allow(clippy::single_match_else)] - match receiver_ty.interned() { - TyKind::Struct(s) => { - match s.field(self.db, name).map(|field| field.ty(self.db)) { - Some(field_ty) => field_ty, - None => { - self.diagnostics - .push(InferenceDiagnostic::AccessUnknownField { - id: tgt_expr, - receiver_ty, - name: name.clone(), - }); - - error_type() - } - } + if let Some((field_ty, is_visible)) = self.lookup_field(receiver_ty.clone(), name) { + if !is_visible { + self.diagnostics + .push(InferenceDiagnostic::AccessPrivateField { + id: tgt_expr, + receiver_ty, + name: name.clone(), + }); } - _ => { - self.diagnostics.push(InferenceDiagnostic::NoFields { - id: *expr, - found: receiver_ty, + field_ty + } else { + self.diagnostics + .push(InferenceDiagnostic::AccessUnknownField { + id: tgt_expr, + receiver_ty, + name: name.clone(), }); - error_type() - } + error_type() } } Expr::UnaryOp { expr, op } => { @@ -579,6 +591,126 @@ impl InferenceResultBuilder<'_> { } } + fn lookup_field(&mut self, receiver_ty: Ty, field_name: &Name) -> Option<(Ty, bool)> { + match receiver_ty.interned() { + TyKind::Tuple(_, subs) => { + let idx = field_name.as_tuple_index()?; + let field_ty = subs.interned().get(idx)?.clone(); + Some((field_ty, true)) + } + TyKind::Struct(s) => { + let struct_data = self.db.struct_data(s.id); + let local_field_idx = struct_data.find_field(field_name)?; + let field_types = self.db.lower_struct(*s); + let field_visibilities = self.db.field_visibilities(s.id.into()); + let field_data = &struct_data.fields[local_field_idx]; + Some(( + field_types[field_data.type_ref].clone(), + field_visibilities[local_field_idx].is_visible_from(self.db, self.module()), + )) + } + _ => None, + } + } + + fn infer_method_call( + &mut self, + tgt_expr: ExprId, + receiver: ExprId, + args: &[ExprId], + method_name: &Name, + _expected: &Expectation, + ) -> Ty { + let receiver_ty = self.infer_expr(receiver, &Expectation::none()); + + // If the method name is missing from the AST we simply return an error type + // since an error would have already been emitted by the AST generation. + if method_name.is_missing() { + return error_type(); + } + + // Resolve the method on the receiver type. + let resolved_function = match lookup_method( + self.db, + &receiver_ty, + self.module(), + method_name, + Some(AssociationMode::WithSelf), + ) { + Ok(resolved) => resolved, + Err(Some(resolved)) => { + self.diagnostics + .push(InferenceDiagnostic::MethodNotInScope { + id: tgt_expr, + receiver_ty, + }); + resolved + } + Err(None) => { + // Check if there is a field with the same name. + let field_with_same_name = self + .lookup_field(receiver_ty.clone(), method_name) + .map(|(field_ty, _is_visible)| field_ty); + + // Check if there is an associated function with the same name. + let associated_function_with_same_name = lookup_method( + self.db, + &receiver_ty, + self.module(), + method_name, + Some(AssociationMode::WithoutSelf), + ) + .map_or_else(identity, Some); + + // Method could not be resolved, emit an error. + self.diagnostics.push(InferenceDiagnostic::MethodNotFound { + id: tgt_expr, + method_name: method_name.clone(), + receiver_ty, + field_with_same_name, + associated_function_with_same_name, + }); + return error_type(); + } + }; + + // Store the method resolution. + self.method_resolution.insert(tgt_expr, resolved_function); + + self.infer_call_arguments_and_return( + tgt_expr, + args, + Function::from(resolved_function).into(), + ) + } + + fn infer_call_arguments_and_return( + &mut self, + tgt_expr: ExprId, + args: &[ExprId], + callable: CallableDef, + ) -> Ty { + // Retrieve the function signature. + let signature = self.db.callable_sig(callable); + + // Verify that the number of arguments matches + if signature.params().len() != args.len() { + self.diagnostics + .push(InferenceDiagnostic::ParameterCountMismatch { + id: tgt_expr, + found: args.len(), + expected: signature.params().len(), + }); + } + + // Verify the argument types + for (&arg, param_ty) in args.iter().zip(signature.params().iter()) { + self.infer_expr_coerce(arg, &Expectation::has_type(param_ty.clone())); + } + + signature.ret().clone() + } + /// Inferences the type of a call expression. fn infer_call( &mut self, @@ -752,7 +884,13 @@ impl InferenceResultBuilder<'_> { }; // Resolve the value. - let function_id = match lookup_method(self.db, &root_ty, self.module(), name) { + let function_id = match lookup_method( + self.db, + &root_ty, + self.module(), + name, + Some(AssociationMode::WithoutSelf), + ) { Ok(value) => value, Err(Some(value)) => { self.diagnostics @@ -883,7 +1021,6 @@ impl InferenceResultBuilder<'_> { *ty = resolved; } InferenceResult { - // method_resolutions: self.method_resolutions, // field_resolutions: self.field_resolutions, // variant_resolutions: self.variant_resolutions, // assoc_resolutions: self.assoc_resolutions, @@ -891,6 +1028,7 @@ impl InferenceResultBuilder<'_> { type_of_pat: pat_types, diagnostics: self.diagnostics, standard_types: InternedStandardTypes::default(), + method_resolutions: self.method_resolution, } } @@ -1117,10 +1255,12 @@ mod diagnostics { diagnostics::{ AccessUnknownField, BreakOutsideLoop, BreakWithValueOutsideLoop, CannotApplyBinaryOp, CannotApplyUnaryOp, CyclicType, DiagnosticSink, ExpectedFunction, FieldCountMismatch, - IncompatibleBranch, InvalidLhs, LiteralOutOfRange, MismatchedStructLit, MismatchedType, - MissingElseBranch, MissingFields, NoFields, NoSuchField, ParameterCountMismatch, - PrivateAccess, ReturnMissingExpression, UnresolvedType, UnresolvedValue, + IncompatibleBranch, InvalidLhs, LiteralOutOfRange, MethodNotFound, MethodNotInScope, + MismatchedStructLit, MismatchedType, MissingElseBranch, MissingFields, NoFields, + NoSuchField, ParameterCountMismatch, PrivateAccess, ReturnMissingExpression, + UnresolvedType, UnresolvedValue, }, + ids::FunctionId, ty::infer::ExprOrPatId, type_ref::LocalTypeRefId, ExprId, Function, HirDatabase, IntTy, Name, Ty, @@ -1187,6 +1327,11 @@ mod diagnostics { receiver_ty: Ty, name: Name, }, + AccessPrivateField { + id: ExprId, + receiver_ty: Ty, + name: Name, + }, FieldCountMismatch { id: ExprId, found: usize, @@ -1220,6 +1365,17 @@ mod diagnostics { PathIsPrivate { id: ExprId, }, + MethodNotInScope { + id: ExprId, + receiver_ty: Ty, + }, + MethodNotFound { + id: ExprId, + method_name: Name, + receiver_ty: Ty, + field_with_same_name: Option, + associated_function_with_same_name: Option, + }, } impl InferenceDiagnostic { @@ -1526,6 +1682,56 @@ mod diagnostics { int_ty: *literal_ty, }); } + InferenceDiagnostic::MethodNotInScope { id, receiver_ty } => { + let method_call = body + .expr_syntax(*id) + .expect("expression missing from source map") + .map(|expr_src| { + expr_src + .left() + .expect("could not retrieve expression from ExprSource") + .cast() + .expect("could not cast expression to method call") + }); + sink.push(MethodNotInScope { + method_call, + receiver_ty: receiver_ty.clone(), + }); + } + InferenceDiagnostic::MethodNotFound { + id, + receiver_ty, + method_name, + field_with_same_name, + associated_function_with_same_name, + } => { + let method_call = body + .expr_syntax(*id) + .expect("expression missing from source map") + .map(|expr_src| { + expr_src + .left() + .expect("could not retrieve expression from ExprSource") + .cast() + .expect("could not cast expression to method call") + }); + sink.push(MethodNotFound { + method_call, + receiver_ty: receiver_ty.clone(), + method_name: method_name.clone(), + field_with_same_name: field_with_same_name.clone(), + associated_function_with_same_name: *associated_function_with_same_name, + }); + } + InferenceDiagnostic::AccessPrivateField { id, .. } => { + // TODO: Add dedicated diagnostic for this + let expr = body + .expr_syntax(*id) + .unwrap() + .value + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); + sink.push(PrivateAccess { file, expr }); + } } } } diff --git a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_access_private_field.snap b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_access_private_field.snap new file mode 100644 index 00000000..4addf965 --- /dev/null +++ b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_access_private_field.snap @@ -0,0 +1,19 @@ +--- +source: crates/mun_hir/src/ty/tests.rs +expression: "infer(r#\"\n //- /foo.mun\n pub struct Foo {\n private: i32\n pub public: i32,\n }\n\n impl Foo {\n pub fn new() -> Self {\n Self { private: 0, public: 0 }\n }\n }\n\n\n //- /mod.mun\n fn main() {\n let foo = foo::Foo::new();\n let a = foo.private;\n let b = foo.public;\n }\n \"#)" +--- +55..66: access of private type +10..93 '{ ...lic; }': () +20..23 'foo': Foo +26..39 'foo::Foo::new': function new() -> Foo +26..41 'foo::Foo::new()': Foo +51..52 'a': i32 +55..58 'foo': Foo +55..66 'foo.private': i32 +76..77 'b': i32 +80..83 'foo': Foo +80..90 'foo.public': i32 +94..140 '{ ... }': Foo +104..134 'Self {...c: 0 }': Foo +120..121 '0': i32 +131..132 '0': i32 diff --git a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_call_assoc_as_method.snap b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_call_assoc_as_method.snap new file mode 100644 index 00000000..d284766b --- /dev/null +++ b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_call_assoc_as_method.snap @@ -0,0 +1,11 @@ +--- +source: crates/mun_hir/src/ty/tests.rs +expression: "infer(r#\"\n pub struct Foo {}\n\n impl Foo {\n fn assoc() {}\n }\n\n fn main() {\n let a = Foo {};\n a.assoc();\n }\n \"#)" +--- +87..96: method `assoc` does not exist +61..99 '{ ...c(); }': () +71..72 'a': Foo +75..81 'Foo {}': Foo +87..88 'a': Foo +87..96 'a.assoc()': {unknown} +45..47 '{}': () diff --git a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_call_method.snap b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_call_method.snap new file mode 100644 index 00000000..75d37429 --- /dev/null +++ b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_call_method.snap @@ -0,0 +1,12 @@ +--- +source: crates/mun_hir/src/ty/tests.rs +expression: "infer(r#\"\n struct Foo {}\n\n impl Foo {\n fn with_self(self) -> Self {\n self\n }\n }\n\n fn main() {\n let a = Foo {};\n a.with_self();\n }\n \"#)" +--- +91..133 '{ ...f(); }': () +101..102 'a': Foo +105..111 'Foo {}': Foo +117..118 'a': Foo +117..130 'a.with_self()': Foo +43..47 'self': Foo +57..77 '{ ... }': Foo +67..71 'self': Foo diff --git a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_call_method_not_in_scope.snap b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_call_method_not_in_scope.snap new file mode 100644 index 00000000..090e5504 --- /dev/null +++ b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_call_method_not_in_scope.snap @@ -0,0 +1,13 @@ +--- +source: crates/mun_hir/src/ty/tests.rs +expression: "infer(r#\"\n //- /foo.mun\n pub struct Foo {}\n\n impl Foo {\n fn with_self(self) -> Self {\n self\n }\n }\n\n //- /mod.mun\n fn main() {\n let a = foo::Foo {};\n a.with_self();\n }\n \"#)" +--- +41..54: method not in scope for type +10..57 '{ ...f(); }': () +20..21 'a': Foo +24..35 'foo::Foo {}': Foo +41..42 'a': Foo +41..54 'a.with_self()': Foo +47..51 'self': Foo +61..81 '{ ... }': Foo +71..75 'self': Foo diff --git a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__struct_field_visibility.snap b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__struct_field_visibility.snap new file mode 100644 index 00000000..6727b769 --- /dev/null +++ b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__struct_field_visibility.snap @@ -0,0 +1,20 @@ +--- +source: crates/mun_hir/src/ty/tests.rs +expression: "infer(r#\"\n //- /foo.mun\n pub struct Foo(pub i32, i32)\n\n impl Foo {\n pub fn new() -> Self {\n Self(1, 2)\n }\n }\n\n //- /mod.mun\n fn main() {\n let foo = foo::Foo::new();\n let a = foo.0;\n let b = foo.1;\n }\"#)" +--- +74..79: access of private type +10..82 '{ ...o.1; }': () +20..23 'foo': Foo +26..39 'foo::Foo::new': function new() -> Foo +26..41 'foo::Foo::new()': Foo +51..52 'a': i32 +55..58 'foo': Foo +55..60 'foo.0': i32 +70..71 'b': i32 +74..77 'foo': Foo +74..79 'foo.1': i32 +66..92 '{ ... }': Foo +76..80 'Self': Foo +76..86 'Self(1, 2)': Foo +81..82 '1': i32 +84..85 '2': i32 diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs index c5012bc0..15ae39cc 100644 --- a/crates/mun_hir/src/ty/tests.rs +++ b/crates/mun_hir/src/ty/tests.rs @@ -658,6 +658,93 @@ fn infer_return() { "###); } +#[test] +fn infer_call_method() { + insta::assert_snapshot!(infer( + r#" + struct Foo {} + + impl Foo { + fn with_self(self) -> Self { + self + } + } + + fn main() { + let a = Foo {}; + a.with_self(); + } + "# + )); +} + +#[test] +fn infer_call_method_not_in_scope() { + insta::assert_snapshot!(infer( + r#" + //- /foo.mun + pub struct Foo {} + + impl Foo { + fn with_self(self) -> Self { + self + } + } + + //- /mod.mun + fn main() { + let a = foo::Foo {}; + a.with_self(); + } + "# + )); +} + +#[test] +fn infer_access_private_field() { + insta::assert_snapshot!(infer( + r#" + //- /foo.mun + pub struct Foo { + private: i32 + pub public: i32, + } + + impl Foo { + pub fn new() -> Self { + Self { private: 0, public: 0 } + } + } + + + //- /mod.mun + fn main() { + let foo = foo::Foo::new(); + let a = foo.private; + let b = foo.public; + } + "# + )); +} + +#[test] +fn infer_call_assoc_as_method() { + insta::assert_snapshot!(infer( + r#" + pub struct Foo {} + + impl Foo { + fn assoc() {} + } + + fn main() { + let a = Foo {}; + a.assoc(); + } + "# + )); +} + #[test] fn infer_self_param() { insta::assert_snapshot!(infer( @@ -1213,6 +1300,28 @@ fn struct_lit() { "###); } +#[test] +fn struct_field_visibility() { + insta::assert_snapshot!(infer( + r#" + //- /foo.mun + pub struct Foo(pub i32, i32) + + impl Foo { + pub fn new() -> Self { + Self(1, 2) + } + } + + //- /mod.mun + fn main() { + let foo = foo::Foo::new(); + let a = foo.0; + let b = foo.1; + }"# + )); +} + #[test] fn struct_field_index() { insta::assert_snapshot!(infer( @@ -1236,15 +1345,15 @@ fn struct_field_index() { let baz = Baz; baz.a // error: attempted to access a non-existent field in a struct. let f = 1.0 - f.0; // error: attempted to access a field on a primitive type. + f.0; // error: attempted to access a non-existent field in a struct. } "#), @r###" 146..151: attempted to access a non-existent field in a struct. 268..273: attempted to access a non-existent field in a struct. 361..366: attempted to access a non-existent field in a struct. - 451..452: attempted to access a field on a primitive type. - 83..516 '{ ...ype. }': () + 451..454: attempted to access a non-existent field in a struct. + 83..521 '{ ...uct. }': () 93..96 'foo': Foo 99..120 'Foo { ...b: 4 }': Foo 108..112 '1.23': f64 diff --git a/crates/mun_hir/src/visibility.rs b/crates/mun_hir/src/visibility.rs index b19fbfd4..bd625dea 100644 --- a/crates/mun_hir/src/visibility.rs +++ b/crates/mun_hir/src/visibility.rs @@ -1,9 +1,16 @@ -use std::iter::successors; +use std::{iter::successors, sync::Arc}; +use la_arena::ArenaMap; use mun_hir_input::{ModuleId, ModuleTree, PackageModuleId}; use mun_syntax::ast; -use crate::{ids::FunctionId, resolve::HasResolver, DefDatabase, HirDatabase, Module, Resolver}; +use crate::{ + code_model::r#struct::LocalFieldId, + has_module::HasModule, + ids::{FunctionId, VariantId}, + resolve::HasResolver, + DefDatabase, HirDatabase, Module, Resolver, +}; /// Visibility of an item, not yet resolved to an actual module. #[derive(Debug, Clone, PartialEq, Eq)] @@ -148,3 +155,21 @@ pub(crate) fn function_visibility_query(db: &dyn DefDatabase, def: FunctionId) - let resolver = def.resolver(db); db.fn_data(def).visibility().resolve(db, &resolver) } + +/// Resolve visibility of all fields of a variant. +pub(crate) fn field_visibilities_query( + db: &dyn DefDatabase, + variant_id: VariantId, +) -> Arc> { + let mut res = ArenaMap::default(); + let resolver = variant_id.module(db).resolver(db); + match variant_id { + VariantId::StructId(st) => { + let struct_data = db.struct_data(st); + for (field_idx, field_data) in struct_data.fields.iter() { + res.insert(field_idx, field_data.visibility.resolve(db, &resolver)); + } + } + }; + Arc::new(res) +} diff --git a/crates/mun_syntax/src/ast/generated.rs b/crates/mun_syntax/src/ast/generated.rs index 0bb9f676..cfc462fd 100644 --- a/crates/mun_syntax/src/ast/generated.rs +++ b/crates/mun_syntax/src/ast/generated.rs @@ -364,6 +364,7 @@ impl AstNode for Expr { | BIN_EXPR | PAREN_EXPR | CALL_EXPR + | METHOD_CALL_EXPR | FIELD_EXPR | IF_EXPR | LOOP_EXPR @@ -395,6 +396,7 @@ pub enum ExprKind { BinExpr(BinExpr), ParenExpr(ParenExpr), CallExpr(CallExpr), + MethodCallExpr(MethodCallExpr), FieldExpr(FieldExpr), IfExpr(IfExpr), LoopExpr(LoopExpr), @@ -436,6 +438,11 @@ impl From for Expr { Expr { syntax: n.syntax } } } +impl From for Expr { + fn from(n: MethodCallExpr) -> Expr { + Expr { syntax: n.syntax } + } +} impl From for Expr { fn from(n: FieldExpr) -> Expr { Expr { syntax: n.syntax } @@ -496,6 +503,9 @@ impl Expr { BIN_EXPR => ExprKind::BinExpr(BinExpr::cast(self.syntax.clone()).unwrap()), PAREN_EXPR => ExprKind::ParenExpr(ParenExpr::cast(self.syntax.clone()).unwrap()), CALL_EXPR => ExprKind::CallExpr(CallExpr::cast(self.syntax.clone()).unwrap()), + METHOD_CALL_EXPR => { + ExprKind::MethodCallExpr(MethodCallExpr::cast(self.syntax.clone()).unwrap()) + } FIELD_EXPR => ExprKind::FieldExpr(FieldExpr::cast(self.syntax.clone()).unwrap()), IF_EXPR => ExprKind::IfExpr(IfExpr::cast(self.syntax.clone()).unwrap()), LOOP_EXPR => ExprKind::LoopExpr(LoopExpr::cast(self.syntax.clone()).unwrap()), @@ -805,6 +815,39 @@ impl AstNode for MemoryTypeSpecifier { } impl MemoryTypeSpecifier {} +// MethodCallExpr + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct MethodCallExpr { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for MethodCallExpr { + fn can_cast(kind: SyntaxKind) -> bool { + matches!(kind, METHOD_CALL_EXPR) + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(MethodCallExpr { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl ast::ArgListOwner for MethodCallExpr {} +impl MethodCallExpr { + pub fn expr(&self) -> Option { + super::child_opt(self) + } + + pub fn name_ref(&self) -> Option { + super::child_opt(self) + } +} + // ModuleItem #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/mun_syntax/src/grammar.ron b/crates/mun_syntax/src/grammar.ron index 4e5fa2c5..6baed487 100644 --- a/crates/mun_syntax/src/grammar.ron +++ b/crates/mun_syntax/src/grammar.ron @@ -152,6 +152,7 @@ Grammar( "BIN_EXPR", "PAREN_EXPR", "CALL_EXPR", + "METHOD_CALL_EXPR", "FIELD_EXPR", "IF_EXPR", "INDEX_EXPR", @@ -295,6 +296,10 @@ Grammar( traits: ["ArgListOwner"], options: [ "Expr" ], ), + "MethodCallExpr": ( + traits: ["ArgListOwner"], + options: [ "Expr", "NameRef" ], + ), "IndexExpr": ( ), "FieldExpr": ( @@ -323,6 +328,7 @@ Grammar( "BinExpr", "ParenExpr", "CallExpr", + "MethodCallExpr", "FieldExpr", "IfExpr", "LoopExpr", diff --git a/crates/mun_syntax/src/parsing/grammar.rs b/crates/mun_syntax/src/parsing/grammar.rs index 9dfb5d5c..61d19133 100644 --- a/crates/mun_syntax/src/parsing/grammar.rs +++ b/crates/mun_syntax/src/parsing/grammar.rs @@ -23,6 +23,8 @@ use super::{ }, }; +const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub]]); + #[derive(Clone, Copy, PartialEq, Eq)] enum BlockLike { Block, diff --git a/crates/mun_syntax/src/parsing/grammar/adt.rs b/crates/mun_syntax/src/parsing/grammar/adt.rs index 8edfc9ad..645eab89 100644 --- a/crates/mun_syntax/src/parsing/grammar/adt.rs +++ b/crates/mun_syntax/src/parsing/grammar/adt.rs @@ -1,8 +1,14 @@ use super::{ declarations, error_block, name, name_recovery, opt_visibility, types, Marker, Parser, EOF, GC_KW, IDENT, MEMORY_TYPE_SPECIFIER, RECORD_FIELD_DEF, RECORD_FIELD_DEF_LIST, STRUCT_DEF, - TUPLE_FIELD_DEF, TUPLE_FIELD_DEF_LIST, TYPE_ALIAS_DEF, VALUE_KW, + TUPLE_FIELD_DEF, TUPLE_FIELD_DEF_LIST, TYPE_ALIAS_DEF, VALUE_KW, VISIBILITY_FIRST, }; +use crate::{ + parsing::{grammar::types::TYPE_FIRST, token_set::TokenSet}, + SyntaxKind::ERROR, +}; + +const TUPLE_FIELD_FIRST: TokenSet = types::TYPE_FIRST.union(VISIBILITY_FIRST); pub(super) fn struct_def(p: &mut Parser<'_>, m: Marker) { assert!(p.at(T![struct])); @@ -78,9 +84,19 @@ pub(super) fn tuple_field_def_list(p: &mut Parser<'_>) { p.bump(T!['(']); while !p.at(T![')']) && !p.at(EOF) { let m = p.start(); - if !p.at_ts(types::TYPE_FIRST) { + if !p.at_ts(TUPLE_FIELD_FIRST) { m.abandon(p); - p.error_and_bump("expected a type"); + p.error_and_bump("expected a tuple field"); + break; + } + let has_vis = opt_visibility(p); + if !p.at_ts(TYPE_FIRST) { + p.error("expected a type"); + if has_vis { + m.complete(p, ERROR); + } else { + m.abandon(p); + } break; } types::type_(p); diff --git a/crates/mun_syntax/src/parsing/grammar/expressions.rs b/crates/mun_syntax/src/parsing/grammar/expressions.rs index 2081f74f..7b1b2f66 100644 --- a/crates/mun_syntax/src/parsing/grammar/expressions.rs +++ b/crates/mun_syntax/src/parsing/grammar/expressions.rs @@ -1,12 +1,12 @@ use super::{ - error_block, expressions, name_ref_or_index, paths, patterns, types, BlockLike, + error_block, expressions, name_ref, name_ref_or_index, paths, patterns, types, BlockLike, CompletedMarker, Marker, Parser, SyntaxKind, TokenSet, ARG_LIST, ARRAY_EXPR, BIN_EXPR, BLOCK_EXPR, BREAK_EXPR, CALL_EXPR, CONDITION, EOF, ERROR, EXPR_STMT, FIELD_EXPR, FLOAT_NUMBER, IDENT, IF_EXPR, INDEX, INDEX_EXPR, INT_NUMBER, LET_STMT, LITERAL, LOOP_EXPR, PAREN_EXPR, PATH_EXPR, PATH_TYPE, PREFIX_EXPR, RECORD_FIELD, RECORD_FIELD_LIST, RECORD_LIT, RETURN_EXPR, STRING, WHILE_EXPR, }; -use crate::parsing::grammar::paths::PATH_FIRST; +use crate::{parsing::grammar::paths::PATH_FIRST, SyntaxKind::METHOD_CALL_EXPR}; pub(crate) const LITERAL_FIRST: TokenSet = TokenSet::new(&[T![true], T![false], INT_NUMBER, FLOAT_NUMBER, STRING]); @@ -245,6 +245,19 @@ fn call_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { m.complete(p, CALL_EXPR) } +fn method_call_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { + let m = lhs.precede(p); + p.bump(T![.]); + name_ref(p); + if p.at(T!['(']) { + arg_list(p); + } else { + p.error("expected argument list"); + } + + m.complete(p, METHOD_CALL_EXPR) +} + fn index_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { assert!(p.at(T!['['])); let m = lhs.precede(p); @@ -276,8 +289,7 @@ fn arg_list(p: &mut Parser<'_>) { fn postfix_dot_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { assert!(p.at(T![.])); if p.nth(1) == IDENT && p.nth(2) == T!['('] { - // TODO: Implement method calls here - //unimplemented!("Method calls are not supported yet."); + return method_call_expr(p, lhs); } field_expr(p, lhs) diff --git a/crates/mun_syntax/src/syntax_kind/generated.rs b/crates/mun_syntax/src/syntax_kind/generated.rs index 8200e59c..169243de 100644 --- a/crates/mun_syntax/src/syntax_kind/generated.rs +++ b/crates/mun_syntax/src/syntax_kind/generated.rs @@ -134,6 +134,7 @@ pub enum SyntaxKind { BIN_EXPR, PAREN_EXPR, CALL_EXPR, + METHOD_CALL_EXPR, FIELD_EXPR, IF_EXPR, INDEX_EXPR, @@ -615,6 +616,7 @@ impl SyntaxKind { BIN_EXPR => &SyntaxInfo { name: "BIN_EXPR" }, PAREN_EXPR => &SyntaxInfo { name: "PAREN_EXPR" }, CALL_EXPR => &SyntaxInfo { name: "CALL_EXPR" }, + METHOD_CALL_EXPR => &SyntaxInfo { name: "METHOD_CALL_EXPR" }, FIELD_EXPR => &SyntaxInfo { name: "FIELD_EXPR" }, IF_EXPR => &SyntaxInfo { name: "IF_EXPR" }, INDEX_EXPR => &SyntaxInfo { name: "INDEX_EXPR" }, diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index d77fb9fc..ebda5ab5 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -1,5 +1,30 @@ use crate::SourceFile; +#[test] +fn tuple_record() { + insta::assert_snapshot!(SourceFile::parse( + r#" + pub struct Foo(pub i32, i32); + "# + ) + .debug_dump()); +} + +#[test] +fn method_call() { + insta::assert_snapshot!(SourceFile::parse( + r#" + fn main() { + a.foo(); + a.0.foo(); + a.0.0.foo(); + a.0 .f32(); + } + "# + ) + .debug_dump()); +} + #[test] fn impl_block() { insta::assert_snapshot!(SourceFile::parse( @@ -774,7 +799,7 @@ fn struct_def() { struct Foo(f64,); struct Foo(f64, i32) "#, - ).debug_dump(), @r#" + ).debug_dump(), @r###" SOURCE_FILE@0..468 WHITESPACE@0..5 "\n " STRUCT_DEF@5..15 @@ -989,8 +1014,8 @@ fn struct_def() { error Offset(15): expected a ';', '{', or '(' error Offset(87): expected a declaration error Offset(178): expected a field declaration - error Offset(366): expected a type - "#); + error Offset(366): expected a tuple field + "###); } #[test] diff --git a/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__method_call.snap b/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__method_call.snap new file mode 100644 index 00000000..8435e838 --- /dev/null +++ b/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__method_call.snap @@ -0,0 +1,89 @@ +--- +source: crates/mun_syntax/src/tests/parser.rs +expression: "SourceFile::parse(r#\"\n fn main() {\n a.foo();\n a.0.foo();\n a.0.0.foo();\n a.0 .f32();\n }\n \"#).debug_dump()" +--- +SOURCE_FILE@0..132 + FUNCTION_DEF@0..123 + WHITESPACE@0..9 "\n " + FN_KW@9..11 "fn" + WHITESPACE@11..12 " " + NAME@12..16 + IDENT@12..16 "main" + PARAM_LIST@16..18 + L_PAREN@16..17 "(" + R_PAREN@17..18 ")" + WHITESPACE@18..19 " " + BLOCK_EXPR@19..123 + L_CURLY@19..20 "{" + WHITESPACE@20..33 "\n " + EXPR_STMT@33..41 + METHOD_CALL_EXPR@33..40 + PATH_EXPR@33..34 + PATH@33..34 + PATH_SEGMENT@33..34 + NAME_REF@33..34 + IDENT@33..34 "a" + DOT@34..35 "." + NAME_REF@35..38 + IDENT@35..38 "foo" + ARG_LIST@38..40 + L_PAREN@38..39 "(" + R_PAREN@39..40 ")" + SEMI@40..41 ";" + WHITESPACE@41..54 "\n " + EXPR_STMT@54..64 + METHOD_CALL_EXPR@54..63 + FIELD_EXPR@54..57 + PATH_EXPR@54..55 + PATH@54..55 + PATH_SEGMENT@54..55 + NAME_REF@54..55 + IDENT@54..55 "a" + INDEX@55..57 ".0" + DOT@57..58 "." + NAME_REF@58..61 + IDENT@58..61 "foo" + ARG_LIST@61..63 + L_PAREN@61..62 "(" + R_PAREN@62..63 ")" + SEMI@63..64 ";" + WHITESPACE@64..77 "\n " + EXPR_STMT@77..89 + METHOD_CALL_EXPR@77..88 + FIELD_EXPR@77..82 + FIELD_EXPR@77..80 + PATH_EXPR@77..78 + PATH@77..78 + PATH_SEGMENT@77..78 + NAME_REF@77..78 + IDENT@77..78 "a" + INDEX@78..80 ".0" + INDEX@80..82 ".0" + DOT@82..83 "." + NAME_REF@83..86 + IDENT@83..86 "foo" + ARG_LIST@86..88 + L_PAREN@86..87 "(" + R_PAREN@87..88 ")" + SEMI@88..89 ";" + WHITESPACE@89..102 "\n " + EXPR_STMT@102..113 + METHOD_CALL_EXPR@102..112 + FIELD_EXPR@102..105 + PATH_EXPR@102..103 + PATH@102..103 + PATH_SEGMENT@102..103 + NAME_REF@102..103 + IDENT@102..103 "a" + INDEX@103..105 ".0" + WHITESPACE@105..106 " " + DOT@106..107 "." + NAME_REF@107..110 + IDENT@107..110 "f32" + ARG_LIST@110..112 + L_PAREN@110..111 "(" + R_PAREN@111..112 ")" + SEMI@112..113 ";" + WHITESPACE@113..122 "\n " + R_CURLY@122..123 "}" + WHITESPACE@123..132 "\n " diff --git a/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__tuple_record.snap b/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__tuple_record.snap new file mode 100644 index 00000000..33947ab2 --- /dev/null +++ b/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__tuple_record.snap @@ -0,0 +1,36 @@ +--- +source: crates/mun_syntax/src/tests/parser.rs +expression: "SourceFile::parse(r#\"\n pub struct Foo(pub i32, i32);\n \"#).debug_dump()" +--- +SOURCE_FILE@0..47 + WHITESPACE@0..9 "\n " + STRUCT_DEF@9..38 + VISIBILITY@9..12 + PUB_KW@9..12 "pub" + WHITESPACE@12..13 " " + STRUCT_KW@13..19 "struct" + WHITESPACE@19..20 " " + NAME@20..23 + IDENT@20..23 "Foo" + TUPLE_FIELD_DEF_LIST@23..38 + L_PAREN@23..24 "(" + TUPLE_FIELD_DEF@24..31 + VISIBILITY@24..27 + PUB_KW@24..27 "pub" + WHITESPACE@27..28 " " + PATH_TYPE@28..31 + PATH@28..31 + PATH_SEGMENT@28..31 + NAME_REF@28..31 + IDENT@28..31 "i32" + COMMA@31..32 "," + WHITESPACE@32..33 " " + TUPLE_FIELD_DEF@33..36 + PATH_TYPE@33..36 + PATH@33..36 + PATH_SEGMENT@33..36 + NAME_REF@33..36 + IDENT@33..36 "i32" + R_PAREN@36..37 ")" + SEMI@37..38 ";" + WHITESPACE@38..47 "\n " diff --git a/crates/tools/Cargo.toml b/crates/tools/Cargo.toml index 9505033d..fe4a2561 100644 --- a/crates/tools/Cargo.toml +++ b/crates/tools/Cargo.toml @@ -7,7 +7,7 @@ edition.workspace = true [dependencies] anyhow = { workspace = true } cbindgen = { workspace = true } -clap = { workspace = true, features = ["derive"] } +clap = { workspace = true, features = ["derive", "std"] } difference = { workspace = true } heck = { workspace = true } ron = { workspace = true }