From ef67581104eb00a0c199f0b2a3b558da8a6f90a2 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Sun, 23 Dec 2018 17:13:11 +0100 Subject: Resolve paths to defs (functions currently) during type inference --- crates/ra_hir/Cargo.toml | 3 + crates/ra_hir/src/db.rs | 9 +- crates/ra_hir/src/function.rs | 16 +- crates/ra_hir/src/lib.rs | 19 ++- crates/ra_hir/src/mock.rs | 1 + crates/ra_hir/src/module.rs | 1 + crates/ra_hir/src/module/nameres.rs | 4 +- crates/ra_hir/src/query_definitions.rs | 16 +- crates/ra_hir/src/ty.rs | 195 ++++++++++++++++--------- crates/ra_hir/src/ty/tests.rs | 7 +- crates/ra_hir/src/ty/tests/data/0003_paths.rs | 10 ++ crates/ra_hir/src/ty/tests/data/0003_paths.txt | 9 ++ 12 files changed, 206 insertions(+), 84 deletions(-) create mode 100644 crates/ra_hir/src/ty/tests/data/0003_paths.rs create mode 100644 crates/ra_hir/src/ty/tests/data/0003_paths.txt (limited to 'crates/ra_hir') diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml index 61650cee9..594176337 100644 --- a/crates/ra_hir/Cargo.toml +++ b/crates/ra_hir/Cargo.toml @@ -16,3 +16,6 @@ ra_syntax = { path = "../ra_syntax" } ra_editor = { path = "../ra_editor" } ra_db = { path = "../ra_db" } test_utils = { path = "../test_utils" } + +[dev-dependencies] +flexi_logger = "0.10.0" diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index f0bff3c02..d94f75857 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -14,7 +14,7 @@ use crate::{ function::FnId, module::{ModuleId, ModuleTree, ModuleSource, nameres::{ItemMap, InputModuleItems}}, - ty::InferenceResult, + ty::{InferenceResult, Ty}, }; salsa::query_group! { @@ -31,11 +31,16 @@ pub trait HirDatabase: SyntaxDatabase use fn query_definitions::fn_syntax; } - fn infer(fn_id: FnId) -> Arc { + fn infer(fn_id: FnId) -> Cancelable> { type InferQuery; use fn query_definitions::infer; } + fn type_for_def(def_id: DefId) -> Cancelable { + type TypeForDefQuery; + use fn query_definitions::type_for_def; + } + fn file_items(file_id: FileId) -> Arc { type SourceFileItemsQuery; use fn query_definitions::file_items; diff --git a/crates/ra_hir/src/function.rs b/crates/ra_hir/src/function.rs index 360e9e9a0..d36477b48 100644 --- a/crates/ra_hir/src/function.rs +++ b/crates/ra_hir/src/function.rs @@ -5,12 +5,13 @@ use std::{ sync::Arc, }; +use ra_db::Cancelable; use ra_syntax::{ TextRange, TextUnit, ast::{self, AstNode, DocCommentsOwner, NameOwner}, }; -use crate::{ DefId, HirDatabase, ty::InferenceResult }; +use crate::{ DefId, HirDatabase, ty::InferenceResult, Module }; pub use self::scope::FnScopes; @@ -18,7 +19,7 @@ pub use self::scope::FnScopes; pub struct FnId(pub(crate) DefId); pub struct Function { - fn_id: FnId, + pub(crate) fn_id: FnId, } impl Function { @@ -27,6 +28,10 @@ impl Function { Function { fn_id } } + pub fn syntax(&self, db: &impl HirDatabase) -> ast::FnDefNode { + db.fn_syntax(self.fn_id) + } + pub fn scopes(&self, db: &impl HirDatabase) -> Arc { db.fn_scopes(self.fn_id) } @@ -36,9 +41,14 @@ impl Function { FnSignatureInfo::new(syntax.borrowed()) } - pub fn infer(&self, db: &impl HirDatabase) -> Arc { + pub fn infer(&self, db: &impl HirDatabase) -> Cancelable> { db.infer(self.fn_id) } + + pub fn module(&self, db: &impl HirDatabase) -> Cancelable { + let loc = self.fn_id.0.loc(db); + Module::new(db, loc.source_root_id, loc.module_id) + } } #[derive(Debug, Clone)] diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index e84f44675..a0d99a84d 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -29,7 +29,7 @@ mod ty; use std::ops::Index; -use ra_syntax::{SyntaxNodeRef, SyntaxNode}; +use ra_syntax::{SyntaxNodeRef, SyntaxNode, SyntaxKind}; use ra_db::{LocationIntener, SourceRootId, FileId, Cancelable}; use crate::{ @@ -67,6 +67,23 @@ pub struct DefLoc { source_item_id: SourceItemId, } +impl DefKind { + pub(crate) fn for_syntax_kind(kind: SyntaxKind) -> Option { + match kind { + SyntaxKind::FN_DEF => Some(DefKind::Function), + SyntaxKind::MODULE => Some(DefKind::Module), + // These define items, but don't have their own DefKinds yet: + SyntaxKind::STRUCT_DEF => Some(DefKind::Item), + SyntaxKind::ENUM_DEF => Some(DefKind::Item), + SyntaxKind::TRAIT_DEF => Some(DefKind::Item), + SyntaxKind::TYPE_DEF => Some(DefKind::Item), + SyntaxKind::CONST_DEF => Some(DefKind::Item), + SyntaxKind::STATIC_DEF => Some(DefKind::Item), + _ => None, + } + } +} + impl DefId { pub(crate) fn loc(self, db: &impl AsRef>) -> DefLoc { db.as_ref().id2loc(self) diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs index 3020ee793..b5a997170 100644 --- a/crates/ra_hir/src/mock.rs +++ b/crates/ra_hir/src/mock.rs @@ -192,6 +192,7 @@ salsa::database_storage! { fn fn_syntax() for db::FnSyntaxQuery; fn submodules() for db::SubmodulesQuery; fn infer() for db::InferQuery; + fn type_for_def() for db::TypeForDefQuery; } } } diff --git a/crates/ra_hir/src/module.rs b/crates/ra_hir/src/module.rs index cd31e8cfe..891119953 100644 --- a/crates/ra_hir/src/module.rs +++ b/crates/ra_hir/src/module.rs @@ -2,6 +2,7 @@ pub(super) mod imp; pub(super) mod nameres; use std::sync::Arc; +use log; use ra_syntax::{ algo::generate, diff --git a/crates/ra_hir/src/module/nameres.rs b/crates/ra_hir/src/module/nameres.rs index 39e891cda..0b152a406 100644 --- a/crates/ra_hir/src/module/nameres.rs +++ b/crates/ra_hir/src/module/nameres.rs @@ -272,13 +272,13 @@ where } } } - // Populate explicitelly declared items, except modules + // Populate explicitly declared items, except modules for item in input.items.iter() { if item.kind == MODULE { continue; } let def_loc = DefLoc { - kind: DefKind::Item, + kind: DefKind::for_syntax_kind(item.kind).unwrap_or(DefKind::Item), source_root_id: self.source_root, module_id, source_item_id: SourceItemId { diff --git a/crates/ra_hir/src/query_definitions.rs b/crates/ra_hir/src/query_definitions.rs index ccbfdf028..b654af920 100644 --- a/crates/ra_hir/src/query_definitions.rs +++ b/crates/ra_hir/src/query_definitions.rs @@ -11,7 +11,7 @@ use ra_syntax::{ use ra_db::{SourceRootId, FileId, Cancelable,}; use crate::{ - SourceFileItems, SourceItemId, DefKind, + SourceFileItems, SourceItemId, DefKind, Function, DefId, db::HirDatabase, function::{FnScopes, FnId}, module::{ @@ -19,7 +19,7 @@ use crate::{ imp::Submodule, nameres::{InputModuleItems, ItemMap, Resolver}, }, - ty::{self, InferenceResult} + ty::{self, InferenceResult, Ty} }; /// Resolve `FnId` to the corresponding `SyntaxNode` @@ -36,11 +36,13 @@ pub(super) fn fn_scopes(db: &impl HirDatabase, fn_id: FnId) -> Arc { Arc::new(res) } -pub(super) fn infer(db: &impl HirDatabase, fn_id: FnId) -> Arc { - let syntax = db.fn_syntax(fn_id); - let scopes = db.fn_scopes(fn_id); - let res = ty::infer(db, syntax.borrowed(), scopes); - Arc::new(res) +pub(super) fn infer(db: &impl HirDatabase, fn_id: FnId) -> Cancelable> { + let function = Function { fn_id }; + ty::infer(db, function).map(Arc::new) +} + +pub(super) fn type_for_def(db: &impl HirDatabase, def_id: DefId) -> Cancelable { + ty::type_for_def(db, def_id) } pub(super) fn file_items(db: &impl HirDatabase, file_id: FileId) -> Arc { diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index 615a1caed..13ee6cb27 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -5,21 +5,17 @@ mod tests; use std::sync::Arc; use std::fmt; +use log; use rustc_hash::{FxHashMap}; -use ra_db::LocalSyntaxPtr; +use ra_db::{LocalSyntaxPtr, Cancelable}; use ra_syntax::{ SmolStr, ast::{self, AstNode, LoopBodyOwner, ArgListOwner}, SyntaxNodeRef }; -use crate::{ - FnScopes, - db::HirDatabase, -}; - -// pub(crate) type TypeId = Id; +use crate::{Def, DefId, FnScopes, Module, Function, Path, db::HirDatabase}; #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum Ty { @@ -65,18 +61,6 @@ pub enum Ty { /// `&'a mut T` or `&'a T`. // Ref(Region<'tcx>, Ty<'tcx>, hir::Mutability), - /// The anonymous type of a function declaration/definition. Each - /// function has a unique type, which is output (for a function - /// named `foo` returning an `i32`) as `fn() -> i32 {foo}`. - /// - /// For example the type of `bar` here: - /// - /// ```rust - /// fn foo() -> i32 { 1 } - /// let bar = foo; // bar: fn() -> i32 {foo} - /// ``` - // FnDef(DefId, &'tcx Substs<'tcx>), - /// A pointer to a function. Written as `fn() -> i32`. /// /// For example the type of `bar` here: @@ -85,7 +69,7 @@ pub enum Ty { /// fn foo() -> i32 { 1 } /// let bar: fn() -> i32 = foo; /// ``` - // FnPtr(PolyFnSig<'tcx>), + FnPtr(Arc), /// A trait, defined with `trait`. // Dynamic(Binder<&'tcx List>>, ty::Region<'tcx>), @@ -139,6 +123,12 @@ pub enum Ty { type TyRef = Arc; +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct FnSig { + input: Vec, + output: Ty, +} + impl Ty { pub fn new(node: ast::TypeRef) -> Self { use ra_syntax::ast::TypeRef::*; @@ -208,11 +198,55 @@ impl fmt::Display for Ty { } write!(f, ")") } + Ty::FnPtr(sig) => { + write!(f, "fn(")?; + for t in &sig.input { + write!(f, "{},", t)?; + } + write!(f, ") -> {}", sig.output) + } Ty::Unknown => write!(f, "[unknown]"), } } } +pub fn type_for_fn(db: &impl HirDatabase, f: Function) -> Cancelable { + eprintln!("type_for_fn {:?}", f.fn_id); + let syntax = f.syntax(db); + let node = syntax.borrowed(); + // TODO we ignore type parameters for now + let input = node + .param_list() + .map(|pl| { + pl.params() + .map(|p| p.type_ref().map(|t| Ty::new(t)).unwrap_or(Ty::Unknown)) + .collect() + }) + .unwrap_or_else(Vec::new); + let output = node + .ret_type() + .and_then(|rt| rt.type_ref()) + .map(|t| Ty::new(t)) + .unwrap_or(Ty::Unknown); + let sig = FnSig { input, output }; + Ok(Ty::FnPtr(Arc::new(sig))) +} + +pub fn type_for_def(db: &impl HirDatabase, def_id: DefId) -> Cancelable { + let def = def_id.resolve(db)?; + match def { + Def::Module(..) => { + log::debug!("trying to get type for module {:?}", def_id); + Ok(Ty::Unknown) + } + Def::Function(f) => type_for_fn(db, f), + Def::Item => { + log::debug!("trying to get type for item of unknown type {:?}", def_id); + Ok(Ty::Unknown) + } + } +} + #[derive(Clone, PartialEq, Eq, Debug)] pub struct InferenceResult { type_for: FxHashMap, @@ -224,18 +258,22 @@ impl InferenceResult { } } -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct InferenceContext { +#[derive(Clone, Debug)] +pub struct InferenceContext<'a, D: HirDatabase> { + db: &'a D, scopes: Arc, + module: Module, // TODO unification tables... type_for: FxHashMap, } -impl InferenceContext { - fn new(scopes: Arc) -> Self { +impl<'a, D: HirDatabase> InferenceContext<'a, D> { + fn new(db: &'a D, scopes: Arc, module: Module) -> Self { InferenceContext { type_for: FxHashMap::default(), + db, scopes, + module, } } @@ -262,36 +300,42 @@ impl InferenceContext { self.unify(ty1, ty2) } - fn infer_path_expr(&mut self, expr: ast::PathExpr) -> Option { - let p = expr.path()?; - if p.qualifier().is_none() { - let name = p.segment().and_then(|s| s.name_ref())?; - let scope_entry = self.scopes.resolve_local_name(name)?; - let ty = self.type_for.get(&scope_entry.ptr())?; - Some(ty.clone()) - } else { - // TODO resolve path - Some(Ty::Unknown) - } + fn infer_path_expr(&mut self, expr: ast::PathExpr) -> Cancelable> { + let ast_path = ctry!(expr.path()); + let path = ctry!(Path::from_ast(ast_path)); + if path.is_ident() { + // resolve locally + let name = ctry!(ast_path.segment().and_then(|s| s.name_ref())); + if let Some(scope_entry) = self.scopes.resolve_local_name(name) { + let ty = ctry!(self.type_for.get(&scope_entry.ptr())); + return Ok(Some(ty.clone())); + }; + }; + + // resolve in module + let resolved = ctry!(self.module.resolve_path(self.db, path)?); + let ty = self.db.type_for_def(resolved)?; + // TODO we will need to add type variables for type parameters etc. here + Ok(Some(ty)) } - fn infer_expr(&mut self, expr: ast::Expr) -> Ty { + fn infer_expr(&mut self, expr: ast::Expr) -> Cancelable { let ty = match expr { ast::Expr::IfExpr(e) => { if let Some(condition) = e.condition() { if let Some(e) = condition.expr() { // TODO if no pat, this should be bool - self.infer_expr(e); + self.infer_expr(e)?; } // TODO write type for pat }; let if_ty = if let Some(block) = e.then_branch() { - self.infer_block(block) + self.infer_block(block)? } else { Ty::Unknown }; let else_ty = if let Some(block) = e.else_branch() { - self.infer_block(block) + self.infer_block(block)? } else { Ty::Unknown }; @@ -304,14 +348,14 @@ impl InferenceContext { } ast::Expr::BlockExpr(e) => { if let Some(block) = e.block() { - self.infer_block(block) + self.infer_block(block)? } else { Ty::Unknown } } ast::Expr::LoopExpr(e) => { if let Some(block) = e.loop_body() { - self.infer_block(block); + self.infer_block(block)?; }; // TODO never, or the type of the break param Ty::Unknown @@ -320,59 +364,69 @@ impl InferenceContext { if let Some(condition) = e.condition() { if let Some(e) = condition.expr() { // TODO if no pat, this should be bool - self.infer_expr(e); + self.infer_expr(e)?; } // TODO write type for pat }; if let Some(block) = e.loop_body() { // TODO - self.infer_block(block); + self.infer_block(block)?; }; // TODO always unit? Ty::Unknown } ast::Expr::ForExpr(e) => { if let Some(expr) = e.iterable() { - self.infer_expr(expr); + self.infer_expr(expr)?; } if let Some(_pat) = e.pat() { // TODO write type for pat } if let Some(block) = e.loop_body() { - self.infer_block(block); + self.infer_block(block)?; } // TODO always unit? Ty::Unknown } ast::Expr::LambdaExpr(e) => { let _body_ty = if let Some(body) = e.body() { - self.infer_expr(body) + self.infer_expr(body)? } else { Ty::Unknown }; Ty::Unknown } ast::Expr::CallExpr(e) => { + let _callee_ty = if let Some(e) = e.expr() { + self.infer_expr(e)? + } else { + Ty::Unknown + }; if let Some(arg_list) = e.arg_list() { for arg in arg_list.args() { // TODO unify / expect argument type - self.infer_expr(arg); + self.infer_expr(arg)?; } } Ty::Unknown } ast::Expr::MethodCallExpr(e) => { + let _receiver_ty = if let Some(e) = e.expr() { + self.infer_expr(e)? + } else { + Ty::Unknown + }; if let Some(arg_list) = e.arg_list() { for arg in arg_list.args() { // TODO unify / expect argument type - self.infer_expr(arg); + self.infer_expr(arg)?; } } Ty::Unknown } ast::Expr::MatchExpr(e) => { let _ty = if let Some(match_expr) = e.expr() { - self.infer_expr(match_expr) + self.infer_expr(match_expr)? } else { Ty::Unknown }; @@ -381,7 +435,7 @@ impl InferenceContext { // TODO type the bindings in pat // TODO type the guard let _ty = if let Some(e) = arm.expr() { - self.infer_expr(e) + self.infer_expr(e)? } else { Ty::Unknown }; @@ -394,12 +448,12 @@ impl InferenceContext { } ast::Expr::TupleExpr(_e) => Ty::Unknown, ast::Expr::ArrayExpr(_e) => Ty::Unknown, - ast::Expr::PathExpr(e) => self.infer_path_expr(e).unwrap_or(Ty::Unknown), + ast::Expr::PathExpr(e) => self.infer_path_expr(e)?.unwrap_or(Ty::Unknown), ast::Expr::ContinueExpr(_e) => Ty::Never, ast::Expr::BreakExpr(_e) => Ty::Never, ast::Expr::ParenExpr(e) => { if let Some(e) = e.expr() { - self.infer_expr(e) + self.infer_expr(e)? } else { Ty::Unknown } @@ -408,7 +462,7 @@ impl InferenceContext { ast::Expr::ReturnExpr(e) => { if let Some(e) = e.expr() { // TODO unify with return type - self.infer_expr(e); + self.infer_expr(e)?; }; Ty::Never } @@ -425,7 +479,7 @@ impl InferenceContext { ast::Expr::FieldExpr(_e) => Ty::Unknown, ast::Expr::TryExpr(e) => { let _inner_ty = if let Some(e) = e.expr() { - self.infer_expr(e) + self.infer_expr(e)? } else { Ty::Unknown }; @@ -433,7 +487,7 @@ impl InferenceContext { } ast::Expr::CastExpr(e) => { let _inner_ty = if let Some(e) = e.expr() { - self.infer_expr(e) + self.infer_expr(e)? } else { Ty::Unknown }; @@ -443,7 +497,7 @@ impl InferenceContext { } ast::Expr::RefExpr(e) => { let _inner_ty = if let Some(e) = e.expr() { - self.infer_expr(e) + self.infer_expr(e)? } else { Ty::Unknown }; @@ -451,7 +505,7 @@ impl InferenceContext { } ast::Expr::PrefixExpr(e) => { let _inner_ty = if let Some(e) = e.expr() { - self.infer_expr(e) + self.infer_expr(e)? } else { Ty::Unknown }; @@ -462,10 +516,10 @@ impl InferenceContext { ast::Expr::Literal(_e) => Ty::Unknown, }; self.write_ty(expr.syntax(), ty.clone()); - ty + Ok(ty) } - fn infer_block(&mut self, node: ast::Block) -> Ty { + fn infer_block(&mut self, node: ast::Block) -> Cancelable { for stmt in node.statements() { match stmt { ast::Stmt::LetStmt(stmt) => { @@ -476,7 +530,7 @@ impl InferenceContext { }; let ty = if let Some(expr) = stmt.initializer() { // TODO pass expectation - let expr_ty = self.infer_expr(expr); + let expr_ty = self.infer_expr(expr)?; self.unify_with_coercion(&expr_ty, &decl_ty) .unwrap_or(decl_ty) } else { @@ -489,23 +543,28 @@ impl InferenceContext { } ast::Stmt::ExprStmt(expr_stmt) => { if let Some(expr) = expr_stmt.expr() { - self.infer_expr(expr); + self.infer_expr(expr)?; } } } } let ty = if let Some(expr) = node.expr() { - self.infer_expr(expr) + self.infer_expr(expr)? } else { Ty::unit() }; self.write_ty(node.syntax(), ty.clone()); - ty + Ok(ty) } } -pub fn infer(_db: &impl HirDatabase, node: ast::FnDef, scopes: Arc) -> InferenceResult { - let mut ctx = InferenceContext::new(scopes); +pub fn infer(db: &impl HirDatabase, function: Function) -> Cancelable { + let scopes = function.scopes(db); + let module = function.module(db)?; + let mut ctx = InferenceContext::new(db, scopes, module); + + let syntax = function.syntax(db); + let node = syntax.borrowed(); if let Some(param_list) = node.param_list() { for param in param_list.params() { @@ -529,12 +588,12 @@ pub fn infer(_db: &impl HirDatabase, node: ast::FnDef, scopes: Arc) -> // (see Expectation in rustc_typeck) if let Some(block) = node.body() { - ctx.infer_block(block); + ctx.infer_block(block)?; } // TODO 'resolve' the types: replace inference variables by their inferred results - InferenceResult { + Ok(InferenceResult { type_for: ctx.type_for, - } + }) } diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs index 0880b51bc..e0458327a 100644 --- a/crates/ra_hir/src/ty/tests.rs +++ b/crates/ra_hir/src/ty/tests.rs @@ -1,5 +1,8 @@ use std::fmt::Write; use std::path::{PathBuf}; +use std::sync::Once; + +use flexi_logger::Logger; use ra_db::{SyntaxDatabase}; use ra_syntax::ast::{self, AstNode}; @@ -22,7 +25,7 @@ fn infer_file(content: &str) -> String { let func = source_binder::function_from_source(&db, file_id, fn_def) .unwrap() .unwrap(); - let inference_result = func.infer(&db); + let inference_result = func.infer(&db).unwrap(); for (syntax_ptr, ty) in &inference_result.type_for { let node = syntax_ptr.resolve(&source_file); write!( @@ -58,6 +61,8 @@ fn ellipsize(mut text: String, max_len: usize) -> String { #[test] pub fn infer_tests() { + static INIT: Once = Once::new(); + INIT.call_once(|| Logger::with_env().start().unwrap()); dir_tests(&test_data_dir(), &["."], |text, _path| infer_file(text)); } diff --git a/crates/ra_hir/src/ty/tests/data/0003_paths.rs b/crates/ra_hir/src/ty/tests/data/0003_paths.rs new file mode 100644 index 000000000..e8b11198b --- /dev/null +++ b/crates/ra_hir/src/ty/tests/data/0003_paths.rs @@ -0,0 +1,10 @@ +fn a() -> u32 { 1 } + +mod b { + fn c() -> u32 { 1 } +} + +fn test() { + a(); + b::c(); +} diff --git a/crates/ra_hir/src/ty/tests/data/0003_paths.txt b/crates/ra_hir/src/ty/tests/data/0003_paths.txt new file mode 100644 index 000000000..3a53370a2 --- /dev/null +++ b/crates/ra_hir/src/ty/tests/data/0003_paths.txt @@ -0,0 +1,9 @@ +[16; 17) '1': [unknown] +[14; 19) '{ 1 }': [unknown] +[47; 52) '{ 1 }': [unknown] +[49; 50) '1': [unknown] +[81; 87) 'b::c()': [unknown] +[66; 90) '{ ...c(); }': () +[72; 73) 'a': fn() -> u32 +[72; 75) 'a()': [unknown] +[81; 85) 'b::c': fn() -> u32 -- cgit v1.2.3