Repository: mun-lang/mun (language: rust)

Issue:
Add language support for specifying `pub(...)` scope
By default, a type or function defined in a module is not accessible outside of its file and sub-modules. You can expand accessibility in three ways:

- `pub`: accessible within the package and externally (incl. marshalling to the host language)
- `pub(package)`: accessible within the package
- `pub(super)`: accessible within parent module and its sub-modules

For an example, see #249.


Discussion / hints:
Im working on this! 👍

Fix patch:
diff --git a/crates/mun/src/ops/init.rs b/crates/mun/src/ops/init.rs
index 8067912db..409389fde 100644
--- a/crates/mun/src/ops/init.rs
+++ b/crates/mun/src/ops/init.rs
@@ -41,7 +41,7 @@ version="0.1.0"
         let src_path = create_in.join("src");
         create_dir(&src_path)?;
 
-        let main_file_path = src_path.join("main.mun");
+        let main_file_path = src_path.join("mod.mun");
 
         write(
             &main_file_path,
diff --git a/crates/mun_codegen/src/assembly.rs b/crates/mun_codegen/src/assembly.rs
index aeba47690..89abb2858 100644
--- a/crates/mun_codegen/src/assembly.rs
+++ b/crates/mun_codegen/src/assembly.rs
@@ -34,7 +34,7 @@ impl Assembly {
 }
 
 /// Builds an assembly for the specified file
-pub(crate) fn build_assembly(db: &dyn CodeGenDatabase, file_id: hir::FileId) -> Arc<Assembly> {
+pub(crate) fn build_assembly(db: &dyn CodeGenDatabase, module: hir::Module) -> Arc<Assembly> {
     // Construct a temporary file for the assembly
     let file = NamedTempFile::new().expect("could not create temp file for shared object");
 
@@ -44,7 +44,7 @@ pub(crate) fn build_assembly(db: &dyn CodeGenDatabase, file_id: hir::FileId) ->
 
     // Construct the module
     let module_builder =
-        ModuleBuilder::new(&code_gen_context, file_id).expect("could not create ModuleBuilder");
+        ModuleBuilder::new(&code_gen_context, module).expect("could not create ModuleBuilder");
     let obj_file = module_builder
         .build()
         .expect("unable to create object file");
diff --git a/crates/mun_codegen/src/code_gen/module_builder.rs b/crates/mun_codegen/src/code_gen/module_builder.rs
index 721215400..73dc2fb24 100644
--- a/crates/mun_codegen/src/code_gen/module_builder.rs
+++ b/crates/mun_codegen/src/code_gen/module_builder.rs
@@ -3,13 +3,12 @@ use crate::code_gen::{optimize_module, symbols, CodeGenContext, CodeGenerationEr
 use crate::ir::file::gen_file_ir;
 use crate::ir::file_group::gen_file_group_ir;
 use crate::value::{IrTypeContext, IrValueContext};
-use hir::FileId;
 use inkwell::module::{Linkage, Module};
 
 /// A struct that can be used to build an LLVM `Module`.
 pub struct ModuleBuilder<'db, 'ink, 'ctx> {
     code_gen: &'ctx CodeGenContext<'db, 'ink>,
-    file_id: FileId,
+    module: hir::Module,
     assembly_module: Module<'ink>,
 }
 
@@ -17,23 +16,26 @@ impl<'db, 'ink, 'ctx> ModuleBuilder<'db, 'ink, 'ctx> {
     /// Constructs a module for the given `hir::FileId` using the provided `CodeGenContext`.
     pub fn new(
         code_gen: &'ctx CodeGenContext<'db, 'ink>,
-        file_id: FileId,
+        module: hir::Module,
     ) -> Result<Self, anyhow::Error> {
         // Construct a module for the assembly
+        let file_id = module
+            .file_id(code_gen.db)
+            .expect("module must have a file");
         let assembly_name = code_gen.db.file_relative_path(file_id);
         let assembly_module = code_gen.create_module(assembly_name);
 
         Ok(Self {
             code_gen,
-            file_id,
+            module,
             assembly_module,
         })
     }
 
     /// Constructs an object file.
     pub fn build(self) -> Result<ObjectFile, anyhow::Error> {
-        let group_ir = gen_file_group_ir(self.code_gen, self.file_id);
-        let file = gen_file_ir(self.code_gen, &group_ir, self.file_id);
+        let group_ir = gen_file_group_ir(self.code_gen, self.module);
+        let file = gen_file_ir(self.code_gen, &group_ir, self.module);
 
         // Clone the LLVM modules so that we can modify it without modifying the cached value.
         self.assembly_module
diff --git a/crates/mun_codegen/src/db.rs b/crates/mun_codegen/src/db.rs
index 6362f61f2..d66ce3cac 100644
--- a/crates/mun_codegen/src/db.rs
+++ b/crates/mun_codegen/src/db.rs
@@ -22,7 +22,7 @@ pub trait CodeGenDatabase: hir::HirDatabase + hir::Upcast<dyn hir::HirDatabase>
     /// Returns a fully linked shared object for the specified group of files.
     /// TODO: Current, a group always consists of a single file. Need to add support for multiple.
     #[salsa::invoke(crate::assembly::build_assembly)]
-    fn assembly(&self, file: hir::FileId) -> Arc<Assembly>;
+    fn assembly(&self, module: hir::Module) -> Arc<Assembly>;
 }
 
 /// Constructs the primary interface to the complete machine description for the target machine. All
diff --git a/crates/mun_codegen/src/ir/body.rs b/crates/mun_codegen/src/ir/body.rs
index a50332dd3..0f63f1374 100644
--- a/crates/mun_codegen/src/ir/body.rs
+++ b/crates/mun_codegen/src/ir/body.rs
@@ -7,8 +7,8 @@ use crate::{
 };
 use hir::{
     ArithOp, BinaryOp, Body, CmpOp, Expr, ExprId, HirDatabase, HirDisplay, InferenceResult,
-    Literal, LogicOp, Name, Ordering, Pat, PatId, Path, Resolution, ResolveBitness, Resolver,
-    Statement, TypeCtor, UnaryOp,
+    Literal, LogicOp, Name, Ordering, Pat, PatId, Path, ResolveBitness, Resolver, Statement,
+    TypeCtor, UnaryOp, ValueNs,
 };
 use inkwell::{
     basic_block::BasicBlock,
@@ -209,7 +209,7 @@ impl<'db, 'ink, 't> BodyIrGenerator<'db, 'ink, 't> {
                 tail,
             } => self.gen_block(expr, statements, *tail),
             Expr::Path(ref p) => {
-                let resolver = hir::resolver_for_expr(self.body.clone(), self.db, expr);
+                let resolver = hir::resolver_for_expr(self.db.upcast(), self.body.owner(), expr);
                 Some(self.gen_path_expr(p, expr, &resolver))
             }
             Expr::Literal(lit) => Some(self.gen_literal(lit, expr)),
@@ -412,7 +412,7 @@ impl<'db, 'ink, 't> BodyIrGenerator<'db, 'ink, 't> {
                 struct_ir_ty
                     .ptr_type(AddressSpace::Generic)
                     .ptr_type(AddressSpace::Generic),
-                &format!("{}_ptr_ptr", hir_struct.name(self.db.upcast()).to_string()),
+                &format!("{}_ptr_ptr", hir_struct.name(self.db).to_string()),
             )
             .into_pointer_value();
 
@@ -421,7 +421,7 @@ impl<'db, 'ink, 't> BodyIrGenerator<'db, 'ink, 't> {
             .builder
             .build_load(
                 struct_ptr_ptr,
-                &format!("{}_mem_ptr", hir_struct.name(self.db.upcast()).to_string()),
+                &format!("{}_mem_ptr", hir_struct.name(self.db).to_string()),
             )
             .into_pointer_value();
 
@@ -557,13 +557,12 @@ impl<'db, 'ink, 't> BodyIrGenerator<'db, 'ink, 't> {
         expr: ExprId,
         resolver: &Resolver,
     ) -> inkwell::values::BasicValueEnum<'ink> {
-        let resolution = resolver
-            .resolve_path_without_assoc_items(self.db, path)
-            .take_values()
-            .expect("unknown path");
-
-        match resolution {
-            Resolution::LocalBinding(pat) => {
+        match resolver
+            .resolve_path_as_value_fully(self.db.upcast(), path)
+            .expect("unknown path")
+            .0
+        {
+            ValueNs::LocalBinding(pat) => {
                 if let Some(param) = self.pat_to_param.get(&pat) {
                     *param
                 } else if let Some(ptr) = self.pat_to_local.get(&pat) {
@@ -573,8 +572,8 @@ impl<'db, 'ink, 't> BodyIrGenerator<'db, 'ink, 't> {
                     unreachable!("could not find the pattern..");
                 }
             }
-            Resolution::Def(hir::ModuleDef::Struct(_)) => self.gen_unit_struct_lit(expr),
-            Resolution::Def(_) => panic!("no support for module definitions"),
+            ValueNs::StructId(_) => self.gen_unit_struct_lit(expr),
+            ValueNs::FunctionId(_) => panic!("unable to generate path expression from a function"),
         }
     }
 
@@ -605,17 +604,18 @@ impl<'db, 'ink, 't> BodyIrGenerator<'db, 'ink, 't> {
         _expr: ExprId,
         resolver: &Resolver,
     ) -> inkwell::values::PointerValue<'ink> {
-        let resolution = resolver
-            .resolve_path_without_assoc_items(self.db, path)
-            .take_values()
-            .expect("unknown path");
-
-        match resolution {
-            Resolution::LocalBinding(pat) => *self
+        match resolver
+            .resolve_path_as_value_fully(self.db.upcast(), path)
+            .expect("unknown path")
+            .0
+        {
+            ValueNs::LocalBinding(pat) => *self
                 .pat_to_local
                 .get(&pat)
                 .expect("unresolved local binding"),
-            Resolution::Def(_) => panic!("no support for module definitions"),
+            ValueNs::FunctionId(_) | ValueNs::StructId(_) => {
+                panic!("no support for module definitions")
+            }
         }
     }
 
@@ -1043,7 +1043,7 @@ impl<'db, 'ink, 't> BodyIrGenerator<'db, 'ink, 't> {
         let body = self.body.clone();
         match &body[expr] {
             Expr::Path(ref p) => {
-                let resolver = hir::resolver_for_expr(self.body.clone(), self.db, expr);
+                let resolver = hir::resolver_for_expr(self.db.upcast(), self.body.owner(), expr);
                 self.gen_path_place_expr(p, expr, &resolver)
             }
             Expr::Field {
@@ -1325,7 +1325,7 @@ impl<'db, 'ink, 't> BodyIrGenerator<'db, 'ink, 't> {
             .as_struct()
             .expect("expected a struct");
 
-        let hir_struct_name = hir_struct.name(self.db.upcast());
+        let hir_struct_name = hir_struct.name(self.db);
 
         let field_idx = hir_struct
             .field(self.db, name)
@@ -1376,7 +1376,7 @@ impl<'db, 'ink, 't> BodyIrGenerator<'db, 'ink, 't> {
             .as_struct()
             .expect("expected a struct");
 
-        let hir_struct_name = hir_struct.name(self.db.upcast());
+        let hir_struct_name = hir_struct.name(self.db);
 
         let field_idx = hir_struct
             .field(self.db, name)
diff --git a/crates/mun_codegen/src/ir/file.rs b/crates/mun_codegen/src/ir/file.rs
index 4dac33542..6417afcc4 100644
--- a/crates/mun_codegen/src/ir/file.rs
+++ b/crates/mun_codegen/src/ir/file.rs
@@ -1,10 +1,12 @@
 use super::body::ExternalGlobals;
-use crate::code_gen::CodeGenContext;
-use crate::ir::body::BodyIrGenerator;
-use crate::ir::file_group::FileGroupIR;
-use crate::ir::{function, type_table::TypeTable};
-use crate::value::Global;
-use hir::{FileId, ModuleDef};
+use crate::{
+    code_gen::CodeGenContext,
+    ir::body::BodyIrGenerator,
+    ir::file_group::FileGroupIR,
+    ir::{function, type_table::TypeTable},
+    value::Global,
+};
+use hir::{FileId, HasVisibility, ModuleDef};
 use inkwell::module::Module;
 use std::collections::{BTreeMap, HashMap, HashSet};
 
@@ -23,8 +25,11 @@ pub struct FileIR<'ink> {
 pub(crate) fn gen_file_ir<'db, 'ink>(
     code_gen: &CodeGenContext<'db, 'ink>,
     group_ir: &FileGroupIR<'ink>,
-    file_id: FileId,
+    module: hir::Module,
 ) -> FileIR<'ink> {
+    let file_id = module
+        .file_id(code_gen.db)
+        .expect("module must have an associated file");
     let llvm_module = code_gen
         .context
         .create_module(code_gen.db.file_relative_path(file_id).as_str());
@@ -35,23 +40,23 @@ pub(crate) fn gen_file_ir<'db, 'ink>(
     // Use a `BTreeMap` to guarantee deterministically ordered output.ures
     let mut functions = HashMap::new();
     let mut wrapper_functions = BTreeMap::new();
-    for def in code_gen.db.module_data(file_id).definitions() {
+    for def in module.declarations(code_gen.db) {
         if let ModuleDef::Function(f) = def {
             if !f.is_extern(code_gen.db) {
-                let fun = function::gen_prototype(code_gen.db, hir_types, *f, &llvm_module);
-                functions.insert(*f, fun);
+                let fun = function::gen_prototype(code_gen.db, hir_types, f, &llvm_module);
+                functions.insert(f, fun);
 
                 let fn_sig = f.ty(code_gen.db).callable_sig(code_gen.db).unwrap();
-                if !f.data(code_gen.db).visibility().is_private()
+                if f.visibility(code_gen.db).is_externally_visible()
                     && !fn_sig.marshallable(code_gen.db)
                 {
                     let wrapper_fun = function::gen_public_prototype(
                         code_gen.db,
                         &code_gen.hir_types,
-                        *f,
+                        f,
                         &llvm_module,
                     );
-                    wrapper_functions.insert(*f, wrapper_fun);
+                    wrapper_functions.insert(f, wrapper_fun);
                 }
             }
         }
@@ -116,7 +121,7 @@ pub(crate) fn gen_file_ir<'db, 'ink>(
     // Filter private methods
     let api: HashSet<hir::Function> = functions
         .keys()
-        .filter(|f| f.visibility(code_gen.db) != hir::Visibility::Private)
+        .filter(|f| f.visibility(code_gen.db).is_externally_visible())
         .cloned()
         .collect();
 
diff --git a/crates/mun_codegen/src/ir/file_group.rs b/crates/mun_codegen/src/ir/file_group.rs
index 83bc5defb..a1410d97a 100644
--- a/crates/mun_codegen/src/ir/file_group.rs
+++ b/crates/mun_codegen/src/ir/file_group.rs
@@ -3,9 +3,11 @@ use super::{
     intrinsics,
     type_table::{TypeTable, TypeTableBuilder},
 };
-use crate::code_gen::CodeGenContext;
-use crate::value::{IrTypeContext, IrValueContext};
-use hir::ModuleDef;
+use crate::{
+    code_gen::CodeGenContext,
+    value::{IrTypeContext, IrValueContext},
+};
+use hir::{HasVisibility, ModuleDef};
 use inkwell::{module::Module, types::PointerType, values::UnnamedAddress, AddressSpace};
 use std::collections::BTreeMap;
 
@@ -28,7 +30,7 @@ pub struct FileGroupIR<'ink> {
 ///  files using something like `FileGroupId`.
 pub(crate) fn gen_file_group_ir<'db, 'ink>(
     code_gen: &CodeGenContext<'db, 'ink>,
-    file_id: hir::FileId,
+    module: hir::Module,
 ) -> FileGroupIR<'ink> {
     let llvm_module = code_gen.context.create_module("group_name");
 
@@ -37,7 +39,7 @@ pub(crate) fn gen_file_group_ir<'db, 'ink>(
     let mut needs_alloc = false;
 
     // Collect all intrinsic functions, wrapper function, and generate struct declarations.
-    for def in code_gen.db.module_data(file_id).definitions() {
+    for def in module.declarations(code_gen.db) {
         match def {
             ModuleDef::Function(f) if !f.is_extern(code_gen.db) => {
                 intrinsics::collect_fn_body(
@@ -51,7 +53,7 @@ pub(crate) fn gen_file_group_ir<'db, 'ink>(
                 );
 
                 let fn_sig = f.ty(code_gen.db).callable_sig(code_gen.db).unwrap();
-                if !f.data(code_gen.db).visibility().is_private()
+                if f.visibility(code_gen.db).is_externally_visible()
                     && !fn_sig.marshallable(code_gen.db)
                 {
                     intrinsics::collect_wrapper_body(
@@ -62,9 +64,10 @@ pub(crate) fn gen_file_group_ir<'db, 'ink>(
                     );
                 }
             }
+            ModuleDef::Module(_) => (),
             ModuleDef::Function(_) => (), // TODO: Extern types?
             ModuleDef::Struct(_) => (),
-            ModuleDef::BuiltinType(_) => (),
+            ModuleDef::PrimitiveType(_) => (),
             ModuleDef::TypeAlias(_) => (),
         }
     }
@@ -78,9 +81,9 @@ pub(crate) fn gen_file_group_ir<'db, 'ink>(
         &intrinsics_map,
         &code_gen.hir_types,
     );
-    for def in code_gen.db.module_data(file_id).definitions() {
+    for def in module.declarations(code_gen.db) {
         if let ModuleDef::Function(f) = def {
-            if !f.data(code_gen.db).visibility().is_private() && !f.is_extern(code_gen.db) {
+            if f.visibility(code_gen.db).is_externally_visible() && !f.is_extern(code_gen.db) {
                 let body = f.body(code_gen.db);
                 let infer = f.infer(code_gen.db);
                 dispatch_table_builder.collect_body(&body, &infer);
@@ -111,15 +114,15 @@ pub(crate) fn gen_file_group_ir<'db, 'ink>(
     );
 
     // Collect all used types
-    for def in code_gen.db.module_data(file_id).definitions() {
+    for def in module.declarations(code_gen.db) {
         match def {
             ModuleDef::Struct(s) => {
-                type_table_builder.collect_struct(*s);
+                type_table_builder.collect_struct(s);
             }
             ModuleDef::Function(f) => {
-                type_table_builder.collect_fn(*f);
+                type_table_builder.collect_fn(f);
             }
-            ModuleDef::BuiltinType(_) | ModuleDef::TypeAlias(_) => (),
+            ModuleDef::PrimitiveType(_) | ModuleDef::TypeAlias(_) | ModuleDef::Module(_) => (),
         }
     }
 
diff --git a/crates/mun_codegen/src/ir/intrinsics.rs b/crates/mun_codegen/src/ir/intrinsics.rs
index 0861401e0..ed97d83ce 100644
--- a/crates/mun_codegen/src/ir/intrinsics.rs
+++ b/crates/mun_codegen/src/ir/intrinsics.rs
@@ -2,7 +2,7 @@ use crate::{
     intrinsics::{self, Intrinsic},
     ir::dispatch_table::FunctionPrototype,
 };
-use hir::{Body, Expr, ExprId, HirDatabase, InferenceResult};
+use hir::{Body, Expr, ExprId, HirDatabase, InferenceResult, ValueNs};
 use inkwell::{context::Context, targets::TargetData, types::FunctionType};
 use std::{collections::BTreeMap, sync::Arc};
 
@@ -56,13 +56,10 @@ fn collect_expr<'db, 'ink>(
     }
 
     if let Expr::Path(path) = expr {
-        let resolver = hir::resolver_for_expr(body.clone(), db, expr_id);
-        let resolution = resolver
-            .resolve_path_without_assoc_items(db, path)
-            .take_values()
-            .expect("unknown path");
-
-        if let hir::Resolution::Def(hir::ModuleDef::Struct(_)) = resolution {
+        let resolver = hir::resolver_for_expr(db.upcast(), body.owner(), expr_id);
+        if let Some((ValueNs::StructId(_), _)) =
+            resolver.resolve_path_as_value_fully(db.upcast(), path)
+        {
             collect_intrinsic(context, &target, &intrinsics::new, intrinsics);
             // self.collect_intrinsic( module, entries, &intrinsics::drop);
             *needs_alloc = true;
diff --git a/crates/mun_codegen/src/ir/ty.rs b/crates/mun_codegen/src/ir/ty.rs
index a0d1e223b..3b9d9acae 100644
--- a/crates/mun_codegen/src/ir/ty.rs
+++ b/crates/mun_codegen/src/ir/ty.rs
@@ -72,7 +72,7 @@ impl<'db, 'ink> HirTypeCache<'db, 'ink> {
         // Opaquely construct the struct type and store it in the cache
         let ir_ty = self
             .context
-            .opaque_struct_type(&struct_ty.name(self.db.upcast()).to_string());
+            .opaque_struct_type(&struct_ty.name(self.db).to_string());
         self.types.borrow_mut().insert(ty, ir_ty);
 
         // Fill the struct members
diff --git a/crates/mun_codegen/src/ir/type_table.rs b/crates/mun_codegen/src/ir/type_table.rs
index 1c530fb27..cca360a5d 100644
--- a/crates/mun_codegen/src/ir/type_table.rs
+++ b/crates/mun_codegen/src/ir/type_table.rs
@@ -5,7 +5,7 @@ use crate::{
     type_info::{TypeGroup, TypeInfo},
     value::{AsValue, CanInternalize, Global, IrValueContext, IterAsIrValue, Value},
 };
-use hir::{Body, ExprId, HirDatabase, InferenceResult};
+use hir::{Body, ExprId, HasVisibility, HirDatabase, InferenceResult};
 use inkwell::{
     context::Context, module::Linkage, module::Module, targets::TargetData, types::ArrayType,
     values::PointerValue,
@@ -163,7 +163,9 @@ impl<'db, 'ink, 't> TypeTableBuilder<'db, 'ink, 't> {
     /// Collects unique `TypeInfo` from the specified function signature and body.
     pub fn collect_fn(&mut self, hir_fn: hir::Function) {
         // Collect type info for exposed function
-        if !hir_fn.data(self.db).visibility().is_private() || self.dispatch_table.contains(hir_fn) {
+        if hir_fn.visibility(self.db).is_externally_visible()
+            || self.dispatch_table.contains(hir_fn)
+        {
             let fn_sig = hir_fn.ty(self.db).callable_sig(self.db).unwrap();
 
             // Collect argument types
@@ -261,7 +263,7 @@ impl<'db, 'ink, 't> TypeTableBuilder<'db, 'ink, 't> {
         hir_struct: hir::Struct,
     ) -> Value<'ink, ir::StructInfo<'ink>> {
         let struct_ir = self.hir_types.get_struct_type(hir_struct);
-        let name = hir_struct.name(self.db.upcast()).to_string();
+        let name = hir_struct.name(self.db).to_string();
         let fields = hir_struct.fields(self.db);
 
         // Construct an array of field names (or null if there are no fields)
diff --git a/crates/mun_codegen/src/mock.rs b/crates/mun_codegen/src/mock.rs
index 87ddc88b4..feb680485 100644
--- a/crates/mun_codegen/src/mock.rs
+++ b/crates/mun_codegen/src/mock.rs
@@ -90,6 +90,7 @@ impl MockDatabase {
 
         db.set_source_root(source_root_id, Arc::new(source_root));
         db.set_optimization_level(OptimizationLevel::None);
+        db.set_package_source_root(hir::PackageId(0), source_root_id);
 
         (db, file_id)
     }
diff --git a/crates/mun_codegen/src/type_info.rs b/crates/mun_codegen/src/type_info.rs
index cc4cf30da..86c43ce23 100644
--- a/crates/mun_codegen/src/type_info.rs
+++ b/crates/mun_codegen/src/type_info.rs
@@ -99,7 +99,7 @@ impl TypeInfo {
     }
 
     pub fn new_struct(db: &dyn HirDatabase, s: hir::Struct, type_size: TypeSize) -> TypeInfo {
-        let name = s.name(db.upcast()).to_string();
+        let name = s.name(db).to_string();
         let guid_string = {
             let fields: Vec<String> = s
                 .fields(db)
diff --git a/crates/mun_compiler/src/diagnostics.rs b/crates/mun_compiler/src/diagnostics.rs
index ff7da5166..08edb38cd 100644
--- a/crates/mun_compiler/src/diagnostics.rs
+++ b/crates/mun_compiler/src/diagnostics.rs
@@ -94,4 +94,9 @@ mod tests {
     fn test_cyclic_type_alias_error() {
         insta::assert_display_snapshot!(compilation_errors("\n\ntype Foo = Foo;"));
     }
+
+    #[test]
+    fn test_expected_function() {
+        insta::assert_display_snapshot!(compilation_errors("\n\nfn foo() { let a = 3; a(); }"));
+    }
 }
diff --git a/crates/mun_compiler/src/driver.rs b/crates/mun_compiler/src/driver.rs
index bf01a241e..a0eb56602 100644
--- a/crates/mun_compiler/src/driver.rs
+++ b/crates/mun_compiler/src/driver.rs
@@ -7,7 +7,8 @@ use crate::{
 };
 use mun_codegen::{Assembly, CodeGenDatabase};
 use mun_hir::{
-    AstDatabase, DiagnosticSink, FileId, RelativePathBuf, SourceDatabase, SourceRoot, SourceRootId,
+    AstDatabase, DiagnosticSink, FileId, Module, PackageId, RelativePathBuf, SourceDatabase,
+    SourceRoot, SourceRootId, Upcast,
 };
 
 use std::{path::PathBuf, sync::Arc};
@@ -26,6 +27,7 @@ use std::path::Path;
 use walkdir::WalkDir;
 
 pub const WORKSPACE: SourceRootId = SourceRootId(0);
+pub const PACKAGE: PackageId = PackageId(0);
 
 pub struct Driver {
     db: CompilerDatabase,
@@ -36,7 +38,7 @@ pub struct Driver {
     file_id_to_path: HashMap<FileId, RelativePathBuf>,
     next_file_id: usize,
 
-    file_id_to_temp_assembly_path: HashMap<FileId, PathBuf>,
+    module_to_temp_assembly_path: HashMap<Module, PathBuf>,
 
     display_color: DisplayColor,
 }
@@ -51,7 +53,7 @@ impl Driver {
             path_to_file_id: Default::default(),
             file_id_to_path: Default::default(),
             next_file_id: 0,
-            file_id_to_temp_assembly_path: Default::default(),
+            module_to_temp_assembly_path: Default::default(),
             display_color: config.display_color,
         })
     }
@@ -94,6 +96,7 @@ impl Driver {
         driver
             .db
             .set_source_root(WORKSPACE, Arc::new(driver.source_root.clone()));
+        driver.db.set_package_source_root(PACKAGE, WORKSPACE);
 
         Ok((driver, file_id))
     }
@@ -144,6 +147,7 @@ impl Driver {
         driver
             .db
             .set_source_root(WORKSPACE, Arc::new(driver.source_root.clone()));
+        driver.db.set_package_source_root(PACKAGE, WORKSPACE);
 
         Ok((package, driver))
     }
@@ -195,43 +199,49 @@ impl Driver {
     /// Emits all diagnostic messages currently in the database; returns true if errors were
     /// emitted.
     pub fn emit_diagnostics(&self, writer: &mut dyn std::io::Write) -> Result<bool, anyhow::Error> {
-        // Iterate over all files in the workspace
         let emit_colors = self.display_color.should_enable();
         let mut has_error = false;
-        for file_id in self.db.source_root(WORKSPACE).files() {
-            let parse = self.db.parse(file_id);
-            let source_code = self.db.file_text(file_id);
-            let relative_file_path = self.db.file_relative_path(file_id);
-            let line_index = self.db.line_index(file_id);
-
-            // Emit all syntax diagnostics
-            for syntax_error in parse.errors().iter() {
-                emit_syntax_error(
-                    syntax_error,
-                    relative_file_path.as_str(),
-                    source_code.as_str(),
-                    &line_index,
-                    emit_colors,
-                    writer,
-                )?;
-                has_error = true;
-            }
 
-            // Emit all HIR diagnostics
-            let mut error = None;
-            mun_hir::Module::from(file_id).diagnostics(
-                &self.db,
-                &mut DiagnosticSink::new(|d| {
-                    has_error = true;
-                    if let Err(e) = emit_hir_diagnostic(d, &self.db, file_id, emit_colors, writer) {
-                        error = Some(e)
-                    };
-                }),
-            );
-
-            // If an error occurred when emitting HIR diagnostics, return early with the error.
-            if let Some(e) = error {
-                return Err(e.into());
+        for package in mun_hir::Package::all(self.db.upcast()) {
+            for module in package.modules(self.db.upcast()) {
+                if let Some(file_id) = module.file_id(self.db.upcast()) {
+                    let parse = self.db.parse(file_id);
+                    let source_code = self.db.file_text(file_id);
+                    let relative_file_path = self.db.file_relative_path(file_id);
+                    let line_index = self.db.line_index(file_id);
+
+                    // Emit all syntax diagnostics
+                    for syntax_error in parse.errors().iter() {
+                        emit_syntax_error(
+                            syntax_error,
+                            relative_file_path.as_str(),
+                            source_code.as_str(),
+                            &line_index,
+                            emit_colors,
+                            writer,
+                        )?;
+                        has_error = true;
+                    }
+
+                    // Emit all HIR diagnostics
+                    let mut error = None;
+                    module.diagnostics(
+                        self.db.upcast(),
+                        &mut DiagnosticSink::new(|d| {
+                            has_error = true;
+                            if let Err(e) =
+                                emit_hir_diagnostic(d, &self.db, file_id, emit_colors, writer)
+                            {
+                                error = Some(e)
+                            };
+                        }),
+                    );
+
+                    // If an error occurred when emitting HIR diagnostics, return early with the error.
+                    if let Some(e) = error {
+                        return Err(e.into());
+                    }
+                }
             }
         }
 
@@ -241,19 +251,29 @@ impl Driver {
 
 impl Driver {
     /// Get the path where the driver will write the assembly for the specified file.
-    pub fn assembly_output_path(&self, file_id: FileId) -> PathBuf {
+    pub fn assembly_output_path_from_file(&self, file_id: FileId) -> PathBuf {
         self.db
             .file_relative_path(file_id)
             .with_extension(Assembly::EXTENSION)
             .to_path(&self.out_dir)
     }
 
-    /// Writes all assemblies
-    pub fn write_all_assemblies(&mut self) -> Result<(), anyhow::Error> {
+    /// Get the path where the driver will write the assembly for the specified file.
+    pub fn assembly_output_path(&self, module: Module) -> PathBuf {
+        let file_id = module
+            .file_id(self.db.upcast())
+            .expect("must have a file_id");
+        self.assembly_output_path_from_file(file_id)
+    }
+
+    /// Writes all assemblies. If `force` is false, the binary will not be written if there are no
+    /// changes since last time it was written.
+    pub fn write_all_assemblies(&mut self, force: bool) -> Result<(), anyhow::Error> {
         // Create a copy of all current files
-        let files = self.source_root.files().collect::<Vec<_>>();
-        for file_id in files {
-            self.write_assembly(file_id, false)?;
+        for package in mun_hir::Package::all(self.db.upcast()) {
+            for module in package.modules(self.db.upcast()) {
+                self.write_assembly(module, force)?;
+            }
         }
         Ok(())
     }
@@ -261,21 +281,21 @@ impl Driver {
     /// Generates an assembly for the given file and stores it in the output location. If `force` is
     /// false, the binary will not be written if there are no changes since last time it was
     /// written. Returns `true` if the assembly was written, `false` if it was up to date.
-    pub fn write_assembly(&mut self, file_id: FileId, force: bool) -> Result<bool, anyhow::Error> {
-        log::trace!("writing assembly for {:?}", file_id);
+    pub fn write_assembly(&mut self, module: Module, force: bool) -> Result<bool, anyhow::Error> {
+        log::trace!("writing assembly for {:?}", module);
 
         // Determine the location of the output file
-        let assembly_path = self.assembly_output_path(file_id);
+        let assembly_path = self.assembly_output_path(module);
 
         // Get the compiled assembly
-        let assembly = self.db.assembly(file_id);
+        let assembly = self.db.assembly(module);
 
         // Did the assembly change since last time?
         if !force
             && assembly_path.is_file()
             && self
-                .file_id_to_temp_assembly_path
-                .get(&file_id)
+                .module_to_temp_assembly_path
+                .get(&module)
                 .map(AsRef::as_ref)
                 == Some(assembly.path())
         {
@@ -286,8 +306,8 @@ impl Driver {
         assembly.copy_to(&assembly_path)?;
 
         // Store the information so we maybe don't have to write it next time
-        self.file_id_to_temp_assembly_path
-            .insert(file_id, assembly.path().to_path_buf());
+        self.module_to_temp_assembly_path
+            .insert(module, assembly.path().to_path_buf());
 
         Ok(true)
     }
diff --git a/crates/mun_compiler/src/lib.rs b/crates/mun_compiler/src/lib.rs
index 88148ba40..41038ecf2 100644
--- a/crates/mun_compiler/src/lib.rs
+++ b/crates/mun_compiler/src/lib.rs
@@ -87,7 +87,7 @@ pub fn compile_manifest(manifest_path: &Path, config: Config) -> Result<bool, an
     };
 
     // Write out all assemblies
-    driver.write_all_assemblies()?;
+    driver.write_all_assemblies(false)?;
 
     Ok(true)
 }
diff --git a/crates/mun_compiler_daemon/src/lib.rs b/crates/mun_compiler_daemon/src/lib.rs
index 94391450f..6b9f4ae5e 100644
--- a/crates/mun_compiler_daemon/src/lib.rs
+++ b/crates/mun_compiler_daemon/src/lib.rs
@@ -27,7 +27,7 @@ pub fn compile_and_watch_manifest(
 
     // Emit all current errors, and write the assemblies if no errors occured
     if !driver.emit_diagnostics(&mut stderr())? {
-        driver.write_all_assemblies()?
+        driver.write_all_assemblies(false)?
     }
 
     // Insert Ctrl+C handler so we can gracefully quit
@@ -49,7 +49,7 @@ pub fn compile_and_watch_manifest(
                     log::info!("Modifying {}", relative_path);
                     driver.update_file(relative_path, file_contents);
                     if !driver.emit_diagnostics(&mut stderr())? {
-                        driver.write_all_assemblies()?;
+                        driver.write_all_assemblies(false)?;
                     }
                 }
                 Create(ref path) if is_source_file(path) => {
@@ -58,17 +58,18 @@ pub fn compile_and_watch_manifest(
                     log::info!("Creating {}", relative_path);
                     driver.add_file(relative_path, file_contents);
                     if !driver.emit_diagnostics(&mut stderr())? {
-                        driver.write_all_assemblies()?;
+                        driver.write_all_assemblies(false)?;
                     }
                 }
                 Remove(ref path) if is_source_file(path) => {
                     // Simply remove the source file from the source root
                     let relative_path = compute_source_relative_path(&source_directory, path)?;
                     log::info!("Removing {}", relative_path);
-                    let assembly_path = driver.assembly_output_path(driver.get_file_id_for_path(&relative_path).expect("cannot remove a file that was not part of the compilation in the first place"));
-                    if assembly_path.is_file() {
-                        std::fs::remove_file(assembly_path)?;
-                    }
+                    // TODO: Remove assembly files if there are no files referencing it.
+                    // let assembly_path = driver.assembly_output_path(driver.get_file_id_for_path(&relative_path).expect("cannot remove a file that was not part of the compilation in the first place"));
+                    // if assembly_path.is_file() {
+                    //     std::fs::remove_file(assembly_path)?;
+                    // }
                     driver.remove_file(relative_path);
                     driver.emit_diagnostics(&mut stderr())?;
                 }
@@ -82,7 +83,7 @@ pub fn compile_and_watch_manifest(
                     log::info!("Renaming {} to {}", from_relative_path, to_relative_path,);
                     driver.rename(from_relative_path, to_relative_path);
                     if !driver.emit_diagnostics(&mut stderr())? {
-                        driver.write_all_assemblies()?;
+                        driver.write_all_assemblies(false)?;
                     }
                 }
                 _ => {}
diff --git a/crates/mun_hir/Cargo.toml b/crates/mun_hir/Cargo.toml
index 6a8f156f7..eaa02e542 100644
--- a/crates/mun_hir/Cargo.toml
+++ b/crates/mun_hir/Cargo.toml
@@ -23,6 +23,7 @@ relative-path = "1.2"
 ena = "0.14"
 drop_bomb = "0.1.4"
 either = "1.5.3"
+itertools = "0.9.0"
 
 [dev-dependencies]
 insta = "0.16"
diff --git a/crates/mun_hir/src/arena/map.rs b/crates/mun_hir/src/arena/map.rs
index 0bb33f671..6b08e07ee 100644
--- a/crates/mun_hir/src/arena/map.rs
+++ b/crates/mun_hir/src/arena/map.rs
@@ -61,8 +61,14 @@ impl<T, V> ArenaMap<Idx<T>, V> {
 
 impl<T, V> std::ops::Index<Idx<V>> for ArenaMap<Idx<V>, T> {
     type Output = T;
-    fn index(&self, id: Idx<V>) -> &T {
-        self.v[Self::idx_to_raw(id)].as_ref().unwrap()
+    fn index(&self, index: Idx<V>) -> &T {
+        self.v[Self::idx_to_raw(index)].as_ref().unwrap()
+    }
+}
+
+impl<T, V> std::ops::IndexMut<Idx<V>> for ArenaMap<Idx<V>, T> {
+    fn index_mut(&mut self, index: Idx<V>) -> &mut Self::Output {
+        self.v[Self::idx_to_raw(index)].as_mut().unwrap()
     }
 }
 
diff --git a/crates/mun_hir/src/builtin_type.rs b/crates/mun_hir/src/builtin_type.rs
deleted file mode 100644
index f4d622a44..000000000
--- a/crates/mun_hir/src/builtin_type.rs
+++ /dev/null
@@ -1,157 +0,0 @@
-use crate::name::{name, Name};
-use std::fmt;
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum Signedness {
-    Signed,
-    Unsigned,
-}
-
-impl Signedness {
-    pub fn is_signed(self) -> bool {
-        self == Signedness::Signed
-    }
-}
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum IntBitness {
-    Xsize,
-    X8,
-    X16,
-    X32,
-    X64,
-    X128,
-}
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum FloatBitness {
-    X32,
-    X64,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct BuiltinInt {
-    pub signedness: Signedness,
-    pub bitness: IntBitness,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct BuiltinFloat {
-    pub bitness: FloatBitness,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum BuiltinType {
-    Float(BuiltinFloat),
-    Int(BuiltinInt),
-    Bool,
-}
-
-impl BuiltinType {
-    #[rustfmt::skip]
-    pub const ALL: &'static [(Name, BuiltinType)] = &[
-        (name![bool],  BuiltinType::Bool),
-
-        (name![isize], BuiltinType::Int(BuiltinInt::ISIZE)),
-        (name![i8],    BuiltinType::Int(BuiltinInt::I8)),
-        (name![i16],   BuiltinType::Int(BuiltinInt::I16)),
-        (name![i32],   BuiltinType::Int(BuiltinInt::I32)),
-        (name![i64],   BuiltinType::Int(BuiltinInt::I64)),
-        (name![i128],  BuiltinType::Int(BuiltinInt::I128)),
-
-        (name![usize], BuiltinType::Int(BuiltinInt::USIZE)),
-        (name![u8],    BuiltinType::Int(BuiltinInt::U8)),
-        (name![u16],   BuiltinType::Int(BuiltinInt::U16)),
-        (name![u32],   BuiltinType::Int(BuiltinInt::U32)),
-        (name![u64],   BuiltinType::Int(BuiltinInt::U64)),
-        (name![u128],  BuiltinType::Int(BuiltinInt::U128)),
-
-        (name![f32],   BuiltinType::Float(BuiltinFloat::F32)),
-        (name![f64],   BuiltinType::Float(BuiltinFloat::F64)),
-    ];
-}
-
-impl fmt::Display for BuiltinType {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let type_name = match self {
-            BuiltinType::Bool => "bool",
-            BuiltinType::Int(BuiltinInt {
-                signedness,
-                bitness,
-            }) => match (signedness, bitness) {
-                (Signedness::Signed, IntBitness::Xsize) => "isize",
-                (Signedness::Signed, IntBitness::X8) => "i8",
-                (Signedness::Signed, IntBitness::X16) => "i16",
-                (Signedness::Signed, IntBitness::X32) => "i32",
-                (Signedness::Signed, IntBitness::X64) => "i64",
-                (Signedness::Signed, IntBitness::X128) => "i128",
-
-                (Signedness::Unsigned, IntBitness::Xsize) => "usize",
-                (Signedness::Unsigned, IntBitness::X8) => "u8",
-                (Signedness::Unsigned, IntBitness::X16) => "u16",
-                (Signedness::Unsigned, IntBitness::X32) => "u32",
-                (Signedness::Unsigned, IntBitness::X64) => "u64",
-                (Signedness::Unsigned, IntBitness::X128) => "u128",
-            },
-            BuiltinType::Float(BuiltinFloat { bitness }) => match bitness {
-                FloatBitness::X32 => "f32",
-                FloatBitness::X64 => "f64",
-            },
-        };
-        f.write_str(type_name)
-    }
-}
-
-#[rustfmt::skip]
-impl BuiltinInt {
-    pub const ISIZE: BuiltinInt = BuiltinInt { signedness: Signedness::Signed, bitness: IntBitness::Xsize       };
-    pub const I8   : BuiltinInt = BuiltinInt { signedness: Signedness::Signed, bitness: IntBitness::X8          };
-    pub const I16  : BuiltinInt = BuiltinInt { signedness: Signedness::Signed, bitness: IntBitness::X16         };
-    pub const I32  : BuiltinInt = BuiltinInt { signedness: Signedness::Signed, bitness: IntBitness::X32         };
-    pub const I64  : BuiltinInt = BuiltinInt { signedness: Signedness::Signed, bitness: IntBitness::X64         };
-    pub const I128 : BuiltinInt = BuiltinInt { signedness: Signedness::Signed, bitness: IntBitness::X128        };
-
-    pub const USIZE: BuiltinInt = BuiltinInt { signedness: Signedness::Unsigned, bitness: IntBitness::Xsize     };
-    pub const U8   : BuiltinInt = BuiltinInt { signedness: Signedness::Unsigned, bitness: IntBitness::X8        };
-    pub const U16  : BuiltinInt = BuiltinInt { signedness: Signedness::Unsigned, bitness: IntBitness::X16       };
-    pub const U32  : BuiltinInt = BuiltinInt { signedness: Signedness::Unsigned, bitness: IntBitness::X32       };
-    pub const U64  : BuiltinInt = BuiltinInt { signedness: Signedness::Unsigned, bitness: IntBitness::X64       };
-    pub const U128 : BuiltinInt = BuiltinInt { signedness: Signedness::Unsigned, bitness: IntBitness::X128      };
-
-
-    pub fn from_suffix(suffix: &str) -> Option<BuiltinInt> {
-        let res = match suffix {
-            "isize" => Self::ISIZE,
-            "i8"    => Self::I8,
-            "i16"   => Self::I16,
-            "i32"   => Self::I32,
-            "i64"   => Self::I64,
-            "i128"  => Self::I128,
-
-            "usize" => Self::USIZE,
-            "u8"    => Self::U8,
-            "u16"   => Self::U16,
-            "u32"   => Self::U32,
-            "u64"   => Self::U64,
-            "u128"  => Self::U128,
-
-            _ => return None,
-        };
-        Some(res)
-    }
-}
-
-#[rustfmt::skip]
-impl BuiltinFloat {
-    pub const F32:   BuiltinFloat = BuiltinFloat { bitness: FloatBitness::X32 };
-    pub const F64:   BuiltinFloat = BuiltinFloat { bitness: FloatBitness::X64 };
-
-    pub fn from_suffix(suffix: &str) -> Option<BuiltinFloat> {
-        let res = match suffix {
-            "f32" => BuiltinFloat::F32,
-            "f64" => BuiltinFloat::F64,
-            _ => return None,
-        };
-        Some(res)
-    }
-}
diff --git a/crates/mun_hir/src/code_model.rs b/crates/mun_hir/src/code_model.rs
index edf48bf40..75a3b8623 100644
--- a/crates/mun_hir/src/code_model.rs
+++ b/crates/mun_hir/src/code_model.rs
@@ -1,155 +1,26 @@
+mod function;
+mod module;
+mod package;
 pub(crate) mod src;
+mod r#struct;
+mod type_alias;
 
-use crate::adt::{LocalStructFieldId, StructData, TypeAliasData};
-use crate::builtin_type::BuiltinType;
-use crate::code_model::diagnostics::ModuleDefinitionDiagnostic;
-use crate::diagnostics::DiagnosticSink;
-use crate::expr::validator::{ExprValidator, TypeAliasValidator};
-use crate::expr::{Body, BodySourceMap};
-use crate::ids::{FunctionLoc, Intern, Lookup, StructLoc, TypeAliasLoc};
-use crate::item_tree::ModItem;
-use crate::name_resolution::Namespace;
-use crate::resolve::{Resolution, Resolver};
-use crate::ty::{lower::LowerBatchResult, InferenceResult};
-use crate::type_ref::{LocalTypeRefId, TypeRefBuilder, TypeRefMap, TypeRefSourceMap};
-use crate::{
-    ids::{FunctionId, StructId, TypeAliasId},
-    DefDatabase, FileId, HirDatabase, InFile, Name, Ty,
-};
-use mun_syntax::ast::{TypeAscriptionOwner, VisibilityOwner};
-use rustc_hash::FxHashMap;
+use crate::{expr::BodySourceMap, HirDatabase, Name};
 use std::sync::Arc;
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct Module {
-    pub(crate) file_id: FileId,
-}
-
-impl From<FileId> for Module {
-    fn from(file_id: FileId) -> Self {
-        Module { file_id }
-    }
-}
-
-impl Module {
-    pub fn file_id(self) -> FileId {
-        self.file_id
-    }
-
-    /// Returns all the definitions declared in this module.
-    pub fn declarations(self, db: &dyn HirDatabase) -> Vec<ModuleDef> {
-        db.module_data(self.file_id).definitions.clone()
-    }
-
-    fn resolver(self, _db: &dyn DefDatabase) -> Resolver {
-        Resolver::default().push_module_scope(self.file_id)
-    }
-
-    pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
-        for diag in db.module_data(self.file_id).diagnostics.iter() {
-            diag.add_to(db.upcast(), self, sink);
-        }
-        for decl in self.declarations(db) {
-            match decl {
-                ModuleDef::Function(f) => f.diagnostics(db, sink),
-                ModuleDef::Struct(s) => s.diagnostics(db, sink),
-                ModuleDef::TypeAlias(t) => t.diagnostics(db, sink),
-                ModuleDef::BuiltinType(_) => (),
-            }
-        }
-    }
-}
-
-#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
-pub struct ModuleData {
-    definitions: Vec<ModuleDef>,
-    diagnostics: Vec<ModuleDefinitionDiagnostic>,
-}
-
-#[derive(Debug, Default, PartialEq, Eq, Clone)]
-pub struct ModuleScope {
-    items: FxHashMap<Name, Resolution>,
-}
-
-impl ModuleData {
-    pub(crate) fn module_data_query(db: &dyn DefDatabase, file_id: FileId) -> Arc<ModuleData> {
-        let items = db.item_tree(file_id);
-        let mut data = ModuleData::default();
-        let mut definition_by_name = FxHashMap::default();
-        for item in items.top_level_items() {
-            let name = match item {
-                ModItem::Function(item) => items[*item].name.clone(),
-                ModItem::Struct(item) => items[*item].name.clone(),
-                ModItem::TypeAlias(item) => items[*item].name.clone(),
-            };
-
-            if let Some(prev_definition) = definition_by_name.get(&name) {
-                data.diagnostics
-                    .push(diagnostics::ModuleDefinitionDiagnostic::DuplicateName {
-                        name,
-                        definition: *item,
-                        first_definition: *prev_definition,
-                    })
-            } else {
-                definition_by_name.insert(name, *item);
-            }
-
-            match item {
-                ModItem::Function(item) => data.definitions.push(ModuleDef::Function(Function {
-                    id: FunctionLoc {
-                        id: InFile::new(file_id, *item),
-                    }
-                    .intern(db),
-                })),
-                ModItem::Struct(item) => data.definitions.push(ModuleDef::Struct(Struct {
-                    id: StructLoc {
-                        id: InFile::new(file_id, *item),
-                    }
-                    .intern(db),
-                })),
-                ModItem::TypeAlias(item) => {
-                    data.definitions.push(ModuleDef::TypeAlias(TypeAlias {
-                        id: TypeAliasLoc {
-                            id: InFile::new(file_id, *item),
-                        }
-                        .intern(db),
-                    }))
-                }
-            };
-        }
-        Arc::new(data)
-    }
-
-    pub fn definitions(&self) -> &[ModuleDef] {
-        &self.definitions
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum ModuleDef {
-    Function(Function),
-    BuiltinType(BuiltinType),
-    Struct(Struct),
-    TypeAlias(TypeAlias),
-}
-
-impl From<Function> for ModuleDef {
-    fn from(t: Function) -> Self {
-        ModuleDef::Function(t)
-    }
-}
-
-impl From<BuiltinType> for ModuleDef {
-    fn from(t: BuiltinType) -> Self {
-        ModuleDef::BuiltinType(t)
-    }
-}
+pub use self::{
+    function::Function,
+    module::{Module, ModuleDef},
+    package::Package,
+    r#struct::{LocalStructFieldId, Struct, StructField, StructKind, StructMemoryKind},
+    type_alias::TypeAlias,
+};
 
-impl From<Struct> for ModuleDef {
-    fn from(t: Struct) -> Self {
-        ModuleDef::Struct(t)
-    }
-}
+pub use self::{
+    function::FunctionData,
+    r#struct::{StructData, StructFieldData},
+    type_alias::TypeAliasData,
+};
 
 /// The definitions that have a body.
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -158,43 +29,20 @@ pub enum DefWithBody {
 }
 impl_froms!(DefWithBody: Function);
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Visibility {
-    Public,
-    Private,
-}
-
 impl DefWithBody {
-    pub fn infer(self, db: &dyn HirDatabase) -> Arc<InferenceResult> {
-        db.infer(self)
-    }
-
-    pub fn body(self, db: &dyn HirDatabase) -> Arc<Body> {
-        db.body(self)
+    pub fn module(self, db: &dyn HirDatabase) -> Module {
+        match self {
+            DefWithBody::Function(f) => f.module(db),
+        }
     }
 
     pub fn body_source_map(self, db: &dyn HirDatabase) -> Arc<BodySourceMap> {
-        db.body_with_source_map(self).1
-    }
-
-    /// Builds a `Resolver` for code inside this item. A `Resolver` enables name resolution.
-    pub(crate) fn resolver(self, db: &dyn HirDatabase) -> Resolver {
         match self {
-            DefWithBody::Function(f) => f.resolver(db),
+            DefWithBody::Function(f) => f.body_source_map(db),
         }
     }
 }
 
-impl Visibility {
-    pub fn is_public(self) -> bool {
-        self == Visibility::Public
-    }
-
-    pub fn is_private(self) -> bool {
-        self == Visibility::Private
-    }
-}
-
 /// Definitions that have a struct.
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub enum DefWithStruct {
@@ -203,21 +51,21 @@ pub enum DefWithStruct {
 impl_froms!(DefWithStruct: Struct);
 
 impl DefWithStruct {
-    pub fn fields(self, db: &dyn HirDatabase) -> Vec<StructField> {
+    pub fn module(self, db: &dyn HirDatabase) -> Module {
         match self {
-            DefWithStruct::Struct(s) => s.fields(db),
+            DefWithStruct::Struct(s) => s.module(db),
         }
     }
 
-    pub fn field(self, db: &dyn HirDatabase, name: &Name) -> Option<StructField> {
+    pub fn fields(self, db: &dyn HirDatabase) -> Vec<StructField> {
         match self {
-            DefWithStruct::Struct(s) => s.field(db, name),
+            DefWithStruct::Struct(s) => s.fields(db),
         }
     }
 
-    pub fn module(self, db: &dyn HirDatabase) -> Module {
+    pub fn field(self, db: &dyn HirDatabase, name: &Name) -> Option<StructField> {
         match self {
-            DefWithStruct::Struct(s) => s.module(db.upcast()),
+            DefWithStruct::Struct(s) => s.field(db, name),
         }
     }
 
@@ -227,328 +75,3 @@ impl DefWithStruct {
         }
     }
 }
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
-pub struct Function {
-    pub(crate) id: FunctionId,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct FunctionData {
-    name: Name,
-    params: Vec<LocalTypeRefId>,
-    visibility: Visibility,
-    ret_type: LocalTypeRefId,
-    type_ref_map: TypeRefMap,
-    type_ref_source_map: TypeRefSourceMap,
-    is_extern: bool,
-}
-
-impl FunctionData {
-    pub(crate) fn fn_data_query(db: &dyn DefDatabase, func: FunctionId) -> Arc<FunctionData> {
-        let loc = func.lookup(db);
-        let item_tree = db.item_tree(loc.id.file_id);
-        let func = &item_tree[loc.id.value];
-        let src = item_tree.source(db, loc.id);
-
-        let mut type_ref_builder = TypeRefBuilder::default();
-
-        let visibility = src
-            .visibility()
-            .map(|_v| Visibility::Public)
-            .unwrap_or(Visibility::Private);
-
-        let mut params = Vec::new();
-        if let Some(param_list) = src.param_list() {
-            for param in param_list.params() {
-                let type_ref = type_ref_builder.alloc_from_node_opt(param.ascribed_type().as_ref());
-                params.push(type_ref);
-            }
-        }
-
-        let ret_type = if let Some(type_ref) = src.ret_type().and_then(|rt| rt.type_ref()) {
-            type_ref_builder.alloc_from_node(&type_ref)
-        } else {
-            type_ref_builder.unit()
-        };
-
-        let (type_ref_map, type_ref_source_map) = type_ref_builder.finish();
-
-        Arc::new(FunctionData {
-            name: func.name.clone(),
-            params,
-            visibility,
-            ret_type,
-            type_ref_map,
-            type_ref_source_map,
-            is_extern: func.is_extern,
-        })
-    }
-
-    pub fn name(&self) -> &Name {
-        &self.name
-    }
-
-    pub fn params(&self) -> &[LocalTypeRefId] {
-        &self.params
-    }
-
-    pub fn visibility(&self) -> Visibility {
-        self.visibility
-    }
-
-    pub fn ret_type(&self) -> &LocalTypeRefId {
-        &self.ret_type
-    }
-
-    pub fn type_ref_source_map(&self) -> &TypeRefSourceMap {
-        &self.type_ref_source_map
-    }
-
-    pub fn type_ref_map(&self) -> &TypeRefMap {
-        &self.type_ref_map
-    }
-}
-
-impl Function {
-    pub fn module(self, db: &dyn DefDatabase) -> Module {
-        Module {
-            file_id: self.id.lookup(db).id.file_id,
-        }
-    }
-
-    pub fn name(self, db: &dyn HirDatabase) -> Name {
-        self.data(db).name.clone()
-    }
-
-    pub fn visibility(self, db: &dyn HirDatabase) -> Visibility {
-        self.data(db).visibility()
-    }
-
-    pub fn data(self, db: &dyn HirDatabase) -> Arc<FunctionData> {
-        db.fn_data(self.id)
-    }
-
-    pub fn body(self, db: &dyn HirDatabase) -> Arc<Body> {
-        db.body(self.into())
-    }
-
-    pub fn ty(self, db: &dyn HirDatabase) -> Ty {
-        // TODO: Add detection of cyclick types
-        db.type_for_def(self.into(), Namespace::Values).0
-    }
-
-    pub fn infer(self, db: &dyn HirDatabase) -> Arc<InferenceResult> {
-        db.infer(self.into())
-    }
-
-    pub fn is_extern(self, db: &dyn HirDatabase) -> bool {
-        db.fn_data(self.id).is_extern
-    }
-
-    pub(crate) fn body_source_map(self, db: &dyn HirDatabase) -> Arc<BodySourceMap> {
-        db.body_with_source_map(self.into()).1
-    }
-
-    pub(crate) fn resolver(self, db: &dyn HirDatabase) -> Resolver {
-        // take the outer scope...
-        self.module(db.upcast()).resolver(db.upcast())
-    }
-
-    pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
-        let body = self.body(db);
-        body.add_diagnostics(db, self.into(), sink);
-        let infer = self.infer(db);
-        infer.add_diagnostics(db, self, sink);
-        let validator = ExprValidator::new(self, db);
-        validator.validate_body(sink);
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct Struct {
-    pub(crate) id: StructId,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct StructField {
-    pub(crate) parent: Struct,
-    pub(crate) id: LocalStructFieldId,
-}
-
-impl StructField {
-    pub fn ty(self, db: &dyn HirDatabase) -> Ty {
-        let data = self.parent.data(db.upcast());
-        let type_ref_id = data.fields[self.id].type_ref;
-        let lower = self.parent.lower(db);
-        lower[type_ref_id].clone()
-    }
-
-    pub fn name(self, db: &dyn HirDatabase) -> Name {
-        self.parent.data(db.upcast()).fields[self.id].name.clone()
-    }
-
-    pub fn id(self) -> LocalStructFieldId {
-        self.id
-    }
-}
-
-impl Struct {
-    pub fn module(self, db: &dyn DefDatabase) -> Module {
-        Module {
-            file_id: self.id.lookup(db).id.file_id,
-        }
-    }
-
-    pub fn data(self, db: &dyn DefDatabase) -> Arc<StructData> {
-        db.struct_data(self.id)
-    }
-
-    pub fn name(self, db: &dyn DefDatabase) -> Name {
-        self.data(db).name.clone()
-    }
-
-    pub fn fields(self, db: &dyn HirDatabase) -> Vec<StructField> {
-        self.data(db.upcast())
-            .fields
-            .iter()
-            .map(|(id, _)| StructField { parent: self, id })
-            .collect()
-    }
-
-    pub fn field(self, db: &dyn HirDatabase, name: &Name) -> Option<StructField> {
-        self.data(db.upcast())
-            .fields
-            .iter()
-            .find(|(_, data)| data.name == *name)
-            .map(|(id, _)| StructField { parent: self, id })
-    }
-
-    pub fn ty(self, db: &dyn HirDatabase) -> Ty {
-        // TODO: Add detection of cyclick types
-        db.type_for_def(self.into(), Namespace::Types).0
-    }
-
-    pub fn lower(self, db: &dyn HirDatabase) -> Arc<LowerBatchResult> {
-        db.lower_struct(self)
-    }
-
-    pub(crate) fn resolver(self, db: &dyn HirDatabase) -> Resolver {
-        // take the outer scope...
-        self.module(db.upcast()).resolver(db.upcast())
-    }
-
-    pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
-        let data = self.data(db.upcast());
-        let lower = self.lower(db);
-        lower.add_diagnostics(
-            db,
-            self.module(db.upcast()).file_id,
-            data.type_ref_source_map(),
-            sink,
-        );
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct TypeAlias {
-    pub(crate) id: TypeAliasId,
-}
-
-impl TypeAlias {
-    pub fn module(self, db: &dyn DefDatabase) -> Module {
-        Module {
-            file_id: self.id.lookup(db).id.file_id,
-        }
-    }
-
-    pub fn data(self, db: &dyn DefDatabase) -> Arc<TypeAliasData> {
-        db.type_alias_data(self.id)
-    }
-
-    pub fn name(self, db: &dyn DefDatabase) -> Name {
-        self.data(db).name.clone()
-    }
-
-    pub fn type_ref(self, db: &dyn HirDatabase) -> LocalTypeRefId {
-        self.data(db.upcast()).type_ref_id
-    }
-
-    pub fn lower(self, db: &dyn HirDatabase) -> Arc<LowerBatchResult> {
-        db.lower_type_alias(self)
-    }
-
-    pub(crate) fn resolver(self, db: &dyn HirDatabase) -> Resolver {
-        // take the outer scope...
-        self.module(db.upcast()).resolver(db.upcast())
-    }
-
-    pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
-        let data = self.data(db.upcast());
-        let lower = self.lower(db);
-        lower.add_diagnostics(
-            db,
-            self.module(db.upcast()).file_id,
-            data.type_ref_source_map(),
-            sink,
-        );
-
-        let validator = TypeAliasValidator::new(self, db);
-        validator.validate_target_type_existence(sink);
-    }
-}
-
-mod diagnostics {
-    use super::Module;
-    use crate::diagnostics::{DiagnosticSink, DuplicateDefinition};
-    use crate::item_tree::{ItemTreeId, ModItem};
-    use crate::{DefDatabase, Name};
-    use mun_syntax::{AstNode, SyntaxNodePtr};
-
-    #[derive(Debug, PartialEq, Eq, Clone, Hash)]
-    pub(super) enum ModuleDefinitionDiagnostic {
-        DuplicateName {
-            name: Name,
-            definition: ModItem,
-            first_definition: ModItem,
-        },
-    }
-
-    fn syntax_ptr_from_def(db: &dyn DefDatabase, owner: Module, item: ModItem) -> SyntaxNodePtr {
-        let file_id = owner.file_id;
-        let item_tree = db.item_tree(file_id);
-        match item {
-            ModItem::Function(id) => {
-                SyntaxNodePtr::new(item_tree.source(db, ItemTreeId::new(file_id, id)).syntax())
-            }
-            ModItem::Struct(id) => {
-                SyntaxNodePtr::new(item_tree.source(db, ItemTreeId::new(file_id, id)).syntax())
-            }
-            ModItem::TypeAlias(id) => {
-                SyntaxNodePtr::new(item_tree.source(db, ItemTreeId::new(file_id, id)).syntax())
-            }
-        }
-    }
-
-    impl ModuleDefinitionDiagnostic {
-        pub(super) fn add_to(
-            &self,
-            db: &dyn DefDatabase,
-            owner: Module,
-            sink: &mut DiagnosticSink,
-        ) {
-            match self {
-                ModuleDefinitionDiagnostic::DuplicateName {
-                    name,
-                    definition,
-                    first_definition,
-                } => sink.push(DuplicateDefinition {
-                    file: owner.file_id,
-                    name: name.to_string(),
-                    definition: syntax_ptr_from_def(db, owner, *definition),
-                    first_definition: syntax_ptr_from_def(db, owner, *first_definition),
-                }),
-            }
-        }
-    }
-}
diff --git a/crates/mun_hir/src/code_model/function.rs b/crates/mun_hir/src/code_model/function.rs
new file mode 100644
index 000000000..26f7da8d3
--- /dev/null
+++ b/crates/mun_hir/src/code_model/function.rs
@@ -0,0 +1,155 @@
+use super::Module;
+use crate::expr::validator::ExprValidator;
+use crate::expr::BodySourceMap;
+use crate::ids::{FunctionId, Lookup};
+use crate::name_resolution::Namespace;
+use crate::resolve::HasResolver;
+use crate::type_ref::{LocalTypeRefId, TypeRefBuilder, TypeRefMap, TypeRefSourceMap};
+use crate::visibility::RawVisibility;
+use crate::{
+    Body, DefDatabase, DiagnosticSink, FileId, HasVisibility, HirDatabase, InferenceResult, Name,
+    Ty, Visibility,
+};
+use mun_syntax::ast::TypeAscriptionOwner;
+use std::sync::Arc;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub struct Function {
+    pub(crate) id: FunctionId,
+}
+
+impl From<FunctionId> for Function {
+    fn from(id: FunctionId) -> Self {
+        Function { id }
+    }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct FunctionData {
+    name: Name,
+    params: Vec<LocalTypeRefId>,
+    visibility: RawVisibility,
+    ret_type: LocalTypeRefId,
+    type_ref_map: TypeRefMap,
+    type_ref_source_map: TypeRefSourceMap,
+    is_extern: bool,
+}
+
+impl FunctionData {
+    pub(crate) fn fn_data_query(db: &dyn DefDatabase, func: FunctionId) -> Arc<FunctionData> {
+        let loc = func.lookup(db);
+        let item_tree = db.item_tree(loc.id.file_id);
+        let func = &item_tree[loc.id.value];
+        let src = item_tree.source(db, loc.id.value);
+
+        let mut type_ref_builder = TypeRefBuilder::default();
+
+        let mut params = Vec::new();
+        if let Some(param_list) = src.param_list() {
+            for param in param_list.params() {
+                let type_ref = type_ref_builder.alloc_from_node_opt(param.ascribed_type().as_ref());
+                params.push(type_ref);
+            }
+        }
+
+        let ret_type = if let Some(type_ref) = src.ret_type().and_then(|rt| rt.type_ref()) {
+            type_ref_builder.alloc_from_node(&type_ref)
+        } else {
+            type_ref_builder.unit()
+        };
+
+        let (type_ref_map, type_ref_source_map) = type_ref_builder.finish();
+
+        Arc::new(FunctionData {
+            name: func.name.clone(),
+            params,
+            ret_type,
+            type_ref_map,
+            type_ref_source_map,
+            is_extern: func.is_extern,
+            visibility: item_tree[func.visibility].clone(),
+        })
+    }
+
+    pub fn name(&self) -> &Name {
+        &self.name
+    }
+
+    pub fn params(&self) -> &[LocalTypeRefId] {
+        &self.params
+    }
+
+    pub fn visibility(&self) -> &RawVisibility {
+        &self.visibility
+    }
+
+    pub fn ret_type(&self) -> &LocalTypeRefId {
+        &self.ret_type
+    }
+
+    pub fn type_ref_source_map(&self) -> &TypeRefSourceMap {
+        &self.type_ref_source_map
+    }
+
+    pub fn type_ref_map(&self) -> &TypeRefMap {
+        &self.type_ref_map
+    }
+}
+
+impl Function {
+    pub fn module(self, db: &dyn HirDatabase) -> Module {
+        Module {
+            id: self.id.lookup(db.upcast()).module,
+        }
+    }
+
+    pub fn file_id(self, db: &dyn HirDatabase) -> FileId {
+        self.id.lookup(db.upcast()).id.file_id
+    }
+
+    pub fn name(self, db: &dyn HirDatabase) -> Name {
+        self.data(db.upcast()).name.clone()
+    }
+
+    pub fn data(self, db: &dyn DefDatabase) -> Arc<FunctionData> {
+        db.fn_data(self.id)
+    }
+
+    pub fn body(self, db: &dyn HirDatabase) -> Arc<Body> {
+        db.body(self.id.into())
+    }
+
+    pub fn ty(self, db: &dyn HirDatabase) -> Ty {
+        // TODO: Add detection of cyclick types
+        db.type_for_def(self.into(), Namespace::Values).0
+    }
+
+    pub fn infer(self, db: &dyn HirDatabase) -> Arc<InferenceResult> {
+        db.infer(self.id.into())
+    }
+
+    pub fn is_extern(self, db: &dyn HirDatabase) -> bool {
+        db.fn_data(self.id).is_extern
+    }
+
+    pub(crate) fn body_source_map(self, db: &dyn HirDatabase) -> Arc<BodySourceMap> {
+        db.body_with_source_map(self.id.into()).1
+    }
+
+    pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
+        let body = self.body(db);
+        body.add_diagnostics(db, self.into(), sink);
+        let infer = self.infer(db);
+        infer.add_diagnostics(db, self, sink);
+        let validator = ExprValidator::new(self, db);
+        validator.validate_body(sink);
+    }
+}
+
+impl HasVisibility for Function {
+    fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+        self.data(db.upcast())
+            .visibility
+            .resolve(db.upcast(), &self.id.resolver(db.upcast()))
+    }
+}
diff --git a/crates/mun_hir/src/code_model/module.rs b/crates/mun_hir/src/code_model/module.rs
new file mode 100644
index 000000000..88e7c1a4d
--- /dev/null
+++ b/crates/mun_hir/src/code_model/module.rs
@@ -0,0 +1,108 @@
+use super::{Function, Package, Struct, TypeAlias};
+use crate::ids::{ItemDefinitionId, ModuleId};
+use crate::primitive_type::PrimitiveType;
+use crate::{DiagnosticSink, FileId, HirDatabase};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Module {
+    pub(crate) id: ModuleId,
+}
+
+impl From<ModuleId> for Module {
+    fn from(id: ModuleId) -> Self {
+        Module { id }
+    }
+}
+
+impl Module {
+    /// Returns the module that corresponds to the given file
+    pub fn from_file(db: &dyn HirDatabase, file: FileId) -> Option<Module> {
+        Package::all(db)
+            .iter()
+            .flat_map(|package| package.modules(db))
+            .find(|m| m.file_id(db) == Some(file))
+    }
+
+    /// Returns the file that defines the module
+    pub fn file_id(self, db: &dyn HirDatabase) -> Option<FileId> {
+        db.module_tree(self.id.package).modules[self.id.local_id].file
+    }
+
+    /// Returns all items declared in this module.
+    pub fn declarations(self, db: &dyn HirDatabase) -> Vec<ModuleDef> {
+        let package_defs = db.package_defs(self.id.package);
+        package_defs.modules[self.id.local_id]
+            .declarations()
+            .map(ModuleDef::from)
+            .collect()
+    }
+
+    /// Iterate over all diagnostics from this `Module` by placing them in the `sink`
+    pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
+        if let Some(file_id) = self.file_id(db) {
+            let item_tree = db.item_tree(file_id);
+            for diagnostics in item_tree.diagnostics.iter() {
+                diagnostics.add_to(db, &*item_tree, sink);
+            }
+        }
+        for decl in self.declarations(db) {
+            match decl {
+                ModuleDef::Function(f) => f.diagnostics(db, sink),
+                ModuleDef::Struct(s) => s.diagnostics(db, sink),
+                ModuleDef::TypeAlias(t) => t.diagnostics(db, sink),
+                _ => (),
+            }
+        }
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ModuleDef {
+    Module(Module),
+    Function(Function),
+    PrimitiveType(PrimitiveType),
+    Struct(Struct),
+    TypeAlias(TypeAlias),
+}
+
+impl From<Function> for ModuleDef {
+    fn from(t: Function) -> Self {
+        ModuleDef::Function(t)
+    }
+}
+
+impl From<PrimitiveType> for ModuleDef {
+    fn from(t: PrimitiveType) -> Self {
+        ModuleDef::PrimitiveType(t)
+    }
+}
+
+impl From<Struct> for ModuleDef {
+    fn from(t: Struct) -> Self {
+        ModuleDef::Struct(t)
+    }
+}
+
+impl From<TypeAlias> for ModuleDef {
+    fn from(t: TypeAlias) -> Self {
+        ModuleDef::TypeAlias(t)
+    }
+}
+
+impl From<Module> for ModuleDef {
+    fn from(m: Module) -> Self {
+        ModuleDef::Module(m)
+    }
+}
+
+impl From<ItemDefinitionId> for ModuleDef {
+    fn from(id: ItemDefinitionId) -> Self {
+        match id {
+            ItemDefinitionId::ModuleId(id) => Module { id }.into(),
+            ItemDefinitionId::FunctionId(id) => Function { id }.into(),
+            ItemDefinitionId::StructId(id) => Struct { id }.into(),
+            ItemDefinitionId::TypeAliasId(id) => TypeAlias { id }.into(),
+            ItemDefinitionId::PrimitiveType(id) => id.into(),
+        }
+    }
+}
diff --git a/crates/mun_hir/src/code_model/package.rs b/crates/mun_hir/src/code_model/package.rs
new file mode 100644
index 000000000..5959667f6
--- /dev/null
+++ b/crates/mun_hir/src/code_model/package.rs
@@ -0,0 +1,44 @@
+use super::Module;
+use crate::ids::PackageId;
+use crate::{HirDatabase, ModuleId};
+
+/// A `Package` describes a single package.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Package {
+    pub(crate) id: PackageId,
+}
+
+impl Package {
+    /// Returns all the packages defined in the database
+    pub fn all(_db: &dyn HirDatabase) -> Vec<Package> {
+        // TODO: Currently we assume there is only a single package with ID 0. This has to be
+        // implemented when have multiple packages. See CrateGraph in rust-analyzer.
+        vec![Package { id: PackageId(0) }]
+    }
+
+    /// Returns the root module of the package (represented by the `mod.rs` in the source root)
+    pub fn root_module(self, db: &dyn HirDatabase) -> Module {
+        let module_tree = db.module_tree(self.id);
+        Module {
+            id: ModuleId {
+                package: self.id,
+                local_id: module_tree.root,
+            },
+        }
+    }
+
+    /// Returns all the modules in the package
+    pub fn modules(self, db: &dyn HirDatabase) -> Vec<Module> {
+        let module_tree = db.module_tree(self.id);
+        module_tree
+            .modules
+            .iter()
+            .map(|(idx, _)| Module {
+                id: ModuleId {
+                    package: self.id,
+                    local_id: idx,
+                },
+            })
+            .collect()
+    }
+}
diff --git a/crates/mun_hir/src/code_model/src.rs b/crates/mun_hir/src/code_model/src.rs
index 00fad3435..f2db58b47 100644
--- a/crates/mun_hir/src/code_model/src.rs
+++ b/crates/mun_hir/src/code_model/src.rs
@@ -1,7 +1,7 @@
 use crate::code_model::{Function, Struct, StructField, TypeAlias};
-use crate::ids::Lookup;
+use crate::ids::{AssocItemLoc, Lookup};
 use crate::in_file::InFile;
-use crate::item_tree::ItemTreeNode;
+use crate::item_tree::{ItemTreeId, ItemTreeNode};
 use crate::{DefDatabase, ItemLoc};
 use mun_syntax::ast;
 
@@ -10,22 +10,38 @@ pub trait HasSource {
     fn source(&self, db: &dyn DefDatabase) -> InFile<Self::Ast>;
 }
 
-impl<N: ItemTreeNode> HasSource for ItemLoc<N> {
+impl<N: ItemTreeNode> HasSource for ItemTreeId<N> {
     type Ast = N::Source;
 
     fn source(&self, db: &dyn DefDatabase) -> InFile<Self::Ast> {
-        let tree = db.item_tree(self.id.file_id);
-        let ast_id_map = db.ast_id_map(self.id.file_id);
-        let root = db.parse(self.id.file_id);
-        let node = &tree[self.id.value];
+        let tree = db.item_tree(self.file_id);
+        let ast_id_map = db.ast_id_map(self.file_id);
+        let root = db.parse(self.file_id);
+        let node = &tree[self.value];
 
         InFile::new(
-            self.id.file_id,
+            self.file_id,
             ast_id_map.get(node.ast_id()).to_node(&root.syntax_node()),
         )
     }
 }
 
+impl<N: ItemTreeNode> HasSource for ItemLoc<N> {
+    type Ast = N::Source;
+
+    fn source(&self, db: &dyn DefDatabase) -> InFile<Self::Ast> {
+        self.id.source(db)
+    }
+}
+
+impl<N: ItemTreeNode> HasSource for AssocItemLoc<N> {
+    type Ast = N::Source;
+
+    fn source(&self, db: &dyn DefDatabase) -> InFile<Self::Ast> {
+        self.id.source(db)
+    }
+}
+
 impl HasSource for Function {
     type Ast = ast::FunctionDef;
     fn source(&self, db: &dyn DefDatabase) -> InFile<Self::Ast> {
diff --git a/crates/mun_hir/src/adt.rs b/crates/mun_hir/src/code_model/struct.rs
similarity index 50%
rename from crates/mun_hir/src/adt.rs
rename to crates/mun_hir/src/code_model/struct.rs
index e9d139e6f..1ae6219c2 100644
--- a/crates/mun_hir/src/adt.rs
+++ b/crates/mun_hir/src/code_model/struct.rs
@@ -1,16 +1,107 @@
-use std::{fmt, sync::Arc};
-
-use crate::type_ref::{LocalTypeRefId, TypeRefBuilder, TypeRefMap, TypeRefSourceMap};
+use super::Module;
 use crate::{
     arena::{Arena, Idx},
-    ids::{StructId, TypeAliasId},
-    AsName, DefDatabase, Name,
+    ids::{Lookup, StructId},
+    name::AsName,
+    name_resolution::Namespace,
+    ty::lower::LowerBatchResult,
+    type_ref::{LocalTypeRefId, TypeRefBuilder, TypeRefMap, TypeRefSourceMap},
+    DefDatabase, DiagnosticSink, FileId, HasVisibility, HirDatabase, Name, Ty, Visibility,
 };
-use mun_syntax::ast::{self, NameOwner, TypeAscriptionOwner};
+use mun_syntax::{
+    ast,
+    ast::{NameOwner, TypeAscriptionOwner},
+};
+use std::{fmt, sync::Arc};
+
+use crate::resolve::HasResolver;
+use crate::visibility::RawVisibility;
+pub use ast::StructMemoryKind;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Struct {
+    pub(crate) id: StructId,
+}
+
+impl From<StructId> for Struct {
+    fn from(id: StructId) -> Self {
+        Struct { id }
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct StructField {
+    pub(crate) parent: Struct,
+    pub(crate) id: LocalStructFieldId,
+}
+
+impl StructField {
+    pub fn ty(self, db: &dyn HirDatabase) -> Ty {
+        let data = self.parent.data(db.upcast());
+        let type_ref_id = data.fields[self.id].type_ref;
+        let lower = self.parent.lower(db);
+        lower[type_ref_id].clone()
+    }
+
+    pub fn name(self, db: &dyn HirDatabase) -> Name {
+        self.parent.data(db.upcast()).fields[self.id].name.clone()
+    }
+
+    pub fn id(self) -> LocalStructFieldId {
+        self.id
+    }
+}
+
+impl Struct {
+    pub fn module(self, db: &dyn HirDatabase) -> Module {
+        Module {
+            id: self.id.lookup(db.upcast()).module,
+        }
+    }
+
+    pub fn file_id(self, db: &dyn HirDatabase) -> FileId {
+        self.id.lookup(db.upcast()).id.file_id
+    }
+
+    pub fn data(self, db: &dyn DefDatabase) -> Arc<StructData> {
+        db.struct_data(self.id)
+    }
+
+    pub fn name(self, db: &dyn HirDatabase) -> Name {
+        self.data(db.upcast()).name.clone()
+    }
+
+    pub fn fields(self, db: &dyn HirDatabase) -> Vec<StructField> {
+        self.data(db.upcast())
+            .fields
+            .iter()
+            .map(|(id, _)| StructField { parent: self, id })
+            .collect()
+    }
+
+    pub fn field(self, db: &dyn HirDatabase, name: &Name) -> Option<StructField> {
+        self.data(db.upcast())
+            .fields
+            .iter()
+            .find(|(_, data)| data.name == *name)
+            .map(|(id, _)| StructField { parent: self, id })
+    }
+
+    pub fn ty(self, db: &dyn HirDatabase) -> Ty {
+        // TODO: Add detection of cyclick types
+        db.type_for_def(self.into(), Namespace::Types).0
+    }
 
-pub use mun_syntax::ast::StructMemoryKind;
+    pub fn lower(self, db: &dyn HirDatabase) -> Arc<LowerBatchResult> {
+        db.lower_struct(self)
+    }
 
-use crate::ids::Lookup;
+    pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
+        let data = self.data(db.upcast());
+        let lower = self.lower(db);
+        lower.add_diagnostics(db, self.file_id(db), data.type_ref_source_map(), sink);
+    }
+}
 
 /// A single field of a record
 /// ```mun
@@ -54,6 +145,7 @@ pub type LocalStructFieldId = Idx<StructFieldData>;
 #[derive(Debug, PartialEq, Eq)]
 pub struct StructData {
     pub name: Name,
+    pub visibility: RawVisibility,
     pub fields: Arena<StructFieldData>,
     pub kind: StructKind,
     pub memory_kind: StructMemoryKind,
@@ -66,7 +158,7 @@ impl StructData {
         let loc = id.lookup(db);
         let item_tree = db.item_tree(loc.id.file_id);
         let strukt = &item_tree[loc.id.value];
-        let src = item_tree.source(db, loc.id);
+        let src = item_tree.source(db, loc.id.value);
 
         let memory_kind = src
             .memory_type_specifier()
@@ -99,9 +191,12 @@ impl StructData {
             ast::StructKind::Unit => (Arena::default(), StructKind::Unit),
         };
 
+        let visibility = item_tree[strukt.visibility].clone();
+
         let (type_ref_map, type_ref_source_map) = type_ref_builder.finish();
         Arc::new(StructData {
             name: strukt.name.clone(),
+            visibility,
             fields,
             kind,
             memory_kind,
@@ -119,39 +214,10 @@ impl StructData {
     }
 }
 
-#[derive(Debug, PartialEq, Eq)]
-pub struct TypeAliasData {
-    pub name: Name,
-    pub type_ref_id: LocalTypeRefId,
-    type_ref_map: TypeRefMap,
-    type_ref_source_map: TypeRefSourceMap,
-}
-impl TypeAliasData {
-    pub(crate) fn type_alias_data_query(
-        db: &dyn DefDatabase,
-        id: TypeAliasId,
-    ) -> Arc<TypeAliasData> {
-        let loc = id.lookup(db);
-        let item_tree = db.item_tree(loc.id.file_id);
-        let alias = &item_tree[loc.id.value];
-        let src = item_tree.source(db, loc.id);
-        let mut type_ref_builder = TypeRefBuilder::default();
-        let type_ref_opt = src.type_ref();
-        let type_ref_id = type_ref_builder.alloc_from_node_opt(type_ref_opt.as_ref());
-        let (type_ref_map, type_ref_source_map) = type_ref_builder.finish();
-        Arc::new(TypeAliasData {
-            name: alias.name.clone(),
-            type_ref_id,
-            type_ref_map,
-            type_ref_source_map,
-        })
-    }
-
-    pub fn type_ref_source_map(&self) -> &TypeRefSourceMap {
-        &self.type_ref_source_map
-    }
-
-    pub fn type_ref_map(&self) -> &TypeRefMap {
-        &self.type_ref_map
+impl HasVisibility for Struct {
+    fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+        self.data(db.upcast())
+            .visibility
+            .resolve(db.upcast(), &self.id.resolver(db.upcast()))
     }
 }
diff --git a/crates/mun_hir/src/code_model/type_alias.rs b/crates/mun_hir/src/code_model/type_alias.rs
new file mode 100644
index 000000000..1ca24b046
--- /dev/null
+++ b/crates/mun_hir/src/code_model/type_alias.rs
@@ -0,0 +1,109 @@
+use std::sync::Arc;
+
+use crate::{
+    ids::Lookup,
+    ids::TypeAliasId,
+    type_ref::{LocalTypeRefId, TypeRefBuilder, TypeRefMap, TypeRefSourceMap},
+    visibility::RawVisibility,
+    DefDatabase, DiagnosticSink, FileId, HasVisibility, HirDatabase, Name, Visibility,
+};
+
+use super::Module;
+use crate::expr::validator::TypeAliasValidator;
+use crate::resolve::HasResolver;
+use crate::ty::lower::LowerBatchResult;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct TypeAlias {
+    pub(crate) id: TypeAliasId,
+}
+
+impl From<TypeAliasId> for TypeAlias {
+    fn from(id: TypeAliasId) -> Self {
+        TypeAlias { id }
+    }
+}
+
+impl TypeAlias {
+    pub fn module(self, db: &dyn HirDatabase) -> Module {
+        Module {
+            id: self.id.lookup(db.upcast()).module,
+        }
+    }
+
+    pub fn file_id(self, db: &dyn HirDatabase) -> FileId {
+        self.id.lookup(db.upcast()).id.file_id
+    }
+
+    pub fn data(self, db: &dyn DefDatabase) -> Arc<TypeAliasData> {
+        db.type_alias_data(self.id)
+    }
+
+    pub fn name(self, db: &dyn HirDatabase) -> Name {
+        self.data(db.upcast()).name.clone()
+    }
+
+    pub fn type_ref(self, db: &dyn HirDatabase) -> LocalTypeRefId {
+        self.data(db.upcast()).type_ref_id
+    }
+
+    pub fn lower(self, db: &dyn HirDatabase) -> Arc<LowerBatchResult> {
+        db.lower_type_alias(self)
+    }
+
+    pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
+        let data = self.data(db.upcast());
+        let lower = self.lower(db);
+        lower.add_diagnostics(db, self.file_id(db), data.type_ref_source_map(), sink);
+
+        let validator = TypeAliasValidator::new(self, db);
+        validator.validate_target_type_existence(sink);
+    }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct TypeAliasData {
+    pub name: Name,
+    pub visibility: RawVisibility,
+    pub type_ref_id: LocalTypeRefId,
+    type_ref_map: TypeRefMap,
+    type_ref_source_map: TypeRefSourceMap,
+}
+impl TypeAliasData {
+    pub(crate) fn type_alias_data_query(
+        db: &dyn DefDatabase,
+        id: TypeAliasId,
+    ) -> Arc<TypeAliasData> {
+        let loc = id.lookup(db);
+        let item_tree = db.item_tree(loc.id.file_id);
+        let alias = &item_tree[loc.id.value];
+        let src = item_tree.source(db, loc.id.value);
+        let mut type_ref_builder = TypeRefBuilder::default();
+        let type_ref_opt = src.type_ref();
+        let type_ref_id = type_ref_builder.alloc_from_node_opt(type_ref_opt.as_ref());
+        let (type_ref_map, type_ref_source_map) = type_ref_builder.finish();
+        Arc::new(TypeAliasData {
+            name: alias.name.clone(),
+            visibility: item_tree[alias.visibility].clone(),
+            type_ref_id,
+            type_ref_map,
+            type_ref_source_map,
+        })
+    }
+
+    pub fn type_ref_source_map(&self) -> &TypeRefSourceMap {
+        &self.type_ref_source_map
+    }
+
+    pub fn type_ref_map(&self) -> &TypeRefMap {
+        &self.type_ref_map
+    }
+}
+
+impl HasVisibility for TypeAlias {
+    fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+        self.data(db.upcast())
+            .visibility
+            .resolve(db.upcast(), &self.id.resolver(db.upcast()))
+    }
+}
diff --git a/crates/mun_hir/src/db.rs b/crates/mun_hir/src/db.rs
index f890fd02c..81b4998cd 100644
--- a/crates/mun_hir/src/db.rs
+++ b/crates/mun_hir/src/db.rs
@@ -1,19 +1,20 @@
 #![allow(clippy::type_repetition_in_bounds)]
 
-use crate::ids::FunctionId;
+use crate::expr::BodySourceMap;
+use crate::ids::{DefWithBodyId, FunctionId, PackageId};
 use crate::input::{SourceRoot, SourceRootId};
 use crate::item_tree::{self, ItemTree};
+use crate::module_tree::ModuleTree;
 use crate::name_resolution::Namespace;
+use crate::package_defs::PackageDefs;
 use crate::ty::lower::LowerBatchResult;
 use crate::ty::{CallableDef, FnSig, Ty, TypableDef};
 use crate::{
-    adt::{StructData, TypeAliasData},
-    code_model::{DefWithBody, FunctionData, ModuleData},
+    code_model::{FunctionData, StructData, TypeAliasData},
     ids,
     line_index::LineIndex,
-    name_resolution::ModuleScope,
     ty::InferenceResult,
-    AstIdMap, ExprScopes, FileId, Struct, TypeAlias,
+    AstIdMap, Body, ExprScopes, FileId, Struct, TypeAlias,
 };
 use mun_syntax::{ast, Parse, SourceFile};
 use mun_target::abi;
@@ -45,6 +46,14 @@ pub trait SourceDatabase: salsa::Database {
     #[salsa::input]
     fn source_root(&self, id: SourceRootId) -> Arc<SourceRoot>;
 
+    /// The source root for a specific package
+    #[salsa::input]
+    fn package_source_root(&self, package: PackageId) -> SourceRootId;
+
+    /// For a package, returns its hierarchy of modules.
+    #[salsa::invoke(ModuleTree::module_tree_query)]
+    fn module_tree(&self, package: PackageId) -> Arc<ModuleTree>;
+
     /// Returns the line index of a file
     #[salsa::invoke(line_index_query)]
     fn line_index(&self, file_id: FileId) -> Arc<LineIndex>;
@@ -77,6 +86,8 @@ pub trait InternDatabase: SourceDatabase {
 
 #[salsa::query_group(DefDatabaseStorage)]
 pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
+    /// Returns the `ItemTree` for a specific file. An `ItemTree` represents all the top level
+    /// declarations within a file.
     #[salsa::invoke(item_tree::ItemTree::item_tree_query)]
     fn item_tree(&self, file_id: FileId) -> Arc<ItemTree>;
 
@@ -89,9 +100,19 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
     #[salsa::invoke(crate::FunctionData::fn_data_query)]
     fn fn_data(&self, func: FunctionId) -> Arc<FunctionData>;
 
-    /// Returns the module data of the specified file
-    #[salsa::invoke(crate::code_model::ModuleData::module_data_query)]
-    fn module_data(&self, file_id: FileId) -> Arc<ModuleData>;
+    /// Returns the `PackageDefs` for the specified `PackageId`. The `PackageDefs` contains all
+    /// resolved items defined for every module in the package.
+    #[salsa::invoke(crate::package_defs::PackageDefs::package_def_map_query)]
+    fn package_defs(&self, package_id: PackageId) -> Arc<PackageDefs>;
+
+    #[salsa::invoke(Body::body_query)]
+    fn body(&self, def: DefWithBodyId) -> Arc<Body>;
+
+    #[salsa::invoke(Body::body_with_source_map_query)]
+    fn body_with_source_map(&self, def: DefWithBodyId) -> (Arc<Body>, Arc<BodySourceMap>);
+
+    #[salsa::invoke(ExprScopes::expr_scopes_query)]
+    fn expr_scopes(&self, def: DefWithBodyId) -> Arc<ExprScopes>;
 }
 
 #[salsa::query_group(HirDatabaseStorage)]
@@ -104,14 +125,8 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
     #[salsa::invoke(target_data_layout)]
     fn target_data_layout(&self) -> Arc<abi::TargetDataLayout>;
 
-    #[salsa::invoke(ExprScopes::expr_scopes_query)]
-    fn expr_scopes(&self, def: DefWithBody) -> Arc<ExprScopes>;
-
-    #[salsa::invoke(crate::name_resolution::module_scope_query)]
-    fn module_scope(&self, file_id: FileId) -> Arc<ModuleScope>;
-
     #[salsa::invoke(crate::ty::infer_query)]
-    fn infer(&self, def: DefWithBody) -> Arc<InferenceResult>;
+    fn infer(&self, def: DefWithBodyId) -> Arc<InferenceResult>;
 
     #[salsa::invoke(crate::ty::lower::lower_struct_query)]
     fn lower_struct(&self, def: Struct) -> Arc<LowerBatchResult>;
@@ -125,15 +140,6 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
     #[salsa::invoke(crate::ty::type_for_def)]
     #[salsa::cycle(crate::ty::type_for_cycle_recover)]
     fn type_for_def(&self, def: TypableDef, ns: Namespace) -> (Ty, bool);
-
-    #[salsa::invoke(crate::expr::body_hir_query)]
-    fn body(&self, def: DefWithBody) -> Arc<crate::expr::Body>;
-
-    #[salsa::invoke(crate::expr::body_with_source_map_query)]
-    fn body_with_source_map(
-        &self,
-        def: DefWithBody,
-    ) -> (Arc<crate::expr::Body>, Arc<crate::expr::BodySourceMap>);
 }
 
 fn parse_query(db: &dyn AstDatabase, file_id: FileId) -> Parse<SourceFile> {
diff --git a/crates/mun_hir/src/diagnostics.rs b/crates/mun_hir/src/diagnostics.rs
index 3e0808e62..70e787b73 100644
--- a/crates/mun_hir/src/diagnostics.rs
+++ b/crates/mun_hir/src/diagnostics.rs
@@ -1,4 +1,4 @@
-use crate::adt::StructKind;
+use crate::code_model::StructKind;
 use crate::in_file::InFile;
 use crate::{FileId, HirDatabase, IntTy, Name, Ty};
 use mun_syntax::{ast, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange};
@@ -137,6 +137,26 @@ impl Diagnostic for CyclicType {
     }
 }
 
+#[derive(Debug)]
+pub struct PrivateAccess {
+    pub file: FileId,
+    pub expr: SyntaxNodePtr,
+}
+
+impl Diagnostic for PrivateAccess {
+    fn message(&self) -> String {
+        "access of private type".to_string()
+    }
+
+    fn source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.expr)
+    }
+
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
+
 #[derive(Debug)]
 pub struct ExpectedFunction {
     pub file: FileId,
diff --git a/crates/mun_hir/src/expr.rs b/crates/mun_hir/src/expr.rs
index 459dc8921..1952a495b 100644
--- a/crates/mun_hir/src/expr.rs
+++ b/crates/mun_hir/src/expr.rs
@@ -2,7 +2,7 @@ use crate::{
     arena::map::ArenaMap,
     arena::{Arena, Idx},
     code_model::DefWithBody,
-    FileId, HirDatabase, Name, Path,
+    DefDatabase, FileId, HirDatabase, Name, Path,
 };
 
 //pub use mun_syntax::ast::PrefixOp as UnaryOp;
@@ -18,10 +18,10 @@ use std::ops::Index;
 use std::sync::Arc;
 
 pub use self::scope::ExprScopes;
-use crate::builtin_type::{BuiltinFloat, BuiltinInt};
 use crate::diagnostics::DiagnosticSink;
+use crate::ids::{DefWithBodyId, Lookup};
 use crate::in_file::InFile;
-use crate::resolve::Resolver;
+use crate::primitive_type::{PrimitiveFloat, PrimitiveInt};
 use std::borrow::Cow;
 use std::str::FromStr;
 
@@ -39,7 +39,7 @@ pub enum ExprDiagnostic {
 /// The body of an item (function, const etc.).
 #[derive(Debug, Eq, PartialEq)]
 pub struct Body {
-    owner: DefWithBody,
+    owner: DefWithBodyId,
     exprs: Arena<Expr>,
     pats: Arena<Pat>,
     type_refs: TypeRefMap,
@@ -58,6 +58,29 @@ pub struct Body {
 }
 
 impl Body {
+    pub(crate) fn body_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc<Body> {
+        db.body_with_source_map(def).0
+    }
+
+    pub(crate) fn body_with_source_map_query(
+        db: &dyn DefDatabase,
+        def: DefWithBodyId,
+    ) -> (Arc<Body>, Arc<BodySourceMap>) {
+        let mut collector;
+
+        match def {
+            DefWithBodyId::FunctionId(f) => {
+                let f = f.lookup(db);
+                let src = f.source(db);
+                collector = ExprCollector::new(def, src.file_id, db);
+                collector.collect_fn_body(&src.value)
+            }
+        }
+
+        let (body, source_map) = collector.finish();
+        (Arc::new(body), Arc::new(source_map))
+    }
+
     pub fn params(&self) -> &[(PatId, LocalTypeRefId)] {
         &self.params
     }
@@ -66,7 +89,7 @@ impl Body {
         self.body_expr
     }
 
-    pub fn owner(&self) -> DefWithBody {
+    pub fn owner(&self) -> DefWithBodyId {
         self.owner
     }
 
@@ -229,7 +252,7 @@ pub struct LiteralInt {
 
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub enum LiteralIntKind {
-    Suffixed(BuiltinInt),
+    Suffixed(PrimitiveInt),
     Unsuffixed,
 }
 
@@ -241,7 +264,7 @@ pub struct LiteralFloat {
 
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub enum LiteralFloatKind {
-    Suffixed(BuiltinFloat),
+    Suffixed(PrimitiveFloat),
     Unsuffixed,
 }
 
@@ -429,8 +452,8 @@ impl Pat {
 // Queries
 
 pub(crate) struct ExprCollector<'a> {
-    db: &'a dyn HirDatabase,
-    owner: DefWithBody,
+    db: &'a dyn DefDatabase,
+    owner: DefWithBodyId,
     exprs: Arena<Expr>,
     pats: Arena<Pat>,
     source_map: BodySourceMap,
@@ -443,7 +466,7 @@ pub(crate) struct ExprCollector<'a> {
 }
 
 impl<'a> ExprCollector<'a> {
-    pub fn new(owner: DefWithBody, file_id: FileId, db: &'a dyn HirDatabase) -> Self {
+    pub fn new(owner: DefWithBodyId, file_id: FileId, db: &'a dyn DefDatabase) -> Self {
         ExprCollector {
             owner,
             db,
@@ -907,49 +930,6 @@ impl<'a> ExprCollector<'a> {
     }
 }
 
-pub(crate) fn body_with_source_map_query(
-    db: &dyn HirDatabase,
-    def: DefWithBody,
-) -> (Arc<Body>, Arc<BodySourceMap>) {
-    let mut collector;
-
-    match def {
-        DefWithBody::Function(ref f) => {
-            let src = f.source(db.upcast());
-            collector = ExprCollector::new(def, src.file_id, db);
-            collector.collect_fn_body(&src.value)
-        }
-    }
-
-    let (body, source_map) = collector.finish();
-    (Arc::new(body), Arc::new(source_map))
-}
-
-pub(crate) fn body_hir_query(db: &dyn HirDatabase, def: DefWithBody) -> Arc<Body> {
-    db.body_with_source_map(def).0
-}
-
-// needs arbitrary_self_types to be a method... or maybe move to the def?
-pub fn resolver_for_expr(body: Arc<Body>, db: &dyn HirDatabase, expr_id: ExprId) -> Resolver {
-    let scopes = db.expr_scopes(body.owner);
-    resolver_for_scope(body, db, scopes.scope_for(expr_id))
-}
-
-#[allow(clippy::needless_collect)] // false positive https://github.com/rust-lang/rust-clippy/issues/5991
-pub(crate) fn resolver_for_scope(
-    body: Arc<Body>,
-    db: &dyn HirDatabase,
-    scope_id: Option<scope::LocalScopeId>,
-) -> Resolver {
-    let mut r = body.owner.resolver(db);
-    let scopes = db.expr_scopes(body.owner);
-    let scope_chain = scopes.scope_chain(scope_id).collect::<Vec<_>>();
-    for scope in scope_chain.into_iter().rev() {
-        r = r.push_expr_scope(Arc::clone(&scopes), scope);
-    }
-    r
-}
-
 /// Removes any underscores from a string if present
 fn strip_underscores(s: &str) -> Cow<str> {
     if s.contains('_') {
@@ -974,7 +954,7 @@ fn filtered_float_lit(str: &str, suffix: Option<&str>, base: u32) -> (Literal, V
         errors.push(LiteralError::NonDecimalFloat(base));
     }
     let kind = match suffix {
-        Some(suf) => match BuiltinFloat::from_suffix(suf) {
+        Some(suf) => match PrimitiveFloat::from_suffix(suf) {
             Some(suf) => LiteralFloatKind::Suffixed(suf),
             None => {
                 errors.push(LiteralError::InvalidFloatSuffix(SmolStr::new(suf)));
@@ -1006,11 +986,11 @@ fn integer_lit(str: &str, suffix: Option<&str>) -> (Literal, Vec<LiteralError>)
     let mut errors = Vec::new();
 
     let kind = match suffix {
-        Some(suf) => match BuiltinInt::from_suffix(suf) {
+        Some(suf) => match PrimitiveInt::from_suffix(suf) {
             Some(ty) => LiteralIntKind::Suffixed(ty),
             None => {
                 // 1f32 is a valid number, but its an integer disguised as a float
-                if BuiltinFloat::from_suffix(suf).is_some() {
+                if PrimitiveFloat::from_suffix(suf).is_some() {
                     return filtered_float_lit(&str, suffix, base);
                 }
 
@@ -1050,9 +1030,9 @@ fn integer_lit(str: &str, suffix: Option<&str>) -> (Literal, Vec<LiteralError>)
 
 #[cfg(test)]
 mod test {
-    use crate::builtin_type::{BuiltinFloat, BuiltinInt};
     use crate::expr::{float_lit, LiteralError, LiteralFloat, LiteralFloatKind};
     use crate::expr::{integer_lit, LiteralInt, LiteralIntKind};
+    use crate::primitive_type::{PrimitiveFloat, PrimitiveInt};
     use crate::Literal;
     use mun_syntax::SmolStr;
 
@@ -1145,7 +1125,7 @@ mod test {
             integer_lit("123", Some("i8")),
             (
                 Literal::Int(LiteralInt {
-                    kind: LiteralIntKind::Suffixed(BuiltinInt::I8),
+                    kind: LiteralIntKind::Suffixed(PrimitiveInt::I8),
                     value: 123
                 }),
                 vec![]
@@ -1156,7 +1136,7 @@ mod test {
             integer_lit("1234", Some("i16")),
             (
                 Literal::Int(LiteralInt {
-                    kind: LiteralIntKind::Suffixed(BuiltinInt::I16),
+                    kind: LiteralIntKind::Suffixed(PrimitiveInt::I16),
                     value: 1234
                 }),
                 vec![]
@@ -1167,7 +1147,7 @@ mod test {
             integer_lit("1234", Some("i32")),
             (
                 Literal::Int(LiteralInt {
-                    kind: LiteralIntKind::Suffixed(BuiltinInt::I32),
+                    kind: LiteralIntKind::Suffixed(PrimitiveInt::I32),
                     value: 1234
                 }),
                 vec![]
@@ -1178,7 +1158,7 @@ mod test {
             integer_lit("1234", Some("i64")),
             (
                 Literal::Int(LiteralInt {
-                    kind: LiteralIntKind::Suffixed(BuiltinInt::I64),
+                    kind: LiteralIntKind::Suffixed(PrimitiveInt::I64),
                     value: 1234
                 }),
                 vec![]
@@ -1189,7 +1169,7 @@ mod test {
             integer_lit("1234", Some("i128")),
             (
                 Literal::Int(LiteralInt {
-                    kind: LiteralIntKind::Suffixed(BuiltinInt::I128),
+                    kind: LiteralIntKind::Suffixed(PrimitiveInt::I128),
                     value: 1234
                 }),
                 vec![]
@@ -1200,7 +1180,7 @@ mod test {
             integer_lit("1234", Some("isize")),
             (
                 Literal::Int(LiteralInt {
-                    kind: LiteralIntKind::Suffixed(BuiltinInt::ISIZE),
+                    kind: LiteralIntKind::Suffixed(PrimitiveInt::ISIZE),
                     value: 1234
                 }),
                 vec![]
@@ -1211,7 +1191,7 @@ mod test {
             integer_lit("123", Some("u8")),
             (
                 Literal::Int(LiteralInt {
-                    kind: LiteralIntKind::Suffixed(BuiltinInt::U8),
+                    kind: LiteralIntKind::Suffixed(PrimitiveInt::U8),
                     value: 123
                 }),
                 vec![]
@@ -1222,7 +1202,7 @@ mod test {
             integer_lit("1234", Some("u16")),
             (
                 Literal::Int(LiteralInt {
-                    kind: LiteralIntKind::Suffixed(BuiltinInt::U16),
+                    kind: LiteralIntKind::Suffixed(PrimitiveInt::U16),
                     value: 1234
                 }),
                 vec![]
@@ -1233,7 +1213,7 @@ mod test {
             integer_lit("1234", Some("u32")),
             (
                 Literal::Int(LiteralInt {
-                    kind: LiteralIntKind::Suffixed(BuiltinInt::U32),
+                    kind: LiteralIntKind::Suffixed(PrimitiveInt::U32),
                     value: 1234
                 }),
                 vec![]
@@ -1244,7 +1224,7 @@ mod test {
             integer_lit("1234", Some("u64")),
             (
                 Literal::Int(LiteralInt {
-                    kind: LiteralIntKind::Suffixed(BuiltinInt::U64),
+                    kind: LiteralIntKind::Suffixed(PrimitiveInt::U64),
                     value: 1234
                 }),
                 vec![]
@@ -1255,7 +1235,7 @@ mod test {
             integer_lit("1234", Some("u128")),
             (
                 Literal::Int(LiteralInt {
-                    kind: LiteralIntKind::Suffixed(BuiltinInt::U128),
+                    kind: LiteralIntKind::Suffixed(PrimitiveInt::U128),
                     value: 1234
                 }),
                 vec![]
@@ -1266,7 +1246,7 @@ mod test {
             integer_lit("1234", Some("usize")),
             (
                 Literal::Int(LiteralInt {
-                    kind: LiteralIntKind::Suffixed(BuiltinInt::USIZE),
+                    kind: LiteralIntKind::Suffixed(PrimitiveInt::USIZE),
                     value: 1234
                 }),
                 vec![]
@@ -1277,7 +1257,7 @@ mod test {
             integer_lit("1", Some("f32")),
             (
                 Literal::Float(LiteralFloat {
-                    kind: LiteralFloatKind::Suffixed(BuiltinFloat::F32),
+                    kind: LiteralFloatKind::Suffixed(PrimitiveFloat::F32),
                     value: 1.0
                 }),
                 vec![]
@@ -1288,7 +1268,7 @@ mod test {
             integer_lit("0x1", Some("f32")),
             (
                 Literal::Float(LiteralFloat {
-                    kind: LiteralFloatKind::Suffixed(BuiltinFloat::F32),
+                    kind: LiteralFloatKind::Suffixed(PrimitiveFloat::F32),
                     value: 0.0
                 }),
                 vec![LiteralError::NonDecimalFloat(16)]
@@ -1346,7 +1326,7 @@ mod test {
             float_lit("1234.1234e2", Some("f32")),
             (
                 Literal::Float(LiteralFloat {
-                    kind: LiteralFloatKind::Suffixed(BuiltinFloat::F32),
+                    kind: LiteralFloatKind::Suffixed(PrimitiveFloat::F32),
                     value: 123412.34
                 }),
                 vec![]
@@ -1357,7 +1337,7 @@ mod test {
             float_lit("1234.1234e2", Some("f64")),
             (
                 Literal::Float(LiteralFloat {
-                    kind: LiteralFloatKind::Suffixed(BuiltinFloat::F64),
+                    kind: LiteralFloatKind::Suffixed(PrimitiveFloat::F64),
                     value: 123412.34
                 }),
                 vec![]
diff --git a/crates/mun_hir/src/expr/scope.rs b/crates/mun_hir/src/expr/scope.rs
index 8940bcfc7..a758a460f 100644
--- a/crates/mun_hir/src/expr/scope.rs
+++ b/crates/mun_hir/src/expr/scope.rs
@@ -1,25 +1,24 @@
-use crate::code_model::DefWithBody;
 use crate::expr::{Expr, Pat, PatId, Statement};
+use crate::ids::DefWithBodyId;
 use crate::{
     arena::{Arena, Idx},
     expr::{Body, ExprId},
-    HirDatabase, Name,
+    DefDatabase, Name,
 };
 use rustc_hash::FxHashMap;
 use std::sync::Arc;
 
 /// The ID of a scope in an `ExprScopes`
-pub(crate) type LocalScopeId = Idx<ScopeData>;
+pub type LocalScopeId = Idx<ScopeData>;
 
 #[derive(Debug, PartialEq, Eq)]
 pub struct ExprScopes {
-    body: Arc<Body>,
     scopes: Arena<ScopeData>,
     scope_by_expr: FxHashMap<ExprId, LocalScopeId>,
 }
 
 #[derive(Debug, PartialEq, Eq)]
-pub(crate) struct ScopeEntry {
+pub struct ScopeEntry {
     name: Name,
     pat: PatId,
 }
@@ -35,26 +34,24 @@ impl ScopeEntry {
 }
 
 #[derive(Debug, PartialEq, Eq)]
-pub(crate) struct ScopeData {
+pub struct ScopeData {
     parent: Option<LocalScopeId>,
     entries: Vec<ScopeEntry>,
 }
 
 impl ExprScopes {
-    pub(crate) fn expr_scopes_query(db: &dyn HirDatabase, def: DefWithBody) -> Arc<ExprScopes> {
+    pub(crate) fn expr_scopes_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc<ExprScopes> {
         let body = db.body(def);
-        let res = ExprScopes::new(body);
-        Arc::new(res)
+        Arc::new(ExprScopes::new(&*body))
     }
 
-    fn new(body: Arc<Body>) -> ExprScopes {
+    fn new(body: &Body) -> ExprScopes {
         let mut scopes = ExprScopes {
-            body: body.clone(),
             scopes: Arena::default(),
             scope_by_expr: FxHashMap::default(),
         };
         let root = scopes.root_scope();
-        scopes.add_params_bindings(root, body.params().iter().map(|p| &p.0));
+        scopes.add_params_bindings(body, root, body.params().iter().map(|p| &p.0));
         compute_expr_scopes(body.body_expr(), &body, &mut scopes, root);
         scopes
     }
@@ -109,11 +106,11 @@ impl ExprScopes {
 
     fn add_params_bindings<'a>(
         &mut self,
+        body: &Body,
         scope: LocalScopeId,
         params: impl Iterator<Item = &'a PatId>,
     ) {
-        let body = Arc::clone(&self.body);
-        params.for_each(|pat| self.add_bindings(&body, scope, *pat));
+        params.for_each(|pat| self.add_bindings(body, scope, *pat));
     }
 
     fn set_scope(&mut self, node: ExprId, scope: LocalScopeId) {
diff --git a/crates/mun_hir/src/expr/validator.rs b/crates/mun_hir/src/expr/validator.rs
index aa9febc93..0486fba4f 100644
--- a/crates/mun_hir/src/expr/validator.rs
+++ b/crates/mun_hir/src/expr/validator.rs
@@ -26,11 +26,11 @@ pub struct ExprValidator<'a> {
 
 impl<'a> ExprValidator<'a> {
     pub fn new(func: Function, db: &'a dyn HirDatabase) -> Self {
-        let (body, body_source_map) = db.body_with_source_map(func.into());
+        let (body, body_source_map) = db.body_with_source_map(func.id.into());
         ExprValidator {
             func,
             db,
-            infer: db.infer(func.into()),
+            infer: db.infer(func.id.into()),
             body,
             body_source_map,
         }
@@ -59,7 +59,7 @@ impl<'a> ExprValidator<'a> {
         }
 
         if let Some(sig) = self.func.ty(self.db).callable_sig(self.db) {
-            let fn_data = self.func.data(self.db);
+            let fn_data = self.func.data(self.db.upcast());
             for (arg_ty, ty_ref) in sig.params().iter().zip(fn_data.params()) {
                 if arg_ty.as_struct().is_some() {
                     let arg_ptr = fn_data
diff --git a/crates/mun_hir/src/expr/validator/uninitialized_access.rs b/crates/mun_hir/src/expr/validator/uninitialized_access.rs
index b2ca46f8f..638955910 100644
--- a/crates/mun_hir/src/expr/validator/uninitialized_access.rs
+++ b/crates/mun_hir/src/expr/validator/uninitialized_access.rs
@@ -1,6 +1,7 @@
 use super::ExprValidator;
 use crate::diagnostics::{DiagnosticSink, PossiblyUninitializedVariable};
-use crate::{BinaryOp, Expr, ExprId, PatId, Path, Resolution, Resolver, Statement};
+use crate::resolve::{resolver_for_expr, ValueNs};
+use crate::{BinaryOp, Expr, ExprId, PatId, Path, Resolver, Statement};
 use std::collections::HashSet;
 
 #[derive(Copy, Clone, PartialEq, Eq)]
@@ -46,7 +47,7 @@ impl<'d> ExprValidator<'d> {
                 }
             }
             Expr::Path(p) => {
-                let resolver = crate::expr::resolver_for_expr(self.body.clone(), self.db, expr);
+                let resolver = resolver_for_expr(self.db.upcast(), self.body.owner(), expr);
                 self.validate_path_access(
                     sink,
                     initialized_patterns,
@@ -193,28 +194,17 @@ impl<'d> ExprValidator<'d> {
         expr: ExprId,
         expr_side: ExprKind,
     ) {
-        let resolution = match resolver
-            .resolve_path_without_assoc_items(self.db, path)
-            .take_values()
-        {
-            Some(resolution) => resolution,
-            None => {
-                // This has already been caught by the inferencing step
-                return;
-            }
-        };
-
-        let pat = match resolution {
-            Resolution::LocalBinding(pat) => pat,
-            Resolution::Def(_def) => return,
+        let pat = match resolver.resolve_path_as_value_fully(self.db.upcast(), path) {
+            Some((ValueNs::LocalBinding(pat), _)) => pat,
+            _ => return,
         };
 
         if expr_side == ExprKind::Normal || expr_side == ExprKind::Both {
             // Check if the binding has already been initialized
             if initialized_patterns.get(&pat).is_none() {
-                let (_, body_source_map) = self.db.body_with_source_map(self.func.into());
+                let (_, body_source_map) = self.db.body_with_source_map(self.func.id.into());
                 sink.push(PossiblyUninitializedVariable {
-                    file: self.func.module(self.db.upcast()).file_id(),
+                    file: self.func.file_id(self.db),
                     pat: body_source_map
                         .expr_syntax(expr)
                         .unwrap()
diff --git a/crates/mun_hir/src/fixture.rs b/crates/mun_hir/src/fixture.rs
index 47b44f6db..3af7efca2 100644
--- a/crates/mun_hir/src/fixture.rs
+++ b/crates/mun_hir/src/fixture.rs
@@ -1,5 +1,6 @@
 #![cfg(test)]
 
+use crate::ids::PackageId;
 use crate::{FileId, SourceDatabase, SourceRoot, SourceRootId};
 pub use mun_test::Fixture;
 use std::convert::TryInto;
@@ -31,6 +32,7 @@ fn with_files(db: &mut dyn SourceDatabase, fixture: &str) -> Vec<FileId> {
 
     let mut source_root = SourceRoot::default();
     let source_root_id = SourceRootId(0);
+    let package_id = PackageId(0);
     let mut files = Vec::new();
 
     for (idx, entry) in fixture.into_iter().enumerate() {
@@ -43,6 +45,7 @@ fn with_files(db: &mut dyn SourceDatabase, fixture: &str) -> Vec<FileId> {
     }
 
     db.set_source_root(source_root_id, Arc::new(source_root));
+    db.set_package_source_root(package_id, source_root_id);
 
     return files;
 }
diff --git a/crates/mun_hir/src/ids.rs b/crates/mun_hir/src/ids.rs
index 7b89a6e6d..e64c2c0c8 100644
--- a/crates/mun_hir/src/ids.rs
+++ b/crates/mun_hir/src/ids.rs
@@ -1,4 +1,6 @@
 use crate::item_tree::{Function, ItemTreeId, ItemTreeNode, Struct, TypeAlias};
+use crate::module_tree::LocalModuleId;
+use crate::primitive_type::PrimitiveType;
 use crate::DefDatabase;
 use std::hash::{Hash, Hasher};
 
@@ -27,6 +29,38 @@ impl<N: ItemTreeNode> Clone for ItemLoc<N> {
 }
 impl<N: ItemTreeNode> Copy for ItemLoc<N> {}
 
+#[derive(Debug)]
+pub struct AssocItemLoc<N: ItemTreeNode> {
+    pub module: ModuleId,
+    pub id: ItemTreeId<N>,
+}
+
+impl<N: ItemTreeNode> Clone for AssocItemLoc<N> {
+    fn clone(&self) -> Self {
+        Self {
+            module: self.module,
+            id: self.id,
+        }
+    }
+}
+
+impl<N: ItemTreeNode> Copy for AssocItemLoc<N> {}
+
+impl<N: ItemTreeNode> PartialEq for AssocItemLoc<N> {
+    fn eq(&self, other: &Self) -> bool {
+        self.module == other.module && self.id == other.id
+    }
+}
+
+impl<N: ItemTreeNode> Eq for AssocItemLoc<N> {}
+
+impl<N: ItemTreeNode> Hash for AssocItemLoc<N> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.module.hash(state);
+        self.id.hash(state);
+    }
+}
+
 macro_rules! impl_intern_key {
     ($name:ident) => {
         impl salsa::InternKey for $name {
@@ -60,9 +94,21 @@ macro_rules! impl_intern {
     };
 }
 
+/// Represents the id of a single package, all packages have a unique id, the main package and all
+/// dependent packages.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct PackageId(pub u32);
+
+/// Represents an id of a module inside a package.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ModuleId {
+    pub package: PackageId,
+    pub local_id: LocalModuleId,
+}
+
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
 pub struct FunctionId(salsa::InternId);
-pub(crate) type FunctionLoc = ItemLoc<Function>;
+pub(crate) type FunctionLoc = AssocItemLoc<Function>;
 impl_intern!(
     FunctionId,
     FunctionLoc,
@@ -72,12 +118,12 @@ impl_intern!(
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct StructId(salsa::InternId);
-pub(crate) type StructLoc = ItemLoc<Struct>;
+pub(crate) type StructLoc = AssocItemLoc<Struct>;
 impl_intern!(StructId, StructLoc, intern_struct, lookup_intern_struct);
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct TypeAliasId(salsa::InternId);
-pub(crate) type TypeAliasLoc = ItemLoc<TypeAlias>;
+pub(crate) type TypeAliasLoc = AssocItemLoc<TypeAlias>;
 impl_intern!(
     TypeAliasId,
     TypeAliasLoc,
@@ -94,3 +140,50 @@ pub trait Lookup {
     type Data;
     fn lookup(&self, db: &dyn DefDatabase) -> Self::Data;
 }
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ItemDefinitionId {
+    ModuleId(ModuleId),
+    FunctionId(FunctionId),
+    StructId(StructId),
+    TypeAliasId(TypeAliasId),
+    PrimitiveType(PrimitiveType),
+}
+
+impl From<ModuleId> for ItemDefinitionId {
+    fn from(id: ModuleId) -> Self {
+        ItemDefinitionId::ModuleId(id)
+    }
+}
+impl From<FunctionId> for ItemDefinitionId {
+    fn from(id: FunctionId) -> Self {
+        ItemDefinitionId::FunctionId(id)
+    }
+}
+impl From<StructId> for ItemDefinitionId {
+    fn from(id: StructId) -> Self {
+        ItemDefinitionId::StructId(id)
+    }
+}
+impl From<TypeAliasId> for ItemDefinitionId {
+    fn from(id: TypeAliasId) -> Self {
+        ItemDefinitionId::TypeAliasId(id)
+    }
+}
+impl From<PrimitiveType> for ItemDefinitionId {
+    fn from(id: PrimitiveType) -> Self {
+        ItemDefinitionId::PrimitiveType(id)
+    }
+}
+
+/// Definitions which have a body
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum DefWithBodyId {
+    FunctionId(FunctionId),
+}
+
+impl From<FunctionId> for DefWithBodyId {
+    fn from(id: FunctionId) -> Self {
+        DefWithBodyId::FunctionId(id)
+    }
+}
diff --git a/crates/mun_hir/src/item_scope.rs b/crates/mun_hir/src/item_scope.rs
new file mode 100644
index 000000000..f3dbe6bf4
--- /dev/null
+++ b/crates/mun_hir/src/item_scope.rs
@@ -0,0 +1,87 @@
+use crate::primitive_type::PrimitiveType;
+use crate::{ids::ItemDefinitionId, visibility::Visibility, Name, PerNs};
+use once_cell::sync::Lazy;
+use rustc_hash::FxHashMap;
+
+/// Holds all items that are visible from an item as well as by which name and under which
+/// visibility they are accessible.
+#[derive(Debug, Default, PartialEq, Eq)]
+pub struct ItemScope {
+    /// All types visible in this scope
+    types: FxHashMap<Name, (ItemDefinitionId, Visibility)>,
+
+    /// All values visible in this scope
+    values: FxHashMap<Name, (ItemDefinitionId, Visibility)>,
+
+    /// All items that are defined in this scope
+    defs: Vec<ItemDefinitionId>,
+}
+
+pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs<(ItemDefinitionId, Visibility)>>> =
+    Lazy::new(|| {
+        PrimitiveType::ALL
+            .iter()
+            .map(|(name, ty)| {
+                (
+                    name.clone(),
+                    PerNs::types((ty.clone().into(), Visibility::Public)),
+                )
+            })
+            .collect()
+    });
+
+impl ItemScope {
+    /// Returns an iterator over all declarations with this scope
+    pub fn declarations(&self) -> impl Iterator<Item = ItemDefinitionId> + '_ {
+        self.defs.iter().copied()
+    }
+
+    /// Adds an item definition to the list of definitions
+    pub(crate) fn add_definition(&mut self, def: ItemDefinitionId) {
+        self.defs.push(def)
+    }
+
+    /// Adds a named item resolution into the scope
+    pub(crate) fn add_resolution(
+        &mut self,
+        name: Name,
+        def: PerNs<(ItemDefinitionId, Visibility)>,
+    ) {
+        if let Some((types, visibility)) = def.types {
+            self.types.insert(name.clone(), (types, visibility));
+        }
+        if let Some((values, visibility)) = def.values {
+            self.values.insert(name, (values, visibility));
+        }
+    }
+
+    /// Gets a name from the current module scope
+    pub(crate) fn get(&self, name: &Name) -> PerNs<(ItemDefinitionId, Visibility)> {
+        PerNs {
+            types: self.types.get(name).copied(),
+            values: self.values.get(name).copied(),
+        }
+    }
+}
+
+impl PerNs<(ItemDefinitionId, Visibility)> {
+    pub(crate) fn from_definition(
+        def: ItemDefinitionId,
+        vis: Visibility,
+        has_constructor: bool,
+    ) -> PerNs<(ItemDefinitionId, Visibility)> {
+        match def {
+            ItemDefinitionId::FunctionId(_) => PerNs::values((def, vis)),
+            ItemDefinitionId::StructId(_) => {
+                if has_constructor {
+                    PerNs::both((def, vis), (def, vis))
+                } else {
+                    PerNs::types((def, vis))
+                }
+            }
+            ItemDefinitionId::TypeAliasId(_) => PerNs::types((def, vis)),
+            ItemDefinitionId::PrimitiveType(_) => PerNs::types((def, vis)),
+            ItemDefinitionId::ModuleId(_) => PerNs::types((def, vis)),
+        }
+    }
+}
diff --git a/crates/mun_hir/src/item_tree.rs b/crates/mun_hir/src/item_tree.rs
index e12ae1ea3..31faed7e5 100644
--- a/crates/mun_hir/src/item_tree.rs
+++ b/crates/mun_hir/src/item_tree.rs
@@ -6,6 +6,7 @@ use crate::{
     arena::{Arena, Idx},
     source_id::FileAstId,
     type_ref::TypeRef,
+    visibility::RawVisibility,
     DefDatabase, FileId, InFile, Name,
 };
 use mun_syntax::{ast, AstNode};
@@ -19,11 +20,38 @@ use std::{
     sync::Arc,
 };
 
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct RawVisibilityId(u32);
+
+impl RawVisibilityId {
+    pub const PUB: Self = RawVisibilityId(u32::max_value());
+    pub const PRIV: Self = RawVisibilityId(u32::max_value() - 1);
+    pub const PUB_PACKAGE: Self = RawVisibilityId(u32::max_value() - 2);
+    pub const PUB_SUPER: Self = RawVisibilityId(u32::max_value() - 3);
+}
+
+impl fmt::Debug for RawVisibilityId {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let mut f = f.debug_tuple("RawVisibilityId");
+        match *self {
+            Self::PUB => f.field(&"pub"),
+            Self::PRIV => f.field(&"pub(self)"),
+            Self::PUB_PACKAGE => f.field(&"pub(package)"),
+            Self::PUB_SUPER => f.field(&"pub(super)"),
+            _ => f.field(&self.0),
+        };
+        f.finish()
+    }
+}
+
 /// An `ItemTree` is a derivative of an AST that only contains the items defined in the AST.
 #[derive(Debug, Eq, PartialEq)]
 pub struct ItemTree {
+    file_id: FileId,
     top_level: Vec<ModItem>,
     data: ItemTreeData,
+
+    pub diagnostics: Vec<diagnostics::ItemTreeDiagnostic>,
 }
 
 impl ItemTree {
@@ -42,22 +70,44 @@ impl ItemTree {
 
     /// Returns the source location of the specified item. Note that the `file_id` of the item must
     /// be the same `file_id` that was used to create this `ItemTree`.
-    pub fn source<S: ItemTreeNode>(&self, db: &dyn DefDatabase, item: ItemTreeId<S>) -> S::Source {
-        let root = db.parse(item.file_id);
-
-        let id = self[item.value].ast_id();
-        let map = db.ast_id_map(item.file_id);
+    pub fn source<S: ItemTreeNode>(
+        &self,
+        db: &dyn DefDatabase,
+        item: LocalItemTreeId<S>,
+    ) -> S::Source {
+        let root = db.parse(self.file_id);
+
+        let id = self[item].ast_id();
+        let map = db.ast_id_map(self.file_id);
         let ptr = map.get(id);
         ptr.to_node(&root.syntax_node())
     }
 }
 
+#[derive(Default, Debug, Eq, PartialEq)]
+struct ItemVisibilities {
+    arena: Arena<RawVisibility>,
+}
+
+impl ItemVisibilities {
+    fn alloc(&mut self, vis: RawVisibility) -> RawVisibilityId {
+        match &vis {
+            RawVisibility::Public => RawVisibilityId::PUB,
+            RawVisibility::This => RawVisibilityId::PRIV,
+            RawVisibility::Package => RawVisibilityId::PUB_PACKAGE,
+            RawVisibility::Super => RawVisibilityId::PUB_SUPER,
+        }
+    }
+}
+
 #[derive(Default, Debug, Eq, PartialEq)]
 struct ItemTreeData {
     functions: Arena<Function>,
     structs: Arena<Struct>,
     fields: Arena<Field>,
     type_aliases: Arena<TypeAlias>,
+
+    visibilities: ItemVisibilities,
 }
 
 /// Trait implemented by all item nodes in the item tree.
@@ -190,6 +240,24 @@ macro_rules! impl_index {
 
 impl_index!(fields: Field);
 
+static VIS_PUB: RawVisibility = RawVisibility::Public;
+static VIS_PRIV: RawVisibility = RawVisibility::This;
+static VIS_PUB_PACKAGE: RawVisibility = RawVisibility::Package;
+static VIS_PUB_SUPER: RawVisibility = RawVisibility::Super;
+
+impl Index<RawVisibilityId> for ItemTree {
+    type Output = RawVisibility;
+    fn index(&self, index: RawVisibilityId) -> &Self::Output {
+        match index {
+            RawVisibilityId::PRIV => &VIS_PRIV,
+            RawVisibilityId::PUB => &VIS_PUB,
+            RawVisibilityId::PUB_PACKAGE => &VIS_PUB_PACKAGE,
+            RawVisibilityId::PUB_SUPER => &VIS_PUB_SUPER,
+            _ => &self.data.visibilities.arena[Idx::from_raw(index.0.into())],
+        }
+    }
+}
+
 impl<N: ItemTreeNode> Index<LocalItemTreeId<N>> for ItemTree {
     type Output = N;
     fn index(&self, id: LocalItemTreeId<N>) -> &N {
@@ -200,6 +268,7 @@ impl<N: ItemTreeNode> Index<LocalItemTreeId<N>> for ItemTree {
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct Function {
     pub name: Name,
+    pub visibility: RawVisibilityId,
     pub is_extern: bool,
     pub params: Box<[TypeRef]>,
     pub ret_type: TypeRef,
@@ -209,6 +278,7 @@ pub struct Function {
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct Struct {
     pub name: Name,
+    pub visibility: RawVisibilityId,
     pub fields: Fields,
     pub ast_id: FileAstId<ast::StructDef>,
     pub kind: StructDefKind,
@@ -217,6 +287,7 @@ pub struct Struct {
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct TypeAlias {
     pub name: Name,
+    pub visibility: RawVisibilityId,
     pub type_ref: Option<TypeRef>,
     pub ast_id: FileAstId<ast::TypeAliasDef>,
 }
@@ -292,3 +363,59 @@ impl<T> PartialEq for IdRange<T> {
 }
 
 impl<T> Eq for IdRange<T> {}
+
+mod diagnostics {
+    use super::{ItemTree, ModItem};
+    use crate::diagnostics::DuplicateDefinition;
+    use crate::{DefDatabase, DiagnosticSink, HirDatabase, Name};
+    use mun_syntax::{AstNode, SyntaxNodePtr};
+
+    #[derive(Clone, Debug, Eq, PartialEq)]
+    pub enum ItemTreeDiagnostic {
+        DuplicateDefinition {
+            name: Name,
+            first: ModItem,
+            second: ModItem,
+        },
+    }
+
+    impl ItemTreeDiagnostic {
+        pub(crate) fn add_to(
+            &self,
+            db: &dyn HirDatabase,
+            item_tree: &ItemTree,
+            sink: &mut DiagnosticSink,
+        ) {
+            match self {
+                ItemTreeDiagnostic::DuplicateDefinition {
+                    name,
+                    first,
+                    second,
+                } => sink.push(DuplicateDefinition {
+                    file: item_tree.file_id,
+                    name: name.to_string(),
+                    first_definition: ast_ptr_from_mod(db.upcast(), item_tree, *first),
+                    definition: ast_ptr_from_mod(db.upcast(), item_tree, *second),
+                }),
+            };
+
+            fn ast_ptr_from_mod(
+                db: &dyn DefDatabase,
+                item_tree: &ItemTree,
+                item: ModItem,
+            ) -> SyntaxNodePtr {
+                match item {
+                    ModItem::Function(item) => {
+                        SyntaxNodePtr::new(item_tree.source(db, item).syntax())
+                    }
+                    ModItem::Struct(item) => {
+                        SyntaxNodePtr::new(item_tree.source(db, item).syntax())
+                    }
+                    ModItem::TypeAlias(item) => {
+                        SyntaxNodePtr::new(item_tree.source(db, item).syntax())
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/crates/mun_hir/src/item_tree/lower.rs b/crates/mun_hir/src/item_tree/lower.rs
index a3bdfdac8..2b54a7464 100644
--- a/crates/mun_hir/src/item_tree/lower.rs
+++ b/crates/mun_hir/src/item_tree/lower.rs
@@ -1,21 +1,22 @@
 //! This module implements the logic to convert an AST to an `ItemTree`.
 
 use super::{
-    Field, Fields, Function, IdRange, ItemTree, ItemTreeData, ItemTreeNode, LocalItemTreeId,
-    ModItem, Struct, StructDefKind, TypeAlias,
+    diagnostics, Field, Fields, Function, IdRange, ItemTree, ItemTreeData, ItemTreeNode,
+    LocalItemTreeId, ModItem, RawVisibilityId, Struct, StructDefKind, TypeAlias,
 };
 use crate::{
     arena::{Idx, RawId},
     name::AsName,
     source_id::AstIdMap,
     type_ref::TypeRef,
+    visibility::RawVisibility,
     DefDatabase, FileId, Name,
 };
 use mun_syntax::{
     ast,
     ast::{ExternOwner, ModuleItemOwner, NameOwner, StructKind, TypeAscriptionOwner},
 };
-use std::{convert::TryInto, marker::PhantomData, sync::Arc};
+use std::{collections::HashMap, convert::TryInto, marker::PhantomData, sync::Arc};
 
 impl<N: ItemTreeNode> From<Idx<N>> for LocalItemTreeId<N> {
     fn from(index: Idx<N>) -> Self {
@@ -30,6 +31,7 @@ pub(super) struct Context {
     file: FileId,
     source_ast_id_map: Arc<AstIdMap>,
     data: ItemTreeData,
+    diagnostics: Vec<diagnostics::ItemTreeDiagnostic>,
 }
 
 impl Context {
@@ -39,6 +41,7 @@ impl Context {
             file,
             source_ast_id_map: db.ast_id_map(file),
             data: ItemTreeData::default(),
+            diagnostics: Vec::new(),
         }
     }
 
@@ -47,10 +50,33 @@ impl Context {
         let top_level = item_owner
             .items()
             .flat_map(|item| self.lower_mod_item(&item))
-            .collect();
+            .collect::<Vec<_>>();
+
+        // Check duplicates
+        let mut set = HashMap::<Name, &ModItem>::new();
+        for item in top_level.iter() {
+            let name = match item {
+                ModItem::Function(item) => &self.data.functions[item.index].name,
+                ModItem::Struct(item) => &self.data.structs[item.index].name,
+                ModItem::TypeAlias(item) => &self.data.type_aliases[item.index].name,
+            };
+            if let Some(first_item) = set.get(&name) {
+                self.diagnostics
+                    .push(diagnostics::ItemTreeDiagnostic::DuplicateDefinition {
+                        name: name.clone(),
+                        first: **first_item,
+                        second: *item,
+                    })
+            } else {
+                set.insert(name.clone(), item);
+            }
+        }
+
         ItemTree {
+            file_id: self.file,
             top_level,
             data: self.data,
+            diagnostics: self.diagnostics,
         }
     }
 
@@ -66,6 +92,7 @@ impl Context {
     /// Lowers a function
     fn lower_function(&mut self, func: &ast::FunctionDef) -> Option<LocalItemTreeId<Function>> {
         let name = func.name()?.as_name();
+        let visibility = self.lower_visibility(func);
 
         // Lower all the params
         let mut params = Vec::new();
@@ -87,6 +114,7 @@ impl Context {
         let ast_id = self.source_ast_id_map.ast_id(func);
         let res = Function {
             name,
+            visibility,
             is_extern,
             params: params.into_boxed_slice(),
             ret_type,
@@ -99,6 +127,7 @@ impl Context {
     /// Lowers a struct
     fn lower_struct(&mut self, strukt: &ast::StructDef) -> Option<LocalItemTreeId<Struct>> {
         let name = strukt.name()?.as_name();
+        let visibility = self.lower_visibility(strukt);
         let fields = self.lower_fields(&strukt.kind());
         let ast_id = self.source_ast_id_map.ast_id(strukt);
         let kind = match strukt.kind() {
@@ -108,6 +137,7 @@ impl Context {
         };
         let res = Struct {
             name,
+            visibility,
             fields,
             ast_id,
             kind,
@@ -174,10 +204,12 @@ impl Context {
         type_alias: &ast::TypeAliasDef,
     ) -> Option<LocalItemTreeId<TypeAlias>> {
         let name = type_alias.name()?.as_name();
+        let visibility = self.lower_visibility(type_alias);
         let type_ref = type_alias.type_ref().map(|ty| self.lower_type_ref(&ty));
         let ast_id = self.source_ast_id_map.ast_id(type_alias);
         let res = TypeAlias {
             name,
+            visibility,
             type_ref,
             ast_id,
         };
@@ -196,6 +228,12 @@ impl Context {
             .unwrap_or(TypeRef::Error)
     }
 
+    /// Lowers an `ast::VisibilityOwner`
+    fn lower_visibility(&mut self, item: &impl ast::VisibilityOwner) -> RawVisibilityId {
+        let vis = RawVisibility::from_ast(item.visibility());
+        self.data.visibilities.alloc(vis)
+    }
+
     /// Returns the `Idx` of the next `Field`
     fn next_field_idx(&self) -> Idx<Field> {
         let idx: u32 = self.data.fields.len().try_into().expect("too many fields");
diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs
index ab350ebf9..27669309a 100644
--- a/crates/mun_hir/src/lib.rs
+++ b/crates/mun_hir/src/lib.rs
@@ -9,40 +9,41 @@
 mod macros;
 #[macro_use]
 mod arena;
-mod adt;
-mod builtin_type;
 mod code_model;
 mod db;
 pub mod diagnostics;
 mod display;
 mod expr;
-mod fixture;
+pub mod fixture;
 mod ids;
 mod in_file;
 mod input;
 mod item_tree;
 pub mod line_index;
-mod model;
+mod module_tree;
 mod name;
 mod name_resolution;
 mod path;
+mod primitive_type;
 mod resolve;
 mod source_id;
 mod ty;
 mod type_ref;
 mod utils;
 
+mod item_scope;
 #[cfg(test)]
 mod mock;
+mod package_defs;
 #[cfg(test)]
 mod tests;
+mod visibility;
 
 pub use salsa;
 
 pub use relative_path::{RelativePath, RelativePathBuf};
 
 pub use crate::{
-    builtin_type::{FloatBitness, IntBitness, Signedness},
     db::{
         AstDatabase, AstDatabaseStorage, DefDatabase, DefDatabaseStorage, HirDatabase,
         HirDatabaseStorage, InternDatabase, InternDatabaseStorage, SourceDatabase,
@@ -51,25 +52,26 @@ pub use crate::{
     diagnostics::{Diagnostic, DiagnosticSink},
     display::HirDisplay,
     expr::{
-        resolver_for_expr, ArithOp, BinaryOp, Body, CmpOp, Expr, ExprId, ExprScopes, Literal,
-        LogicOp, Ordering, Pat, PatId, RecordLitField, Statement, UnaryOp,
+        ArithOp, BinaryOp, Body, CmpOp, Expr, ExprId, ExprScopes, Literal, LogicOp, Ordering, Pat,
+        PatId, RecordLitField, Statement, UnaryOp,
     },
-    ids::ItemLoc,
+    ids::{ItemLoc, ModuleId, PackageId},
     in_file::InFile,
     input::{FileId, SourceRoot, SourceRootId},
     name::Name,
     name_resolution::PerNs,
     path::{Path, PathKind},
-    resolve::{Resolution, Resolver},
+    primitive_type::{FloatBitness, IntBitness, Signedness},
+    resolve::{resolver_for_expr, resolver_for_scope, Resolver, TypeNs, ValueNs},
     ty::{
         lower::CallableDef, ApplicationTy, FloatTy, InferenceResult, IntTy, ResolveBitness, Ty,
         TypeCtor,
     },
+    visibility::{HasVisibility, Visibility},
 };
 
 use crate::{name::AsName, source_id::AstIdMap};
 
-pub use self::adt::StructMemoryKind;
 pub use self::code_model::{
-    Function, FunctionData, Module, ModuleDef, Struct, TypeAlias, Visibility,
+    Function, FunctionData, Module, ModuleDef, Package, Struct, StructMemoryKind, TypeAlias,
 };
diff --git a/crates/mun_hir/src/model.rs b/crates/mun_hir/src/model.rs
deleted file mode 100644
index 0d43ff07e..000000000
--- a/crates/mun_hir/src/model.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use crate::FileId;
-
-pub struct Module {
-    file_id: FileId,
-}
-
-impl Module {}
diff --git a/crates/mun_hir/src/module_tree.rs b/crates/mun_hir/src/module_tree.rs
new file mode 100644
index 000000000..f8e1be50c
--- /dev/null
+++ b/crates/mun_hir/src/module_tree.rs
@@ -0,0 +1,248 @@
+use crate::{
+    arena::{Arena, Idx},
+    ids::{ModuleId, PackageId},
+    module_tree::diagnostics::ModuleTreeDiagnostic,
+    visibility::RawVisibility,
+    DefDatabase, FileId, Name, SourceDatabase, Visibility,
+};
+use itertools::Itertools;
+use relative_path::RelativePath;
+use rustc_hash::FxHashMap;
+use std::sync::Arc;
+
+/// Represents the tree of modules in a package.
+#[derive(Debug, PartialEq, Eq)]
+pub struct ModuleTree {
+    pub root: LocalModuleId,
+    pub modules: Arena<ModuleData>,
+    pub package: PackageId,
+
+    pub diagnostics: Vec<diagnostics::ModuleTreeDiagnostic>,
+}
+
+/// A module in the tree of modules
+#[derive(Default, Debug, PartialEq, Eq)]
+pub struct ModuleData {
+    pub parent: Option<LocalModuleId>,
+    pub children: FxHashMap<Name, LocalModuleId>,
+    pub file: Option<FileId>,
+}
+
+/// The ID of a module within a specific package
+pub(crate) type LocalModuleId = Idx<ModuleData>;
+
+// Using a `LocalModuleId` you can access the `ModuleTree` to get the `ModuleData`
+impl std::ops::Index<LocalModuleId> for ModuleTree {
+    type Output = ModuleData;
+    fn index(&self, id: LocalModuleId) -> &ModuleData {
+        &self.modules[id]
+    }
+}
+
+impl ModuleTree {
+    /// Constructs the tree of modules from the set of files in a package
+    pub(crate) fn module_tree_query(
+        db: &dyn SourceDatabase,
+        package: PackageId,
+    ) -> Arc<ModuleTree> {
+        use diagnostics::ModuleTreeDiagnostic::*;
+
+        let mut diagnostics = Vec::new();
+
+        // Get the sources for the package
+        let source_root_id = db.package_source_root(package);
+        let source_root = db.source_root(source_root_id);
+
+        let mut modules = Arena::default();
+        let root = modules.alloc(ModuleData::default());
+
+        // Iterate over all files and add them to the module tree
+        for (file_id, relative_path) in source_root
+            .files()
+            .map(|file_id| (file_id, db.file_relative_path(file_id)))
+            .sorted_by(|(_, a), (_, b)| a.cmp(b))
+        {
+            // Iterate over all segments of the relative path and construct modules on the way
+            let mut module_id = root;
+            for path_segment in path_to_module_path(&relative_path)
+                .into_iter()
+                .map(Name::new)
+            {
+                module_id = match modules[module_id].children.get(&path_segment) {
+                    Some(id) => *id,
+                    None => {
+                        let child_module_id = modules.alloc(ModuleData {
+                            parent: Some(module_id),
+                            children: Default::default(),
+                            file: None,
+                        });
+
+                        if !is_valid_module_name(path_segment.to_string()) {
+                            diagnostics
+                                .push(ModuleTreeDiagnostic::InvalidModuleName(child_module_id))
+                        }
+
+                        modules[module_id]
+                            .children
+                            .insert(path_segment, child_module_id);
+
+                        child_module_id
+                    }
+                };
+            }
+
+            // Mark the found module with the current file id
+            let module = &mut modules[module_id];
+            if let Some(other_file) = module.file {
+                diagnostics.push(DuplicateModuleFile(module_id, vec![other_file, file_id]));
+            }
+
+            module.file = Some(file_id);
+        }
+
+        Arc::new(ModuleTree {
+            package,
+            diagnostics,
+            modules,
+            root,
+        })
+    }
+
+    /// Given a `RawVisibility` which describes the visibility of an item relative to a module into
+    /// a `Visibility` which describes the absolute visibility within the module tree.
+    pub(crate) fn resolve_visibility(
+        &self,
+        _db: &dyn DefDatabase,
+        original_module: LocalModuleId,
+        visibility: &RawVisibility,
+    ) -> Visibility {
+        match visibility {
+            RawVisibility::This => Visibility::Module(ModuleId {
+                package: self.package,
+                local_id: original_module,
+            }),
+            RawVisibility::Super => {
+                let parent_module_id = self[original_module].parent.unwrap_or(original_module);
+                Visibility::Module(ModuleId {
+                    package: self.package,
+                    local_id: parent_module_id,
+                })
+            }
+            RawVisibility::Package => Visibility::Module(ModuleId {
+                package: self.package,
+                local_id: self.root,
+            }),
+            RawVisibility::Public => Visibility::Public,
+        }
+    }
+
+    /// Returns the module that is defined by the specified `file`
+    pub fn module_for_file(&self, file: FileId) -> Option<LocalModuleId> {
+        self.modules.iter().find_map(|(idx, data)| {
+            if data.file == Some(file) {
+                Some(idx)
+            } else {
+                None
+            }
+        })
+    }
+}
+
+/// Given a relative path, returns a Vec with all the module names
+fn path_to_module_path(path: &RelativePath) -> Vec<String> {
+    if path.extension().is_none() {
+        path.components().map(|c| c.as_str().to_owned()).collect()
+    } else if path
+        .file_stem()
+        .map(|stem| stem.to_lowercase())
+        .expect("the file has an extension so it must also have a file stem")
+        == "mod"
+    {
+        // The parent directory is the module path
+        path_to_module_path(
+            path.parent()
+                .expect("path has a filename so it must also have a parent"),
+        )
+    } else {
+        // Simply strip the extension and use that as the module path
+        path_to_module_path(&path.with_extension(""))
+    }
+}
+
+/// Given a module name returns true if it is a valid name
+fn is_valid_module_name(name: impl AsRef<str>) -> bool {
+    let mut chars = name.as_ref().chars();
+    if let Some(first_char) = chars.next() {
+        first_char.is_alphabetic() && chars.all(|c| c.is_alphanumeric() || c == '_')
+    } else {
+        false
+    }
+}
+
+mod diagnostics {
+    use super::LocalModuleId;
+    use crate::FileId;
+
+    #[derive(Debug, PartialEq, Eq)]
+    pub enum ModuleTreeDiagnostic {
+        DuplicateModuleFile(LocalModuleId, Vec<FileId>),
+        InvalidModuleName(LocalModuleId),
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::{fixture::WithFixture, mock::MockDatabase};
+
+    #[test]
+    fn valid_module_name() {
+        assert_eq!(is_valid_module_name("foo"), true);
+        assert_eq!(is_valid_module_name("bar"), true);
+        assert_eq!(is_valid_module_name("foo_bar"), true);
+        assert_eq!(is_valid_module_name("3bar"), false);
+        assert_eq!(is_valid_module_name("bar3"), true);
+        assert_eq!(is_valid_module_name("foo-bar"), false);
+        assert_eq!(is_valid_module_name(""), false);
+    }
+
+    #[test]
+    fn module_path() {
+        assert_eq!(
+            path_to_module_path(RelativePath::new("foo/bar/baz.mun")),
+            vec!["foo", "bar", "baz"]
+        );
+        assert_eq!(
+            path_to_module_path(RelativePath::new("foo/bar/mod.mun")),
+            vec!["foo", "bar"]
+        );
+        assert_eq!(
+            path_to_module_path(RelativePath::new("foo/mod.mun")),
+            vec!["foo"]
+        );
+        assert_eq!(
+            path_to_module_path(RelativePath::new("foo.mun")),
+            vec!["foo"]
+        );
+        assert_eq!(
+            path_to_module_path(RelativePath::new("mod.mun")),
+            Vec::<String>::new()
+        );
+    }
+
+    #[test]
+    fn module_tree() {
+        let mock_db = MockDatabase::with_files(
+            r#"
+        //- /mod.mun
+        //- /foo.mun
+        //- /foo/mod.mun
+        //- /foo/bar.mun
+        //- /foo/baz/mod.mun
+        //- /baz/foo.mun
+        "#,
+        );
+        let module_tree = mock_db.module_tree(PackageId(0));
+        insta::assert_debug_snapshot!(module_tree);
+    }
+}
diff --git a/crates/mun_hir/src/name.rs b/crates/mun_hir/src/name.rs
index 92691867b..53a8b6255 100644
--- a/crates/mun_hir/src/name.rs
+++ b/crates/mun_hir/src/name.rs
@@ -48,6 +48,10 @@ impl Name {
         }
     }
 
+    pub(crate) fn new(text: impl AsRef<str>) -> Name {
+        Name::new_text(SmolStr::new(text))
+    }
+
     pub(crate) fn missing() -> Name {
         Name::new_text("[missing name]".into())
     }
diff --git a/crates/mun_hir/src/name_resolution.rs b/crates/mun_hir/src/name_resolution.rs
index bfac749ce..9399b58f9 100644
--- a/crates/mun_hir/src/name_resolution.rs
+++ b/crates/mun_hir/src/name_resolution.rs
@@ -1,79 +1,4 @@
+mod path_resolution;
 mod per_ns;
 
 pub use self::per_ns::{Namespace, PerNs};
-use crate::{builtin_type::BuiltinType, FileId, HirDatabase, ModuleDef, Name};
-use once_cell::sync::Lazy;
-use rustc_hash::FxHashMap;
-use std::sync::Arc;
-
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
-pub struct Resolution {
-    /// None for unresolved
-    pub def: PerNs<ModuleDef>,
-    //    /// ident by which this is imported into local scope.
-    //    pub import: Option<ImportId>,
-}
-
-#[derive(Debug, Default, PartialEq, Eq, Clone)]
-pub struct ModuleScope {
-    items: FxHashMap<Name, Resolution>,
-}
-
-static BUILTIN_SCOPE: Lazy<FxHashMap<Name, Resolution>> = Lazy::new(|| {
-    BuiltinType::ALL
-        .iter()
-        .map(|(name, ty)| {
-            (
-                name.clone(),
-                Resolution {
-                    def: PerNs::types(ty.clone().into()),
-                },
-            )
-        })
-        .collect()
-});
-
-impl ModuleScope {
-    pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a Name, &'a Resolution)> + 'a {
-        //FIXME: shadowing
-        self.items.iter().chain(BUILTIN_SCOPE.iter())
-    }
-    pub fn get(&self, name: &Name) -> Option<&Resolution> {
-        self.items.get(name).or_else(|| BUILTIN_SCOPE.get(name))
-    }
-}
-
-pub(crate) fn module_scope_query(db: &dyn HirDatabase, file_id: FileId) -> Arc<ModuleScope> {
-    let mut scope = ModuleScope::default();
-    let defs = db.module_data(file_id);
-    for def in defs.definitions() {
-        match def {
-            ModuleDef::Function(f) => {
-                scope.items.insert(
-                    f.name(db),
-                    Resolution {
-                        def: PerNs::values(*def),
-                    },
-                );
-            }
-            ModuleDef::Struct(s) => {
-                scope.items.insert(
-                    s.name(db.upcast()),
-                    Resolution {
-                        def: PerNs::both(*def, *def),
-                    },
-                );
-            }
-            ModuleDef::TypeAlias(t) => {
-                scope.items.insert(
-                    t.name(db.upcast()),
-                    Resolution {
-                        def: PerNs::types(*def),
-                    },
-                );
-            }
-            ModuleDef::BuiltinType(_) => {}
-        }
-    }
-    Arc::new(scope)
-}
diff --git a/crates/mun_hir/src/name_resolution/path_resolution.rs b/crates/mun_hir/src/name_resolution/path_resolution.rs
new file mode 100644
index 000000000..da60ee4df
--- /dev/null
+++ b/crates/mun_hir/src/name_resolution/path_resolution.rs
@@ -0,0 +1,145 @@
+use crate::ids::{ItemDefinitionId, ModuleId, PackageId};
+use crate::item_scope::BUILTIN_SCOPE;
+use crate::module_tree::LocalModuleId;
+use crate::package_defs::PackageDefs;
+use crate::{DefDatabase, Name, Path, PathKind, PerNs, Visibility};
+use itertools::__std_iter::successors;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(super) enum ReachedFixedPoint {
+    Yes,
+    No,
+}
+
+/// Contains the result of resolving a path. It contains how far the path was able to be resolved
+/// as well as the resolved values or types so far.
+#[derive(Debug, Clone)]
+pub(super) struct ResolvePathResult {
+    pub(super) resolved_def: PerNs<(ItemDefinitionId, Visibility)>,
+    pub(super) segment_index: Option<usize>,
+    pub(super) reached_fixedpoint: ReachedFixedPoint,
+    pub(super) package: Option<PackageId>,
+}
+
+impl ResolvePathResult {
+    /// Constructs an empty `ResolvePathResult`
+    fn empty(reached_fixedpoint: ReachedFixedPoint) -> ResolvePathResult {
+        ResolvePathResult::with(PerNs::none(), reached_fixedpoint, None, None)
+    }
+
+    /// Constructs a new instance of `ResolvePathResult`
+    fn with(
+        resolved_def: PerNs<(ItemDefinitionId, Visibility)>,
+        reached_fixedpoint: ReachedFixedPoint,
+        segment_index: Option<usize>,
+        package: Option<PackageId>,
+    ) -> ResolvePathResult {
+        ResolvePathResult {
+            resolved_def,
+            reached_fixedpoint,
+            segment_index,
+            package,
+        }
+    }
+}
+
+impl PackageDefs {
+    /// Resolves the specified `path` from within the specified `module`. Also optionally returns
+    /// which part of the path was resolved, if this is not `None` it means the path didn't resolve
+    /// completely yet.
+    pub(crate) fn resolve_path_in_module(
+        &self,
+        db: &dyn DefDatabase,
+        module: LocalModuleId,
+        path: &Path,
+    ) -> (PerNs<(ItemDefinitionId, Visibility)>, Option<usize>) {
+        let res = self.resolve_path_with_fixedpoint(db, module, path);
+        (res.resolved_def, res.segment_index)
+    }
+
+    /// Resolves the specified `name` from within the specified `module`
+    fn resolve_name_in_module(
+        &self,
+        _db: &dyn DefDatabase,
+        module: LocalModuleId,
+        name: &Name,
+    ) -> PerNs<(ItemDefinitionId, Visibility)> {
+        self[module]
+            .get(name)
+            .or(BUILTIN_SCOPE.get(name).copied().unwrap_or_else(PerNs::none))
+    }
+
+    /// Resolves the specified `path` from within the specified `module`. Also returns whether or
+    /// not additions to the `PackageDef` would change the result or whether a fixed point has been
+    /// reached. This is useful when resolving all imports.
+    pub(super) fn resolve_path_with_fixedpoint(
+        &self,
+        db: &dyn DefDatabase,
+        original_module: LocalModuleId,
+        path: &Path,
+    ) -> ResolvePathResult {
+        let mut segments = path.segments.iter().enumerate();
+        let mut curr_per_ns: PerNs<(ItemDefinitionId, Visibility)> = match path.kind {
+            PathKind::Plain => {
+                let (_, segment) = match segments.next() {
+                    Some((idx, segment)) => (idx, segment),
+                    None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
+                };
+                self.resolve_name_in_module(db, original_module, &segment)
+            }
+            PathKind::Super(lvl) => {
+                let m = successors(Some(original_module), |m| self.module_tree[*m].parent)
+                    .nth(lvl as usize);
+                if let Some(local_id) = m {
+                    PerNs::types((
+                        ModuleId {
+                            package: self.module_tree.package,
+                            local_id,
+                        }
+                        .into(),
+                        Visibility::Public,
+                    ))
+                } else {
+                    return ResolvePathResult::empty(ReachedFixedPoint::Yes);
+                }
+            }
+            PathKind::Package => PerNs::types((
+                ModuleId {
+                    package: self.module_tree.package,
+                    local_id: self.module_tree.root,
+                }
+                .into(),
+                Visibility::Public,
+            )),
+        };
+
+        for (i, segment) in segments {
+            let (curr, vis) = match curr_per_ns.take_types() {
+                Some(r) => r,
+                None => {
+                    return ResolvePathResult::empty(ReachedFixedPoint::No);
+                }
+            };
+
+            curr_per_ns = match curr {
+                ItemDefinitionId::ModuleId(module) => self[module.local_id].get(&segment),
+                // TODO: Enum variants
+                s => {
+                    return ResolvePathResult::with(
+                        PerNs::types((s, vis)),
+                        ReachedFixedPoint::Yes,
+                        Some(i),
+                        Some(self.module_tree.package),
+                    );
+                }
+            };
+        }
+
+        ResolvePathResult::with(
+            curr_per_ns,
+            ReachedFixedPoint::Yes,
+            None,
+            Some(self.module_tree.package),
+        )
+    }
+}
diff --git a/crates/mun_hir/src/package_defs.rs b/crates/mun_hir/src/package_defs.rs
new file mode 100644
index 000000000..7aafb6e84
--- /dev/null
+++ b/crates/mun_hir/src/package_defs.rs
@@ -0,0 +1,31 @@
+mod collector;
+
+use crate::{
+    arena::map::ArenaMap, ids::PackageId, item_scope::ItemScope, module_tree::LocalModuleId,
+    module_tree::ModuleTree, DefDatabase,
+};
+use std::{ops::Index, sync::Arc};
+
+/// Contains all top-level definitions for a package.
+#[derive(Debug, PartialEq, Eq)]
+pub struct PackageDefs {
+    pub modules: ArenaMap<LocalModuleId, ItemScope>,
+    pub module_tree: Arc<ModuleTree>,
+}
+
+impl PackageDefs {
+    pub(crate) fn package_def_map_query(
+        db: &dyn DefDatabase,
+        package: PackageId,
+    ) -> Arc<PackageDefs> {
+        Arc::new(collector::collect(db, package))
+    }
+}
+
+impl Index<LocalModuleId> for PackageDefs {
+    type Output = ItemScope;
+
+    fn index(&self, index: LocalModuleId) -> &Self::Output {
+        &self.modules[index]
+    }
+}
diff --git a/crates/mun_hir/src/package_defs/collector.rs b/crates/mun_hir/src/package_defs/collector.rs
new file mode 100644
index 000000000..e16a81ffc
--- /dev/null
+++ b/crates/mun_hir/src/package_defs/collector.rs
@@ -0,0 +1,202 @@
+use super::PackageDefs;
+use crate::{
+    arena::map::ArenaMap,
+    ids::{FunctionLoc, Intern, ItemDefinitionId, ModuleId, PackageId, StructLoc, TypeAliasLoc},
+    item_scope::ItemScope,
+    item_tree::{
+        Function, ItemTree, ItemTreeId, LocalItemTreeId, ModItem, Struct, StructDefKind, TypeAlias,
+    },
+    module_tree::{LocalModuleId, ModuleTree},
+    visibility::RawVisibility,
+    DefDatabase, FileId, Name, PerNs, Visibility,
+};
+use std::sync::Arc;
+
+pub(super) fn collect(db: &dyn DefDatabase, package_id: PackageId) -> PackageDefs {
+    let mut collector = DefCollector {
+        db,
+        package_id,
+        modules: Default::default(),
+        module_tree: db.module_tree(package_id),
+    };
+    collector.collect();
+    collector.finish()
+}
+
+/// A helper struct to collect all definitions for all modules in a package.
+struct DefCollector<'db> {
+    db: &'db dyn DefDatabase,
+    package_id: PackageId,
+    modules: ArenaMap<LocalModuleId, ItemScope>,
+    module_tree: Arc<ModuleTree>,
+}
+
+impl<'db> DefCollector<'db> {
+    /// Collects all information and stores it in the instance
+    fn collect(&mut self) {
+        // Collect all definitions in each module
+        let module_tree = self.module_tree.clone();
+
+        fn collect_modules_recursive(
+            collector: &mut DefCollector,
+            module_id: LocalModuleId,
+            parent: Option<(Name, LocalModuleId)>,
+        ) {
+            // Insert an empty item scope for this module, this will be filled in.
+            collector.modules.insert(module_id, ItemScope::default());
+
+            // If there is a file associated with the module, collect all definitions from it
+            let module_data = &collector.module_tree[module_id];
+            if let Some(file_id) = module_data.file {
+                let item_tree = collector.db.item_tree(file_id);
+                let mut mod_collector = ModCollectorContext {
+                    def_collector: collector,
+                    module_id,
+                    file_id,
+                    item_tree: &item_tree,
+                };
+
+                mod_collector.collect(item_tree.top_level_items());
+            }
+
+            // Insert this module into the scope of the parent
+            if let Some((name, parent)) = parent {
+                collector.modules[parent].add_resolution(
+                    name,
+                    PerNs::from_definition(
+                        ModuleId {
+                            package: collector.package_id,
+                            local_id: module_id,
+                        }
+                        .into(),
+                        Visibility::Public,
+                        false,
+                    ),
+                );
+            }
+
+            // Iterate over all children
+            let child_module_ids = collector.module_tree[module_id]
+                .children
+                .iter()
+                .map(|(name, local_id)| (name.clone(), *local_id))
+                .collect::<Vec<_>>();
+            for (name, child_module_id) in child_module_ids {
+                collect_modules_recursive(collector, child_module_id, Some((name, module_id)));
+            }
+        };
+
+        collect_modules_recursive(self, module_tree.root, None);
+    }
+
+    /// Create the `PackageDefs` struct that holds all the items
+    fn finish(self) -> PackageDefs {
+        PackageDefs {
+            modules: self.modules,
+            module_tree: self.module_tree,
+        }
+    }
+}
+
+/// Collects all items from a module
+struct ModCollectorContext<'a, 'db> {
+    def_collector: &'a mut DefCollector<'db>,
+    module_id: LocalModuleId,
+    file_id: FileId,
+    item_tree: &'a ItemTree,
+}
+
+impl<'a> ModCollectorContext<'a, '_> {
+    fn collect(&mut self, items: &[ModItem]) {
+        for &item in items {
+            let definition = match item {
+                ModItem::Function(id) => self.collect_function(id),
+                ModItem::Struct(id) => self.collect_struct(id),
+                ModItem::TypeAlias(id) => self.collect_type_alias(id),
+            };
+
+            if let Some(DefData {
+                id,
+                name,
+                visibility,
+                has_constructor,
+            }) = definition
+            {
+                self.def_collector.modules[self.module_id].add_definition(id);
+                let visibility = self.def_collector.module_tree.resolve_visibility(
+                    self.def_collector.db,
+                    self.module_id,
+                    visibility,
+                );
+                self.def_collector.modules[self.module_id].add_resolution(
+                    name.clone(),
+                    PerNs::from_definition(id, visibility, has_constructor),
+                )
+            }
+        }
+    }
+
+    /// Collects the definition data from a `Function`
+    fn collect_function(&self, id: LocalItemTreeId<Function>) -> Option<DefData<'a>> {
+        let func = &self.item_tree[id];
+        Some(DefData {
+            id: FunctionLoc {
+                module: ModuleId {
+                    package: self.def_collector.package_id,
+                    local_id: self.module_id,
+                },
+                id: ItemTreeId::new(self.file_id, id),
+            }
+            .intern(self.def_collector.db)
+            .into(),
+            name: &func.name,
+            visibility: &self.item_tree[func.visibility],
+            has_constructor: false,
+        })
+    }
+
+    /// Collects the definition data from a `Struct`
+    fn collect_struct(&self, id: LocalItemTreeId<Struct>) -> Option<DefData<'a>> {
+        let adt = &self.item_tree[id];
+        Some(DefData {
+            id: StructLoc {
+                module: ModuleId {
+                    package: self.def_collector.package_id,
+                    local_id: self.module_id,
+                },
+                id: ItemTreeId::new(self.file_id, id),
+            }
+            .intern(self.def_collector.db)
+            .into(),
+            name: &adt.name,
+            visibility: &self.item_tree[adt.visibility],
+            has_constructor: adt.kind != StructDefKind::Record,
+        })
+    }
+
+    /// Collects the definition data from a `TypeAlias`
+    fn collect_type_alias(&self, id: LocalItemTreeId<TypeAlias>) -> Option<DefData<'a>> {
+        let type_alias = &self.item_tree[id];
+        Some(DefData {
+            id: TypeAliasLoc {
+                module: ModuleId {
+                    package: self.def_collector.package_id,
+                    local_id: self.module_id,
+                },
+                id: ItemTreeId::new(self.file_id, id),
+            }
+            .intern(self.def_collector.db)
+            .into(),
+            name: &type_alias.name,
+            visibility: &self.item_tree[type_alias.visibility],
+            has_constructor: false,
+        })
+    }
+}
+
+struct DefData<'a> {
+    id: ItemDefinitionId,
+    name: &'a Name,
+    visibility: &'a RawVisibility,
+    has_constructor: bool,
+}
diff --git a/crates/mun_hir/src/path.rs b/crates/mun_hir/src/path.rs
index 9f9b468c1..c7f79ed60 100644
--- a/crates/mun_hir/src/path.rs
+++ b/crates/mun_hir/src/path.rs
@@ -4,52 +4,48 @@ use mun_syntax::ast;
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Path {
     pub kind: PathKind,
-    pub segments: Vec<PathSegment>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct PathSegment {
-    pub name: Name,
+    pub segments: Vec<Name>,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum PathKind {
     Plain,
-    Self_,
-    Super,
-    Abs,
+    // `self` is Super(0)
+    Super(u8),
+    Package,
 }
 
 impl Path {
     /// Converts an `ast::Path` to `Path`.
-    pub fn from_ast(path: ast::Path) -> Option<Path> {
+    pub fn from_ast(mut path: ast::Path) -> Option<Path> {
         let mut kind = PathKind::Plain;
         let mut segments = Vec::new();
-        //        loop {
-        let segment = path.segment()?;
+        loop {
+            let segment = path.segment()?;
 
-        if segment.has_colon_colon() {
-            kind = PathKind::Abs;
-        }
-
-        match segment.kind()? {
-            ast::PathSegmentKind::Name(name) => {
-                let segment = PathSegment {
-                    name: name.as_name(),
-                };
-                segments.push(segment);
-            }
-            ast::PathSegmentKind::SelfKw => {
-                kind = PathKind::Self_;
-                //                    break;
+            match segment.kind()? {
+                ast::PathSegmentKind::Name(name) => {
+                    segments.push(name.as_name());
+                }
+                ast::PathSegmentKind::SelfKw => {
+                    kind = PathKind::Super(0);
+                    break;
+                }
+                ast::PathSegmentKind::SuperKw => {
+                    kind = PathKind::Super(1);
+                    break;
+                }
+                ast::PathSegmentKind::PackageKw => {
+                    kind = PathKind::Package;
+                    break;
+                }
             }
-            ast::PathSegmentKind::SuperKw => {
-                kind = PathKind::Super;
-                //                    break;
+
+            path = match path.qualifier() {
+                Some(p) => p,
+                None => break,
             }
         }
-        //            break;
-        //        }
         segments.reverse();
         Some(Path { kind, segments })
     }
@@ -67,7 +63,7 @@ impl Path {
     /// If this path represents a single identifier, like `foo`, return its name.
     pub fn as_ident(&self) -> Option<&Name> {
         if self.is_ident() {
-            return self.segments.first().map(|s| &s.name);
+            return self.segments.first();
         }
         None
     }
@@ -77,7 +73,7 @@ impl From<Name> for Path {
     fn from(name: Name) -> Path {
         Path {
             kind: PathKind::Plain,
-            segments: vec![PathSegment { name }],
+            segments: vec![name],
         }
     }
 }
diff --git a/crates/mun_hir/src/primitive_type.rs b/crates/mun_hir/src/primitive_type.rs
new file mode 100644
index 000000000..d664dece0
--- /dev/null
+++ b/crates/mun_hir/src/primitive_type.rs
@@ -0,0 +1,157 @@
+use crate::name::{name, Name};
+use std::fmt;
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum Signedness {
+    Signed,
+    Unsigned,
+}
+
+impl Signedness {
+    pub fn is_signed(self) -> bool {
+        self == Signedness::Signed
+    }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum IntBitness {
+    Xsize,
+    X8,
+    X16,
+    X32,
+    X64,
+    X128,
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum FloatBitness {
+    X32,
+    X64,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct PrimitiveInt {
+    pub signedness: Signedness,
+    pub bitness: IntBitness,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct PrimitiveFloat {
+    pub bitness: FloatBitness,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum PrimitiveType {
+    Float(PrimitiveFloat),
+    Int(PrimitiveInt),
+    Bool,
+}
+
+impl PrimitiveType {
+    #[rustfmt::skip]
+    pub const ALL: &'static [(Name, PrimitiveType)] = &[
+        (name![bool], PrimitiveType::Bool),
+
+        (name![isize], PrimitiveType::Int(PrimitiveInt::ISIZE)),
+        (name![i8], PrimitiveType::Int(PrimitiveInt::I8)),
+        (name![i16], PrimitiveType::Int(PrimitiveInt::I16)),
+        (name![i32], PrimitiveType::Int(PrimitiveInt::I32)),
+        (name![i64], PrimitiveType::Int(PrimitiveInt::I64)),
+        (name![i128], PrimitiveType::Int(PrimitiveInt::I128)),
+
+        (name![usize], PrimitiveType::Int(PrimitiveInt::USIZE)),
+        (name![u8], PrimitiveType::Int(PrimitiveInt::U8)),
+        (name![u16], PrimitiveType::Int(PrimitiveInt::U16)),
+        (name![u32], PrimitiveType::Int(PrimitiveInt::U32)),
+        (name![u64], PrimitiveType::Int(PrimitiveInt::U64)),
+        (name![u128], PrimitiveType::Int(PrimitiveInt::U128)),
+
+        (name![f32], PrimitiveType::Float(PrimitiveFloat::F32)),
+        (name![f64], PrimitiveType::Float(PrimitiveFloat::F64)),
+    ];
+}
+
+impl fmt::Display for PrimitiveType {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let type_name = match self {
+            PrimitiveType::Bool => "bool",
+            PrimitiveType::Int(PrimitiveInt {
+                signedness,
+                bitness,
+            }) => match (signedness, bitness) {
+                (Signedness::Signed, IntBitness::Xsize) => "isize",
+                (Signedness::Signed, IntBitness::X8) => "i8",
+                (Signedness::Signed, IntBitness::X16) => "i16",
+                (Signedness::Signed, IntBitness::X32) => "i32",
+                (Signedness::Signed, IntBitness::X64) => "i64",
+                (Signedness::Signed, IntBitness::X128) => "i128",
+
+                (Signedness::Unsigned, IntBitness::Xsize) => "usize",
+                (Signedness::Unsigned, IntBitness::X8) => "u8",
+                (Signedness::Unsigned, IntBitness::X16) => "u16",
+                (Signedness::Unsigned, IntBitness::X32) => "u32",
+                (Signedness::Unsigned, IntBitness::X64) => "u64",
+                (Signedness::Unsigned, IntBitness::X128) => "u128",
+            },
+            PrimitiveType::Float(PrimitiveFloat { bitness }) => match bitness {
+                FloatBitness::X32 => "f32",
+                FloatBitness::X64 => "f64",
+            },
+        };
+        f.write_str(type_name)
+    }
+}
+
+#[rustfmt::skip]
+impl PrimitiveInt {
+    pub const ISIZE: PrimitiveInt = PrimitiveInt { signedness: Signedness::Signed, bitness: IntBitness::Xsize       };
+    pub const I8   : PrimitiveInt = PrimitiveInt { signedness: Signedness::Signed, bitness: IntBitness::X8          };
+    pub const I16  : PrimitiveInt = PrimitiveInt { signedness: Signedness::Signed, bitness: IntBitness::X16         };
+    pub const I32  : PrimitiveInt = PrimitiveInt { signedness: Signedness::Signed, bitness: IntBitness::X32         };
+    pub const I64  : PrimitiveInt = PrimitiveInt { signedness: Signedness::Signed, bitness: IntBitness::X64         };
+    pub const I128 : PrimitiveInt = PrimitiveInt { signedness: Signedness::Signed, bitness: IntBitness::X128        };
+
+    pub const USIZE: PrimitiveInt = PrimitiveInt { signedness: Signedness::Unsigned, bitness: IntBitness::Xsize     };
+    pub const U8   : PrimitiveInt = PrimitiveInt { signedness: Signedness::Unsigned, bitness: IntBitness::X8        };
+    pub const U16  : PrimitiveInt = PrimitiveInt { signedness: Signedness::Unsigned, bitness: IntBitness::X16       };
+    pub const U32  : PrimitiveInt = PrimitiveInt { signedness: Signedness::Unsigned, bitness: IntBitness::X32       };
+    pub const U64  : PrimitiveInt = PrimitiveInt { signedness: Signedness::Unsigned, bitness: IntBitness::X64       };
+    pub const U128 : PrimitiveInt = PrimitiveInt { signedness: Signedness::Unsigned, bitness: IntBitness::X128      };
+
+
+    pub fn from_suffix(suffix: &str) -> Option<PrimitiveInt> {
+        let res = match suffix {
+            "isize" => Self::ISIZE,
+            "i8"    => Self::I8,
+            "i16"   => Self::I16,
+            "i32"   => Self::I32,
+            "i64"   => Self::I64,
+            "i128"  => Self::I128,
+
+            "usize" => Self::USIZE,
+            "u8"    => Self::U8,
+            "u16"   => Self::U16,
+            "u32"   => Self::U32,
+            "u64"   => Self::U64,
+            "u128"  => Self::U128,
+
+            _ => return None,
+        };
+        Some(res)
+    }
+}
+
+#[rustfmt::skip]
+impl PrimitiveFloat {
+    pub const F32: PrimitiveFloat = PrimitiveFloat { bitness: FloatBitness::X32 };
+    pub const F64: PrimitiveFloat = PrimitiveFloat { bitness: FloatBitness::X64 };
+
+    pub fn from_suffix(suffix: &str) -> Option<PrimitiveFloat> {
+        let res = match suffix {
+            "f32" => PrimitiveFloat::F32,
+            "f64" => PrimitiveFloat::F64,
+            _ => return None,
+        };
+        Some(res)
+    }
+}
diff --git a/crates/mun_hir/src/raw.rs b/crates/mun_hir/src/raw.rs
deleted file mode 100644
index b5851844e..000000000
--- a/crates/mun_hir/src/raw.rs
+++ /dev/null
@@ -1,83 +0,0 @@
-use mun_syntax::ast::{self, ModuleItemOwner, NameOwner};
-
-use crate::{
-    arena::{Arena, Idx},
-    name::AsName,
-    DefDatabase, FileAstId, FileId, Name,
-};
-use std::ops::Index;
-use std::sync::Arc;
-
-/// `RawItems` are top level file items. `RawItems` do not change on most edits.
-#[derive(Debug, Default, PartialEq, Eq)]
-pub struct RawItems {
-    definitions: Arena<DefData>,
-    items: Vec<RawFileItem>,
-}
-
-/// Represents an id of a `DefData` in `RawItems`.
-pub(super) type LocalDefId = Idx<DefData>;
-
-#[derive(Debug, PartialEq, Eq)]
-pub(super) struct DefData {
-    pub(super) name: Name,
-    pub(super) kind: DefKind,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub(super) enum DefKind {
-    Function(FileAstId<ast::FunctionDef>),
-    Struct(FileAstId<ast::StructDef>),
-    TypeAlias(FileAstId<ast::TypeAliasDef>),
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub(super) enum RawFileItem {
-    Definition(LocalDefId),
-}
-
-impl Index<LocalDefId> for RawItems {
-    type Output = DefData;
-
-    fn index(&self, index: LocalDefId) -> &Self::Output {
-        &self.definitions[index]
-    }
-}
-
-impl RawItems {
-    pub(crate) fn raw_file_items_query(db: &dyn DefDatabase, file_id: FileId) -> Arc<RawItems> {
-        let mut items = RawItems::default();
-
-        let source_file = db.parse(file_id).tree();
-        let ast_id_map = db.ast_id_map(file_id);
-
-        // Iterate over all items in the source file
-        for item in source_file.items() {
-            let (kind, name) = match item.kind() {
-                ast::ModuleItemKind::FunctionDef(it) => {
-                    (DefKind::Function((*ast_id_map).ast_id(&it)), it.name())
-                }
-                ast::ModuleItemKind::StructDef(it) => {
-                    (DefKind::Struct((*ast_id_map).ast_id(&it)), it.name())
-                }
-                ast::ModuleItemKind::TypeAliasDef(it) => {
-                    (DefKind::TypeAlias((*ast_id_map).ast_id(&it)), it.name())
-                }
-            };
-
-            // If no name is provided an error is already emitted
-            if let Some(name) = name {
-                let id = items.definitions.alloc(DefData {
-                    name: name.as_name(),
-                    kind,
-                });
-                items.items.push(RawFileItem::Definition(id));
-            }
-        }
-        Arc::new(items)
-    }
-
-    pub(super) fn items(&self) -> &[RawFileItem] {
-        &self.items
-    }
-}
diff --git a/crates/mun_hir/src/resolve.rs b/crates/mun_hir/src/resolve.rs
index 97f486b3b..a4796da9a 100644
--- a/crates/mun_hir/src/resolve.rs
+++ b/crates/mun_hir/src/resolve.rs
@@ -1,6 +1,13 @@
+use crate::ids::{
+    DefWithBodyId, FunctionId, ItemDefinitionId, Lookup, ModuleId, StructId, TypeAliasId,
+};
+use crate::module_tree::LocalModuleId;
+use crate::package_defs::PackageDefs;
+use crate::primitive_type::PrimitiveType;
+use crate::visibility::RawVisibility;
 use crate::{
-    expr::scope::LocalScopeId, expr::PatId, ExprScopes, FileId, HirDatabase, ModuleDef, Name, Path,
-    PerNs,
+    expr::scope::LocalScopeId, expr::PatId, DefDatabase, ExprId, ExprScopes, Path, PerNs,
+    Visibility,
 };
 use std::sync::Arc;
 
@@ -20,131 +27,309 @@ pub(crate) enum Scope {
 
 #[derive(Debug, Clone)]
 pub(crate) struct ModuleItemMap {
-    file_id: FileId,
+    package_defs: Arc<PackageDefs>,
+    module_id: LocalModuleId,
 }
 
 #[derive(Debug, Clone)]
 pub(crate) struct ExprScope {
+    owner: DefWithBodyId,
     expr_scopes: Arc<ExprScopes>,
     scope_id: LocalScopeId,
 }
 
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum ResolveValueResult {
+    ValueNs(ValueNs, Visibility),
+    Partial(TypeNs, usize),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum ValueNs {
+    LocalBinding(PatId),
+    FunctionId(FunctionId),
+    StructId(StructId),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum TypeNs {
+    StructId(StructId),
+    TypeAliasId(TypeAliasId),
+    PrimitiveType(PrimitiveType),
+}
+
 impl Resolver {
+    /// Adds another scope to the resolver from which it can resolve names
     pub(crate) fn push_scope(mut self, scope: Scope) -> Resolver {
         self.scopes.push(scope);
         self
     }
 
-    pub(crate) fn push_module_scope(self, file_id: FileId) -> Resolver {
-        self.push_scope(Scope::ModuleScope(ModuleItemMap { file_id }))
+    /// Adds a module scope to the resolver from which it can resolve names
+    pub(crate) fn push_module_scope(
+        self,
+        package_defs: Arc<PackageDefs>,
+        module_id: LocalModuleId,
+    ) -> Resolver {
+        self.push_scope(Scope::ModuleScope(ModuleItemMap {
+            package_defs,
+            module_id,
+        }))
     }
 
+    /// Adds an expression scope from which it can resolve names
     pub(crate) fn push_expr_scope(
         self,
+        owner: DefWithBodyId,
         expr_scopes: Arc<ExprScopes>,
         scope_id: LocalScopeId,
     ) -> Resolver {
         self.push_scope(Scope::ExprScope(ExprScope {
+            owner,
             expr_scopes,
             scope_id,
         }))
     }
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum Resolution {
-    /// An item
-    Def(ModuleDef),
-    /// A local binding (only value namespace)
-    LocalBinding(PatId),
-}
-
 impl Resolver {
-    pub fn resolve_name(&self, db: &dyn HirDatabase, name: &Name) -> PerNs<Resolution> {
-        let mut resolution = PerNs::none();
+    // TODO: This function is useful when we need to resolve paths between modules
+    // /// Resolves a path
+    // fn resolve_module_path(
+    //     &self,
+    //     db: &dyn DefDatabase,
+    //     path: &Path,
+    // ) -> PerNs<(ItemDefinitionId, Visibility)> {
+    //     let (defs, module) = match self.module_scope() {
+    //         None => return PerNs::none(),
+    //         Some(it) => it,
+    //     };
+    //
+    //     let (module_res, segment_index) = defs.resolve_path_in_module(db, module, &path);
+    //
+    //     // If the `segment_index` contains a value it means the path didn't resolve completely yet
+    //     if segment_index.is_some() {
+    //         return PerNs::none();
+    //     }
+    //
+    //     module_res
+    // }
+
+    /// Returns the `Module` scope of the resolver
+    fn module_scope(&self) -> Option<(&PackageDefs, LocalModuleId)> {
+        self.scopes.iter().rev().find_map(|scope| match scope {
+            Scope::ModuleScope(m) => Some((&*m.package_defs, m.module_id)),
+            _ => None,
+        })
+    }
+
+    /// Resolves the visibility of the the `RawVisibility`
+    pub fn resolve_visibility(
+        &self,
+        db: &dyn DefDatabase,
+        visibility: &RawVisibility,
+    ) -> Option<Visibility> {
+        self.module_scope().map(|(package_defs, module)| {
+            package_defs
+                .module_tree
+                .resolve_visibility(db, module, visibility)
+        })
+    }
+
+    /// Resolves the specified `path` as a value. Returns a result that can also indicate that the
+    /// path was only partially resolved.
+    pub fn resolve_path_as_value(
+        &self,
+        db: &dyn DefDatabase,
+        path: &Path,
+    ) -> Option<ResolveValueResult> {
+        let segments_count = path.segments.len();
+        let first_name = path.segments.first()?;
         for scope in self.scopes.iter().rev() {
-            resolution = resolution.or(scope.resolve_name(db, name));
-            if resolution.is_both() {
-                return resolution;
-            }
+            match scope {
+                Scope::ExprScope(scope) if segments_count <= 1 => {
+                    let entry = scope
+                        .expr_scopes
+                        .entries(scope.scope_id)
+                        .iter()
+                        .find(|entry| entry.name() == first_name);
+
+                    if let Some(e) = entry {
+                        return Some(ResolveValueResult::ValueNs(
+                            ValueNs::LocalBinding(e.pat()),
+                            Visibility::Public,
+                        ));
+                    }
+                }
+                Scope::ExprScope(_) => continue,
+
+                Scope::ModuleScope(m) => {
+                    let (module_def, idx) =
+                        m.package_defs
+                            .resolve_path_in_module(db, m.module_id, &path);
+                    return match idx {
+                        None => {
+                            let (value, vis) = to_value_ns(module_def)?;
+                            Some(ResolveValueResult::ValueNs(value, vis))
+                        }
+                        Some(idx) => {
+                            let ty = match module_def.take_types()? {
+                                (ItemDefinitionId::StructId(id), _) => TypeNs::StructId(id),
+                                (ItemDefinitionId::TypeAliasId(id), _) => TypeNs::TypeAliasId(id),
+                                (ItemDefinitionId::PrimitiveType(id), _) => {
+                                    TypeNs::PrimitiveType(id)
+                                }
+                                (ItemDefinitionId::ModuleId(_), _)
+                                | (ItemDefinitionId::FunctionId(_), _) => return None,
+                            };
+                            Some(ResolveValueResult::Partial(ty, idx))
+                        }
+                    };
+                }
+            };
+        }
+        return None;
+
+        fn to_value_ns(
+            per_ns: PerNs<(ItemDefinitionId, Visibility)>,
+        ) -> Option<(ValueNs, Visibility)> {
+            let (res, vis) = match per_ns.take_values()? {
+                (ItemDefinitionId::FunctionId(id), vis) => (ValueNs::FunctionId(id), vis),
+                (ItemDefinitionId::StructId(id), vis) => (ValueNs::StructId(id), vis),
+                (ItemDefinitionId::ModuleId(_), _)
+                | (ItemDefinitionId::TypeAliasId(_), _)
+                | (ItemDefinitionId::PrimitiveType(_), _) => return None,
+            };
+            Some((res, vis))
         }
-        resolution
     }
 
-    /// Returns the fully resolved path if we were able to resolve it.
-    /// otherwise returns `PerNs::none`
-    pub fn resolve_path_without_assoc_items(
+    /// Resolves the specified `path` as a value. Returns either `None` or the resolved path value.
+    pub fn resolve_path_as_value_fully(
         &self,
-        db: &dyn HirDatabase,
+        db: &dyn DefDatabase,
         path: &Path,
-    ) -> PerNs<Resolution> {
-        if let Some(name) = path.as_ident() {
-            self.resolve_name(db, name)
-        } else {
-            PerNs::none()
+    ) -> Option<(ValueNs, Visibility)> {
+        match self.resolve_path_as_value(db, path)? {
+            ResolveValueResult::ValueNs(val, vis) => Some((val, vis)),
+            ResolveValueResult::Partial(..) => None,
         }
     }
-}
 
-impl Scope {
-    fn resolve_name(&self, db: &dyn HirDatabase, name: &Name) -> PerNs<Resolution> {
-        match self {
-            Scope::ModuleScope(m) => db
-                .module_scope(m.file_id)
-                .get(name)
-                .map(|r| r.def)
-                .unwrap_or_else(PerNs::none)
-                .map(Resolution::Def),
-            Scope::ExprScope(e) => {
-                let entry = e
-                    .expr_scopes
-                    .entries(e.scope_id)
-                    .iter()
-                    .find(|entry| entry.name() == name);
-                match entry {
-                    Some(e) => PerNs::values(Resolution::LocalBinding(e.pat())),
-                    None => PerNs::none(),
+    /// Resolves the specified `path` as a type. Returns a result that can also indicate that the
+    /// path was only partially resolved.
+    pub fn resolve_path_as_type(
+        &self,
+        db: &dyn DefDatabase,
+        path: &Path,
+    ) -> Option<(TypeNs, Visibility, Option<usize>)> {
+        for scope in self.scopes.iter().rev() {
+            match scope {
+                Scope::ExprScope(_) => continue,
+                Scope::ModuleScope(m) => {
+                    let (module_def, idx) =
+                        m.package_defs
+                            .resolve_path_in_module(db, m.module_id, &path);
+                    let (res, vis) = to_type_ns(module_def)?;
+                    return Some((res, vis, idx));
                 }
             }
         }
+        return None;
+        fn to_type_ns(
+            per_ns: PerNs<(ItemDefinitionId, Visibility)>,
+        ) -> Option<(TypeNs, Visibility)> {
+            let (res, vis) = match per_ns.take_types()? {
+                (ItemDefinitionId::StructId(id), vis) => (TypeNs::StructId(id), vis),
+                (ItemDefinitionId::TypeAliasId(id), vis) => (TypeNs::TypeAliasId(id), vis),
+                (ItemDefinitionId::PrimitiveType(id), vis) => (TypeNs::PrimitiveType(id), vis),
+
+                (ItemDefinitionId::ModuleId(_), _) | (ItemDefinitionId::FunctionId(_), _) => {
+                    return None;
+                }
+            };
+            Some((res, vis))
+        }
     }
 
-    //    fn collect_names(&self, db: &dyn HirDatabase, f: &mut dyn FnMut(Name, PerNs<Resolution>)) {
-    //        match self {
-    //            Scope::ModuleScope(m) => {
-    //                // FIXME: should we provide `self` here?
-    //                // f(
-    //                //     Name::self_param(),
-    //                //     PerNs::types(Resolution::Def {
-    //                //         def: m.module.into(),
-    //                //     }),
-    //                // );
-    //                m.crate_def_map[m.module_id].scope.entries().for_each(|(name, res)| {
-    //                    f(name.clone(), res.def.map(Resolution::Def));
-    //                });
-    //                m.crate_def_map.extern_prelude().iter().for_each(|(name, def)| {
-    //                    f(name.clone(), PerNs::types(Resolution::Def(*def)));
-    //                });
-    //                if let Some(prelude) = m.crate_def_map.prelude() {
-    //                    let prelude_def_map = db.crate_def_map(prelude.krate);
-    //                    prelude_def_map[prelude.module_id].scope.entries().for_each(|(name, res)| {
-    //                        f(name.clone(), res.def.map(Resolution::Def));
-    //                    });
-    //                }
-    //            }
-    //            Scope::GenericParams(gp) => {
-    //                for param in &gp.params {
-    //                    f(param.name.clone(), PerNs::types(Resolution::GenericParam(param.idx)))
-    //                }
-    //            }
-    //            Scope::ImplBlockScope(i) => {
-    //                f(SELF_TYPE, PerNs::types(Resolution::SelfType(*i)));
-    //            }
-    //            Scope::ExprScope(e) => {
-    //                e.expr_scopes.entries(e.scope_id).iter().for_each(|e| {
-    //                    f(e.name().clone(), PerNs::values(Resolution::LocalBinding(e.pat())));
-    //                });
-    //            }
-    //        }
-    //    }
+    /// Resolves the specified `path` as a type. Returns either `None` or the resolved path type.
+    pub fn resolve_path_as_type_fully(
+        &self,
+        db: &dyn DefDatabase,
+        path: &Path,
+    ) -> Option<(TypeNs, Visibility)> {
+        let (res, visibility, unresolved) = self.resolve_path_as_type(db, path)?;
+        if unresolved.is_some() {
+            return None;
+        }
+        Some((res, visibility))
+    }
+
+    /// Returns the module from which this instance resolves names
+    pub fn module(&self) -> Option<ModuleId> {
+        let (package_defs, local_id) = self.module_scope()?;
+        Some(ModuleId {
+            package: package_defs.module_tree.package,
+            local_id,
+        })
+    }
+}
+
+/// Returns a resolver applicable to the specified expression
+pub fn resolver_for_expr(db: &dyn DefDatabase, owner: DefWithBodyId, expr_id: ExprId) -> Resolver {
+    let scopes = db.expr_scopes(owner);
+    resolver_for_scope(db, owner, scopes.scope_for(expr_id))
+}
+
+#[allow(clippy::needless_collect)] // false positive https://github.com/rust-lang/rust-clippy/issues/5991
+pub fn resolver_for_scope(
+    db: &dyn DefDatabase,
+    owner: DefWithBodyId,
+    scope_id: Option<LocalScopeId>,
+) -> Resolver {
+    let mut r = owner.resolver(db);
+    let scopes = db.expr_scopes(owner);
+    let scope_chain = scopes.scope_chain(scope_id).collect::<Vec<_>>();
+    for scope in scope_chain.into_iter().rev() {
+        r = r.push_expr_scope(owner, Arc::clone(&scopes), scope);
+    }
+    r
+}
+
+pub trait HasResolver: Copy {
+    /// Builds a resolver for type or value references inside this instance.
+    fn resolver(self, db: &dyn DefDatabase) -> Resolver;
+}
+
+impl HasResolver for ModuleId {
+    fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+        let defs = db.package_defs(self.package);
+        Resolver::default().push_module_scope(defs, self.local_id)
+    }
+}
+
+impl HasResolver for FunctionId {
+    fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+        self.lookup(db).module.resolver(db)
+    }
+}
+
+impl HasResolver for StructId {
+    fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+        self.lookup(db).module.resolver(db)
+    }
+}
+
+impl HasResolver for TypeAliasId {
+    fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+        self.lookup(db).module.resolver(db)
+    }
+}
+
+impl HasResolver for DefWithBodyId {
+    fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+        match self {
+            DefWithBodyId::FunctionId(f) => f.resolver(db),
+        }
+    }
 }
diff --git a/crates/mun_hir/src/ty.rs b/crates/mun_hir/src/ty.rs
index 68aabc723..aa2d17ba4 100644
--- a/crates/mun_hir/src/ty.rs
+++ b/crates/mun_hir/src/ty.rs
@@ -149,7 +149,7 @@ impl Ty {
     pub fn guid_string(&self, db: &dyn HirDatabase) -> Option<String> {
         self.as_simple().and_then(|ty_ctor| match ty_ctor {
             TypeCtor::Struct(s) => {
-                let name = s.name(db.upcast()).to_string();
+                let name = s.name(db).to_string();
 
                 Some(if s.data(db.upcast()).memory_kind == StructMemoryKind::GC {
                     format!("struct {}", name)
@@ -270,8 +270,8 @@ impl HirDisplay for ApplicationTy {
             TypeCtor::Float(ty) => write!(f, "{}", ty),
             TypeCtor::Int(ty) => write!(f, "{}", ty),
             TypeCtor::Bool => write!(f, "bool"),
-            TypeCtor::Struct(def) => write!(f, "{}", def.name(f.db.upcast())),
-            TypeCtor::TypeAlias(def) => write!(f, "{}", def.name(f.db.upcast())),
+            TypeCtor::Struct(def) => write!(f, "{}", def.name(f.db)),
+            TypeCtor::TypeAlias(def) => write!(f, "{}", def.name(f.db)),
             TypeCtor::Never => write!(f, "never"),
             TypeCtor::FnDef(CallableDef::Function(def)) => {
                 let sig = fn_sig_for_fn(f.db, def);
@@ -283,7 +283,7 @@ impl HirDisplay for ApplicationTy {
             }
             TypeCtor::FnDef(CallableDef::Struct(def)) => {
                 let sig = fn_sig_for_struct_constructor(f.db, def);
-                let name = def.name(f.db.upcast());
+                let name = def.name(f.db);
                 write!(f, "ctor {}", name)?;
                 write!(f, "(")?;
                 f.write_joined(sig.params(), ", ")?;
diff --git a/crates/mun_hir/src/ty/infer.rs b/crates/mun_hir/src/ty/infer.rs
index e61a92e0b..9c2e5a983 100644
--- a/crates/mun_hir/src/ty/infer.rs
+++ b/crates/mun_hir/src/ty/infer.rs
@@ -1,12 +1,10 @@
 use crate::{
-    adt::StructKind,
     arena::map::ArenaMap,
-    code_model::{DefWithBody, DefWithStruct, Struct},
+    code_model::{Struct, StructKind},
     diagnostics::DiagnosticSink,
-    expr,
     expr::{Body, Expr, ExprId, Literal, Pat, PatId, RecordLitField, Statement, UnaryOp},
     name_resolution::Namespace,
-    resolve::{Resolution, Resolver},
+    resolve::{Resolver, TypeNs, ValueNs},
     ty::infer::diagnostics::InferenceDiagnostic,
     ty::infer::type_variable::TypeVariableTable,
     ty::lower::LowerDiagnostic,
@@ -24,6 +22,8 @@ mod type_variable;
 mod unify;
 
 use crate::expr::{LiteralFloat, LiteralFloatKind, LiteralInt, LiteralIntKind};
+use crate::ids::DefWithBodyId;
+use crate::resolve::{resolver_for_expr, HasResolver};
 use crate::ty::primitives::{FloatTy, IntTy};
 use std::mem;
 pub use type_variable::TypeVarId;
@@ -84,13 +84,13 @@ impl InferenceResult {
 
 /// The entry point of type inference. This method takes a body and infers the types of all the
 /// expressions and patterns. Diagnostics are also reported and stored in the `InferenceResult`.
-pub fn infer_query(db: &dyn HirDatabase, def: DefWithBody) -> Arc<InferenceResult> {
-    let body = def.body(db);
-    let resolver = def.resolver(db);
+pub fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<InferenceResult> {
+    let body = db.body(def);
+    let resolver = def.resolver(db.upcast());
     let mut ctx = InferenceResultBuilder::new(db, body, resolver);
 
     match def {
-        DefWithBody::Function(_) => ctx.infer_signature(),
+        DefWithBodyId::FunctionId(_) => ctx.infer_signature(),
     }
 
     ctx.infer_body();
@@ -197,6 +197,7 @@ impl<'a> InferenceResultBuilder<'a> {
                     InferenceDiagnostic::UnresolvedType { id }
                 }
                 LowerDiagnostic::CyclicType { id } => InferenceDiagnostic::CyclicType { id },
+                LowerDiagnostic::TypeIsPrivate { id } => InferenceDiagnostic::TypeIsPrivate { id },
             };
             self.diagnostics.push(diag);
         }
@@ -294,7 +295,7 @@ impl<'a> InferenceResultBuilder<'a> {
             Expr::Missing => Ty::Unknown,
             Expr::Path(p) => {
                 // FIXME this could be more efficient...
-                let resolver = expr::resolver_for_expr(self.body.clone(), self.db, tgt_expr);
+                let resolver = resolver_for_expr(self.db.upcast(), self.body.owner(), tgt_expr);
                 self.infer_path_expr(&resolver, p, tgt_expr, check_params)
                     .unwrap_or(Ty::Unknown)
             }
@@ -312,7 +313,7 @@ impl<'a> InferenceResultBuilder<'a> {
                     let lhs_ty = self.infer_expr(*lhs, &lhs_expected);
                     if let BinaryOp::Assignment { op: _op } = op {
                         let resolver =
-                            expr::resolver_for_expr(self.body.clone(), self.db, tgt_expr);
+                            resolver_for_expr(self.db.upcast(), self.body.owner(), tgt_expr);
                         if !self.check_place_expression(&resolver, *lhs) {
                             self.diagnostics.push(InferenceDiagnostic::InvalidLHS {
                                 id: tgt_expr,
@@ -549,14 +550,16 @@ impl<'a> InferenceResultBuilder<'a> {
 
         match callee_ty {
             ty_app!(TypeCtor::Struct(s)) => {
-                // Erroneously found either a unit struct or tuple struct literal
-                let struct_data = s.data(self.db.upcast());
-                self.diagnostics
-                    .push(InferenceDiagnostic::MismatchedStructLit {
-                        id: tgt_expr,
-                        expected: struct_data.kind,
-                        found: StructKind::Tuple,
-                    });
+                // Erroneously found either a unit struct or record struct literal. Record struct
+                // literals can never be used as a value so that will have already been reported.
+                if s.data(self.db.upcast()).kind == StructKind::Unit {
+                    self.diagnostics
+                        .push(InferenceDiagnostic::MismatchedStructLit {
+                            id: tgt_expr,
+                            expected: StructKind::Unit,
+                            found: StructKind::Tuple,
+                        });
+                }
 
                 // Still derive subtypes
                 for arg in args.iter() {
@@ -581,6 +584,10 @@ impl<'a> InferenceResultBuilder<'a> {
 
                 ret_ty
             }
+            Ty::Unknown => {
+                // Error has already been emitted somewhere else
+                Ty::Unknown
+            }
             _ => {
                 self.diagnostics
                     .push(InferenceDiagnostic::ExpectedFunction {
@@ -680,35 +687,80 @@ impl<'a> InferenceResultBuilder<'a> {
         id: ExprId,
         check_params: &CheckParams,
     ) -> Option<Ty> {
-        let resolution = match resolver
-            .resolve_path_without_assoc_items(self.db, path)
-            .take_values()
-        {
-            Some(resolution) => resolution,
-            None => {
-                self.diagnostics
-                    .push(InferenceDiagnostic::UnresolvedValue { id: id.into() });
-                return None;
-            }
-        };
+        match resolver.resolve_path_as_value_fully(self.db.upcast(), path) {
+            Some((value, vis)) => {
+                // Check visibility of this item
+                if !vis.is_visible_from(
+                    self.db,
+                    self.resolver
+                        .module()
+                        .expect("resolver must have a module to be able to resolve modules"),
+                ) {
+                    self.diagnostics
+                        .push(diagnostics::InferenceDiagnostic::PathIsPrivate { id })
+                }
 
-        match resolution {
-            Resolution::LocalBinding(pat) => {
-                let ty = self.type_of_pat.get(pat)?.clone();
-                //let ty = self.resolve_ty_as_possible(&mut vec![], ty);
-                Some(ty)
+                // Match based on what type of value we found
+                match value {
+                    ValueNs::LocalBinding(pat) => Some(self.type_of_pat.get(pat)?.clone()),
+                    ValueNs::FunctionId(f) => {
+                        let (ty, _) = self
+                            .db
+                            .type_for_def(TypableDef::Function(f.into()), Namespace::Values);
+                        Some(ty)
+                    }
+                    ValueNs::StructId(s) => {
+                        if check_params.is_unit_struct {
+                            self.check_unit_struct_lit(id, s.into())
+                        }
+                        let (ty, _) = self
+                            .db
+                            .type_for_def(TypableDef::Struct(s.into()), Namespace::Values);
+                        Some(ty)
+                    }
+                }
             }
-            Resolution::Def(def) => {
-                let typable: Option<TypableDef> = def.into();
-                let typable = typable?;
-                // TODO: Add detection of cyclick types
-                let (ty, _) = self.db.type_for_def(typable, Namespace::Values);
-                if check_params.is_unit_struct {
-                    if let Some(s) = ty.as_struct() {
-                        self.check_unit_struct_lit(id, s);
+
+            None => {
+                // If no value was found, try to resolve the path as a type. This will always result
+                // in an error but it does provide much better diagnostics.
+                let ty = resolver.resolve_path_as_type_fully(self.db.upcast(), path);
+                if let Some((TypeNs::StructId(struct_id), _)) = ty {
+                    // We can only really get here if the struct is actually a record. Both other
+                    // types can be seen as a values because they have a constructor.
+                    debug_assert_eq!(
+                        Struct::from(struct_id).data(self.db.upcast()).kind,
+                        StructKind::Record
+                    );
+
+                    // Should it be a unit struct?
+                    if check_params.is_unit_struct {
+                        self.diagnostics
+                            .push(InferenceDiagnostic::MismatchedStructLit {
+                                id,
+                                expected: StructKind::Record,
+                                found: StructKind::Unit,
+                            });
+                    } else {
+                        self.diagnostics
+                            .push(InferenceDiagnostic::MismatchedStructLit {
+                                id,
+                                expected: StructKind::Record,
+                                found: StructKind::Tuple,
+                            });
                     }
+
+                    let (ty, _) = self
+                        .db
+                        .type_for_def(TypableDef::Struct(struct_id.into()), Namespace::Values);
+                    return Some(ty);
                 }
-                Some(ty)
+
+                // If the path also cannot be resolved as type, it must be considered an invalid
+                // value and there is nothing we can make of this path.
+                self.diagnostics
+                    .push(InferenceDiagnostic::UnresolvedValue { id: id.into() });
+                None
             }
         }
     }
@@ -749,44 +801,6 @@ impl<'a> InferenceResultBuilder<'a> {
         }
     }
 
-    fn resolve_struct(&mut self, path: Option<&Path>) -> (Ty, Option<DefWithStruct>) {
-        let path = match path {
-            Some(path) => path,
-            None => return (Ty::Unknown, None),
-        };
-        let resolver = &self.resolver;
-        let resolution = match resolver
-            .resolve_path_without_assoc_items(self.db, &path)
-            .take_types()
-        {
-            Some(resolution) => resolution,
-            None => return (Ty::Unknown, None),
-        };
-
-        match resolution {
-            Resolution::LocalBinding(pat) => {
-                let ty = self
-                    .type_of_pat
-                    .get(pat)
-                    .map_or(Ty::Unknown, |ty| ty.clone());
-                //let ty = self.resolve_ty_as_possible(&mut vec![], ty);
-                (ty, None)
-            }
-            Resolution::Def(def) => {
-                if let Some(typable) = def.into() {
-                    match typable {
-                        TypableDef::Struct(s) => (s.ty(self.db), Some(s.into())),
-                        TypableDef::BuiltinType(_)
-                        | TypableDef::Function(_)
-                        | TypableDef::TypeAlias(_) => (Ty::Unknown, None),
-                    }
-                } else {
-                    unreachable!();
-                }
-            }
-        }
-    }
-
     fn infer_block(
         &mut self,
         statements: &[Statement],
@@ -1000,15 +1014,15 @@ impl From<PatId> for ExprOrPatId {
 }
 
 mod diagnostics {
-    use crate::diagnostics::{
-        AccessUnknownField, BreakOutsideLoop, BreakWithValueOutsideLoop, CannotApplyBinaryOp,
-        CannotApplyUnaryOp, ExpectedFunction, FieldCountMismatch, IncompatibleBranch, InvalidLHS,
-        LiteralOutOfRange, MismatchedStructLit, MismatchedType, MissingElseBranch, MissingFields,
-        NoFields, NoSuchField, ParameterCountMismatch, ReturnMissingExpression,
-    };
+    use crate::diagnostics::PrivateAccess;
     use crate::{
-        adt::StructKind,
-        code_model::src::HasSource,
+        code_model::{src::HasSource, StructKind},
+        diagnostics::{
+            AccessUnknownField, BreakOutsideLoop, BreakWithValueOutsideLoop, CannotApplyBinaryOp,
+            CannotApplyUnaryOp, ExpectedFunction, FieldCountMismatch, IncompatibleBranch,
+            InvalidLHS, LiteralOutOfRange, MismatchedStructLit, MismatchedType, MissingElseBranch,
+            MissingFields, NoFields, NoSuchField, ParameterCountMismatch, ReturnMissingExpression,
+        },
         diagnostics::{CyclicType, DiagnosticSink, UnresolvedType, UnresolvedValue},
         ty::infer::ExprOrPatId,
         type_ref::LocalTypeRefId,
@@ -1103,6 +1117,12 @@ mod diagnostics {
             id: ExprId,
             literal_ty: IntTy,
         },
+        TypeIsPrivate {
+            id: LocalTypeRefId,
+        },
+        PathIsPrivate {
+            id: ExprId,
+        },
     }
 
     impl InferenceDiagnostic {
@@ -1137,6 +1157,26 @@ mod diagnostics {
                     let type_ref = body.type_ref_syntax(*id).expect("If this is not found, it must be a type ref generated by the library which should never be unresolved.");
                     sink.push(CyclicType { file, type_ref });
                 }
+                InferenceDiagnostic::TypeIsPrivate { id } => {
+                    let type_ref = body.type_ref_syntax(*id).expect("If this is not found, it must be a type ref generated by the library which should never be unresolved.");
+                    sink.push(PrivateAccess {
+                        file,
+                        expr: type_ref.syntax_node_ptr(),
+                    });
+                }
+                InferenceDiagnostic::PathIsPrivate { id } => {
+                    let expr_syntax = body
+                        .expr_syntax(*id)
+                        .map(|ptr| {
+                            ptr.value
+                                .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr())
+                        })
+                        .expect("could not resolve expression to syntax node");
+                    sink.push(PrivateAccess {
+                        file,
+                        expr: expr_syntax,
+                    });
+                }
                 InferenceDiagnostic::ParameterCountMismatch {
                     id,
                     expected,
diff --git a/crates/mun_hir/src/ty/infer/place_expr.rs b/crates/mun_hir/src/ty/infer/place_expr.rs
index 2f352ef19..642a3c508 100644
--- a/crates/mun_hir/src/ty/infer/place_expr.rs
+++ b/crates/mun_hir/src/ty/infer/place_expr.rs
@@ -1,4 +1,5 @@
-use crate::{ty::infer::InferenceResultBuilder, Expr, ExprId, Path, Resolution, Resolver};
+use crate::resolve::ValueNs;
+use crate::{ty::infer::InferenceResultBuilder, Expr, ExprId, Path, Resolver};
 use std::sync::Arc;
 
 impl<'a> InferenceResultBuilder<'a> {
@@ -15,17 +16,9 @@ impl<'a> InferenceResultBuilder<'a> {
 
     /// Checks if the specified path references a memory location.
     fn check_place_path(&mut self, resolver: &Resolver, path: &Path) -> bool {
-        let resolution = match resolver
-            .resolve_path_without_assoc_items(self.db, path)
-            .take_values()
-        {
-            Some(resolution) => resolution,
-            None => return false,
-        };
-
-        match resolution {
-            Resolution::LocalBinding(_) => true,
-            Resolution::Def(_) => false,
+        match resolver.resolve_path_as_value_fully(self.db.upcast(), path) {
+            Some((ValueNs::LocalBinding(_), _)) => true,
+            Some((ValueNs::FunctionId(_), _)) | Some((ValueNs::StructId(_), _)) | None => false,
         }
     }
 }
diff --git a/crates/mun_hir/src/ty/lower.rs b/crates/mun_hir/src/ty/lower.rs
index 2a10dab25..370a4b1a8 100644
--- a/crates/mun_hir/src/ty/lower.rs
+++ b/crates/mun_hir/src/ty/lower.rs
@@ -1,15 +1,17 @@
 pub(crate) use self::diagnostics::LowerDiagnostic;
-use crate::adt::StructKind;
-use crate::arena::map::ArenaMap;
-use crate::builtin_type::BuiltinType;
-use crate::diagnostics::DiagnosticSink;
-use crate::name_resolution::Namespace;
-use crate::resolve::{Resolution, Resolver};
-use crate::ty::{FnSig, Ty, TypeCtor};
-use crate::type_ref::{LocalTypeRefId, TypeRef, TypeRefMap, TypeRefSourceMap};
-use crate::{FileId, Function, HirDatabase, ModuleDef, Path, Struct, TypeAlias};
-use std::ops::Index;
-use std::sync::Arc;
+use crate::resolve::{HasResolver, TypeNs};
+use crate::{
+    arena::map::ArenaMap,
+    code_model::StructKind,
+    diagnostics::DiagnosticSink,
+    name_resolution::Namespace,
+    primitive_type::PrimitiveType,
+    resolve::Resolver,
+    ty::{FnSig, Ty, TypeCtor},
+    type_ref::{LocalTypeRefId, TypeRef, TypeRefMap, TypeRefSourceMap},
+    FileId, Function, HirDatabase, ModuleDef, Path, Struct, TypeAlias,
+};
+use std::{ops::Index, sync::Arc};
 
 #[derive(Clone, PartialEq, Eq, Debug)]
 pub(crate) struct LowerResult {
@@ -66,7 +68,7 @@ impl Ty {
         type_ref: LocalTypeRefId,
     ) -> Ty {
         let res = match &type_ref_map[type_ref] {
-            TypeRef::Path(path) => Ty::from_hir_path(db, resolver, path),
+            TypeRef::Path(path) => Ty::from_hir_path(db, resolver, type_ref, path, diagnostics),
             TypeRef::Error => Some((Ty::Unknown, false)),
             TypeRef::Empty => Some((Ty::Empty, false)),
             TypeRef::Never => Some((Ty::simple(TypeCtor::Never), false)),
@@ -82,30 +84,31 @@ impl Ty {
         }
     }
 
-    pub(crate) fn from_hir_path(
+    fn from_hir_path(
         db: &dyn HirDatabase,
         resolver: &Resolver,
+        type_ref: LocalTypeRefId,
         path: &Path,
+        diagnostics: &mut Vec<LowerDiagnostic>,
     ) -> Option<(Self, bool)> {
-        let resolution = resolver
-            .resolve_path_without_assoc_items(db, path)
-            .take_types();
-
-        let def = match resolution {
-            Some(Resolution::Def(def)) => def,
-            Some(Resolution::LocalBinding(..)) => {
-                // this should never happen
-                panic!("path resolved to local binding in type ns");
-            }
-            None => return None,
+        // Find the type
+        let (ty, vis) = resolver.resolve_path_as_type_fully(db.upcast(), path)?;
+
+        // Get the definition and visibility
+        let def = match ty {
+            TypeNs::StructId(id) => TypableDef::Struct(id.into()),
+            TypeNs::TypeAliasId(id) => TypableDef::TypeAlias(id.into()),
+            TypeNs::PrimitiveType(id) => TypableDef::PrimitiveType(id),
         };
 
-        let typable: TypableDef = match def.into() {
-            None => return None,
-            Some(it) => it,
-        };
+        // Get the current module and see if the type is visible from here
+        if let Some(module) = resolver.module() {
+            if !vis.is_visible_from(db, module) {
+                diagnostics.push(LowerDiagnostic::TypeIsPrivate { id: type_ref })
+            }
+        }
 
-        Some(db.type_for_def(typable, Namespace::Types))
+        Some(db.type_for_def(def, Namespace::Types))
     }
 }
 
@@ -128,18 +131,18 @@ pub fn types_from_hir(
 
 pub fn lower_struct_query(db: &dyn HirDatabase, s: Struct) -> Arc<LowerBatchResult> {
     let data = s.data(db.upcast());
-    types_from_hir(db, &s.resolver(db), data.type_ref_map())
+    types_from_hir(db, &s.id.resolver(db.upcast()), data.type_ref_map())
 }
 
 pub fn lower_type_alias_query(db: &dyn HirDatabase, t: TypeAlias) -> Arc<LowerBatchResult> {
     let data = t.data(db.upcast());
-    types_from_hir(db, &t.resolver(db), data.type_ref_map())
+    types_from_hir(db, &t.id.resolver(db.upcast()), data.type_ref_map())
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub enum TypableDef {
     Function(Function),
-    BuiltinType(BuiltinType),
+    PrimitiveType(PrimitiveType),
     Struct(Struct),
     TypeAlias(TypeAlias),
 }
@@ -150,9 +153,9 @@ impl From<Function> for TypableDef {
     }
 }
 
-impl From<BuiltinType> for TypableDef {
-    fn from(f: BuiltinType) -> Self {
-        TypableDef::BuiltinType(f)
+impl From<PrimitiveType> for TypableDef {
+    fn from(f: PrimitiveType) -> Self {
+        TypableDef::PrimitiveType(f)
     }
 }
 
@@ -166,9 +169,10 @@ impl From<ModuleDef> for Option<TypableDef> {
     fn from(d: ModuleDef) -> Self {
         match d {
             ModuleDef::Function(f) => Some(TypableDef::Function(f)),
-            ModuleDef::BuiltinType(t) => Some(TypableDef::BuiltinType(t)),
+            ModuleDef::PrimitiveType(t) => Some(TypableDef::PrimitiveType(t)),
             ModuleDef::Struct(t) => Some(TypableDef::Struct(t)),
             ModuleDef::TypeAlias(t) => Some(TypableDef::TypeAlias(t)),
+            ModuleDef::Module(_) => None,
         }
     }
 }
@@ -197,14 +201,14 @@ impl CallableDef {
 pub(crate) fn type_for_def(db: &dyn HirDatabase, def: TypableDef, ns: Namespace) -> (Ty, bool) {
     let ty = match (def, ns) {
         (TypableDef::Function(f), Namespace::Values) => type_for_fn(db, f),
-        (TypableDef::BuiltinType(t), Namespace::Types) => type_for_builtin(t),
+        (TypableDef::PrimitiveType(t), Namespace::Types) => type_for_primitive(t),
         (TypableDef::Struct(s), Namespace::Values) => type_for_struct_constructor(db, s),
         (TypableDef::Struct(s), Namespace::Types) => type_for_struct(db, s),
         (TypableDef::TypeAlias(t), Namespace::Types) => type_for_type_alias(db, t),
 
         // 'error' cases:
         (TypableDef::Function(_), Namespace::Types) => Ty::Unknown,
-        (TypableDef::BuiltinType(_), Namespace::Values) => Ty::Unknown,
+        (TypableDef::PrimitiveType(_), Namespace::Values) => Ty::Unknown,
         (TypableDef::TypeAlias(_), Namespace::Values) => Ty::Unknown,
     };
     (ty, false)
@@ -221,11 +225,11 @@ pub(crate) fn type_for_cycle_recover(
 }
 
 /// Build the declared type of a static.
-fn type_for_builtin(def: BuiltinType) -> Ty {
+fn type_for_primitive(def: PrimitiveType) -> Ty {
     Ty::simple(match def {
-        BuiltinType::Float(f) => TypeCtor::Float(f.into()),
-        BuiltinType::Int(i) => TypeCtor::Int(i.into()),
-        BuiltinType::Bool => TypeCtor::Bool,
+        PrimitiveType::Float(f) => TypeCtor::Float(f.into()),
+        PrimitiveType::Int(i) => TypeCtor::Int(i.into()),
+        PrimitiveType::Bool => TypeCtor::Bool,
     })
 }
 
@@ -243,8 +247,8 @@ pub(crate) fn callable_item_sig(db: &dyn HirDatabase, def: CallableDef) -> FnSig
 }
 
 pub(crate) fn fn_sig_for_fn(db: &dyn HirDatabase, def: Function) -> FnSig {
-    let data = def.data(db);
-    let resolver = def.resolver(db);
+    let data = def.data(db.upcast());
+    let resolver = def.id.resolver(db.upcast());
     let params = data
         .params()
         .iter()
@@ -256,7 +260,7 @@ pub(crate) fn fn_sig_for_fn(db: &dyn HirDatabase, def: Function) -> FnSig {
 
 pub(crate) fn fn_sig_for_struct_constructor(db: &dyn HirDatabase, def: Struct) -> FnSig {
     let data = def.data(db.upcast());
-    let resolver = def.resolver(db);
+    let resolver = def.id.resolver(db.upcast());
     let params = data
         .fields
         .iter()
@@ -282,13 +286,13 @@ fn type_for_struct(_db: &dyn HirDatabase, def: Struct) -> Ty {
 
 fn type_for_type_alias(db: &dyn HirDatabase, def: TypeAlias) -> Ty {
     let data = def.data(db.upcast());
-    let resolver = def.resolver(db);
+    let resolver = def.id.resolver(db.upcast());
     let type_ref = def.type_ref(db);
     Ty::from_hir(db, &resolver, data.type_ref_map(), type_ref).ty
 }
 
 pub mod diagnostics {
-    use crate::diagnostics::{CyclicType, UnresolvedType};
+    use crate::diagnostics::{CyclicType, PrivateAccess, UnresolvedType};
     use crate::{
         diagnostics::DiagnosticSink,
         type_ref::{LocalTypeRefId, TypeRefSourceMap},
@@ -298,6 +302,7 @@ pub mod diagnostics {
     #[derive(Debug, PartialEq, Eq, Clone)]
     pub(crate) enum LowerDiagnostic {
         UnresolvedType { id: LocalTypeRefId },
+        TypeIsPrivate { id: LocalTypeRefId },
         CyclicType { id: LocalTypeRefId },
     }
 
@@ -318,6 +323,10 @@ pub mod diagnostics {
                     file: file_id,
                     type_ref: source_map.type_ref_syntax(*id).unwrap(),
                 }),
+                LowerDiagnostic::TypeIsPrivate { id } => sink.push(PrivateAccess {
+                    file: file_id,
+                    expr: source_map.type_ref_syntax(*id).unwrap().syntax_node_ptr(),
+                }),
             }
         }
     }
diff --git a/crates/mun_hir/src/ty/primitives.rs b/crates/mun_hir/src/ty/primitives.rs
index 97252256a..7e48458fe 100644
--- a/crates/mun_hir/src/ty/primitives.rs
+++ b/crates/mun_hir/src/ty/primitives.rs
@@ -1,4 +1,4 @@
-use crate::builtin_type::{BuiltinFloat, BuiltinInt, FloatBitness, IntBitness, Signedness};
+use crate::primitive_type::{FloatBitness, IntBitness, PrimitiveFloat, PrimitiveInt, Signedness};
 use mun_target::abi;
 use mun_target::abi::Integer;
 use std::fmt::{self};
@@ -197,8 +197,8 @@ impl FloatTy {
     }
 }
 
-impl From<BuiltinInt> for IntTy {
-    fn from(t: BuiltinInt) -> Self {
+impl From<PrimitiveInt> for IntTy {
+    fn from(t: PrimitiveInt) -> Self {
         IntTy {
             signedness: t.signedness,
             bitness: t.bitness,
@@ -206,8 +206,8 @@ impl From<BuiltinInt> for IntTy {
     }
 }
 
-impl From<BuiltinFloat> for FloatTy {
-    fn from(t: BuiltinFloat) -> Self {
+impl From<PrimitiveFloat> for FloatTy {
+    fn from(t: PrimitiveFloat) -> Self {
         FloatTy { bitness: t.bitness }
     }
 }
diff --git a/crates/mun_hir/src/visibility.rs b/crates/mun_hir/src/visibility.rs
new file mode 100644
index 000000000..de2742c0a
--- /dev/null
+++ b/crates/mun_hir/src/visibility.rs
@@ -0,0 +1,84 @@
+use crate::{ids::ModuleId, DefDatabase, HirDatabase, Resolver};
+use mun_syntax::ast;
+use std::iter::successors;
+
+/// Visibility of an item, not yet resolved to an actual module.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum RawVisibility {
+    /// Accessible from self (Self) and all sub-modules
+    This,
+
+    /// Accessible from self, parent module, and all sub-modules
+    Super,
+
+    /// Accessible from everywhere in the package
+    Package,
+
+    /// Accessible from everywhere
+    Public,
+}
+
+impl RawVisibility {
+    /// Constructs a private visibility only visible from `self`.
+    const fn private() -> RawVisibility {
+        RawVisibility::This
+    }
+
+    /// Constructs a `RawVisibility` from an AST node.
+    pub(crate) fn from_ast(node: Option<ast::Visibility>) -> RawVisibility {
+        let node = match node {
+            None => return RawVisibility::private(),
+            Some(node) => node,
+        };
+
+        match node.kind() {
+            ast::VisibilityKind::Pub => RawVisibility::Public,
+            ast::VisibilityKind::PubSuper => RawVisibility::Super,
+            ast::VisibilityKind::PubPackage => RawVisibility::Package,
+        }
+    }
+
+    pub fn resolve(&self, db: &dyn DefDatabase, resolver: &Resolver) -> Visibility {
+        // we fall back to public visibility (i.e. fail open) if the path can't be resolved
+        resolver
+            .resolve_visibility(db, self)
+            .unwrap_or(Visibility::Public)
+    }
+}
+
+/// Visibility of an item, modules resolved.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum Visibility {
+    /// Accessible from the specified module and all sub-modules
+    Module(ModuleId),
+
+    /// Publicly accessible
+    Public,
+}
+
+impl Visibility {
+    /// Returns true if an item with this visibility is accessible from the given module.
+    pub fn is_visible_from(self, db: &dyn HirDatabase, from_module: ModuleId) -> bool {
+        let to_module = match self {
+            Visibility::Module(m) => m,
+            Visibility::Public => return true,
+        };
+
+        let module_tree = db.module_tree(from_module.package);
+        let mut ancestors = successors(Some(from_module.local_id), |m| module_tree[*m].parent);
+
+        ancestors.any(|m| m == to_module.local_id)
+    }
+
+    /// Returns true if an item with this visibility is accessible externally
+    pub fn is_externally_visible(self) -> bool {
+        match self {
+            Visibility::Module(_) => false,
+            Visibility::Public => true,
+        }
+    }
+}
+
+pub trait HasVisibility {
+    fn visibility(&self, db: &dyn HirDatabase) -> Visibility;
+}
diff --git a/crates/mun_language_server/src/change.rs b/crates/mun_language_server/src/change.rs
index d1ac72210..e9af08788 100644
--- a/crates/mun_language_server/src/change.rs
+++ b/crates/mun_language_server/src/change.rs
@@ -7,7 +7,7 @@ use std::sync::Arc;
 /// Represents an atomic change to the state of the `Analysis`
 #[derive(Default)]
 pub struct AnalysisChange {
-    new_roots: Vec<hir::SourceRootId>,
+    new_roots: Vec<(hir::SourceRootId, hir::PackageId)>,
     roots_changed: HashMap<hir::SourceRootId, RootChange>,
     files_changed: Vec<(hir::FileId, Arc<String>)>,
 }
@@ -35,8 +35,8 @@ impl AnalysisChange {
     }
 
     /// Records the addition of a new root
-    pub fn add_root(&mut self, root_id: hir::SourceRootId) {
-        self.new_roots.push(root_id);
+    pub fn add_root(&mut self, root_id: hir::SourceRootId, package_id: hir::PackageId) {
+        self.new_roots.push((root_id, package_id));
     }
 
     /// Records the addition of a new file to a root
@@ -115,9 +115,10 @@ impl AnalysisDatabase {
     /// Applies the specified change to the database
     pub(crate) fn apply_change(&mut self, change: AnalysisChange) {
         // Add new source roots
-        for root_id in change.new_roots {
+        for (root_id, package_id) in change.new_roots {
             let root = hir::SourceRoot::new();
-            self.set_source_root(root_id, Arc::new(root))
+            self.set_source_root(root_id, Arc::new(root));
+            self.set_package_source_root(package_id, root_id);
         }
 
         // Modify existing source roots
diff --git a/crates/mun_language_server/src/diagnostics.rs b/crates/mun_language_server/src/diagnostics.rs
index c6c96eef4..a7c88f460 100644
--- a/crates/mun_language_server/src/diagnostics.rs
+++ b/crates/mun_language_server/src/diagnostics.rs
@@ -1,6 +1,5 @@
 use crate::db::AnalysisDatabase;
-use hir::AstDatabase;
-use hir::InFile;
+use hir::{AstDatabase, InFile, ModuleId, PackageId, SourceDatabase};
 use mun_diagnostics::DiagnosticForWith;
 use mun_syntax::{Location, TextRange};
 use std::cell::RefCell;
@@ -60,7 +59,16 @@ pub(crate) fn diagnostics(db: &AnalysisDatabase, file_id: hir::FileId) -> Vec<Di
             }
         }));
     });
-    hir::Module::from(file_id).diagnostics(db, &mut sink);
+
+    let package_id = PackageId(0);
+    let module_tree = db.module_tree(package_id);
+    if let Some(local_id) = module_tree.module_for_file(file_id) {
+        let module_id = ModuleId {
+            package: package_id,
+            local_id,
+        };
+        hir::Module::from(module_id).diagnostics(db, &mut sink);
+    }
     drop(sink);
 
     // Returns the result
diff --git a/crates/mun_language_server/src/main_loop.rs b/crates/mun_language_server/src/main_loop.rs
index 8be921f43..0f8ecf9fd 100644
--- a/crates/mun_language_server/src/main_loop.rs
+++ b/crates/mun_language_server/src/main_loop.rs
@@ -111,7 +111,7 @@ impl LanguageServerState {
             config
                 .workspace_roots
                 .into_iter()
-                .map(|root| RootEntry::new(root, Box::new(MunFilter {})))
+                .map(|root| RootEntry::new(root.join("src"), Box::new(MunFilter {})))
                 .collect(),
             task_sender,
             ra_vfs::Watch(config.watcher == FilesWatcher::Notify),
@@ -121,7 +121,7 @@ impl LanguageServerState {
         let mut source_roots = Vec::new();
         let mut change = AnalysisChange::new();
         for root in vfs.1.iter() {
-            change.add_root(hir::SourceRootId(root.0));
+            change.add_root(hir::SourceRootId(root.0), hir::PackageId(root.0));
             source_roots.push(hir::SourceRootId(root.0));
         }
 
diff --git a/crates/mun_runtime/benches/util/mod.rs b/crates/mun_runtime/benches/util/mod.rs
index d15d89a82..84bd07406 100644
--- a/crates/mun_runtime/benches/util/mod.rs
+++ b/crates/mun_runtime/benches/util/mod.rs
@@ -31,8 +31,8 @@ pub fn runtime_from_file<P: AsRef<Path>>(p: P) -> Rc<RefCell<mun_runtime::Runtim
         panic!("compiler errors..\n{}", errors);
     }
 
-    let out_path = driver.assembly_output_path(file_id);
-    driver.write_assembly(file_id, true).unwrap();
+    let out_path = driver.assembly_output_path_from_file(file_id);
+    driver.write_all_assemblies(false).unwrap();
     RuntimeBuilder::new(out_path).spawn().unwrap()
 }
 
diff --git a/crates/mun_syntax/src/ast.rs b/crates/mun_syntax/src/ast.rs
index e3cbe062a..4d3be012e 100644
--- a/crates/mun_syntax/src/ast.rs
+++ b/crates/mun_syntax/src/ast.rs
@@ -9,7 +9,7 @@ use crate::{syntax_node::SyntaxNodeChildren, SmolStr, SyntaxKind, SyntaxNode, Sy
 
 pub use self::{
     expr_extensions::*,
-    extensions::{PathSegmentKind, StructKind},
+    extensions::{PathSegmentKind, StructKind, VisibilityKind},
     generated::*,
     tokens::*,
     traits::*,
diff --git a/crates/mun_syntax/src/ast/extensions.rs b/crates/mun_syntax/src/ast/extensions.rs
index 850a27005..a7033e4d6 100644
--- a/crates/mun_syntax/src/ast/extensions.rs
+++ b/crates/mun_syntax/src/ast/extensions.rs
@@ -69,6 +69,7 @@ pub enum PathSegmentKind {
     Name(ast::NameRef),
     SelfKw,
     SuperKw,
+    PackageKw,
 }
 
 impl ast::PathSegment {
@@ -86,6 +87,7 @@ impl ast::PathSegment {
             match self.syntax().first_child_or_token()?.kind() {
                 T![self] => PathSegmentKind::SelfKw,
                 T![super] => PathSegmentKind::SuperKw,
+                T![package] => PathSegmentKind::PackageKw,
                 _ => return None,
             }
         };
diff --git a/crates/mun_syntax/src/ast/generated.rs b/crates/mun_syntax/src/ast/generated.rs
index 869309b35..b9d6a0eeb 100644
--- a/crates/mun_syntax/src/ast/generated.rs
+++ b/crates/mun_syntax/src/ast/generated.rs
@@ -1434,6 +1434,7 @@ impl AstNode for TypeAliasDef {
     }
 }
 impl ast::NameOwner for TypeAliasDef {}
+impl ast::VisibilityOwner for TypeAliasDef {}
 impl ast::DocCommentsOwner for TypeAliasDef {}
 impl TypeAliasDef {
     pub fn type_ref(&self) -> Option<TypeRef> {
diff --git a/crates/mun_syntax/src/grammar.ron b/crates/mun_syntax/src/grammar.ron
index 75fdace2f..b7c628890 100644
--- a/crates/mun_syntax/src/grammar.ron
+++ b/crates/mun_syntax/src/grammar.ron
@@ -211,6 +211,7 @@ Grammar(
             options: ["TypeRef"],
             traits: [
                 "NameOwner",
+                "VisibilityOwner",
                 "DocCommentsOwner",
             ]
         ),
diff --git a/crates/mun_syntax/src/parsing/grammar/declarations.rs b/crates/mun_syntax/src/parsing/grammar/declarations.rs
index 84c17cde1..2b20acc0d 100644
--- a/crates/mun_syntax/src/parsing/grammar/declarations.rs
+++ b/crates/mun_syntax/src/parsing/grammar/declarations.rs
@@ -1,7 +1,7 @@
 use super::*;
 use crate::T;
 
-pub(super) const DECLARATION_RECOVERY_SET: TokenSet = token_set![FN_KW, PUB_KW, STRUCT_KW];
+pub(super) const DECLARATION_RECOVERY_SET: TokenSet = TokenSet::new(&[T![fn], T![pub], T![struct]]);
 
 pub(super) fn mod_contents(p: &mut Parser) {
     while !p.at(EOF) {
@@ -77,7 +77,7 @@ pub(super) fn fn_def(p: &mut Parser) {
     assert!(p.at(T![fn]));
     p.bump(T![fn]);
 
-    name_recovery(p, DECLARATION_RECOVERY_SET.union(token_set![L_PAREN]));
+    name_recovery(p, DECLARATION_RECOVERY_SET.union(TokenSet::new(&[T![')']])));
 
     if p.at(T!['(']) {
         params::param_list(p);
diff --git a/crates/mun_syntax/src/parsing/grammar/expressions.rs b/crates/mun_syntax/src/parsing/grammar/expressions.rs
index 4f0ee6d19..38a38f5be 100644
--- a/crates/mun_syntax/src/parsing/grammar/expressions.rs
+++ b/crates/mun_syntax/src/parsing/grammar/expressions.rs
@@ -2,11 +2,11 @@ use super::*;
 use crate::parsing::grammar::paths::PATH_FIRST;
 
 pub(crate) const LITERAL_FIRST: TokenSet =
-    token_set![TRUE_KW, FALSE_KW, INT_NUMBER, FLOAT_NUMBER, STRING];
+    TokenSet::new(&[T![true], T![false], INT_NUMBER, FLOAT_NUMBER, STRING]);
 
-const EXPR_RECOVERY_SET: TokenSet = token_set![LET_KW];
+const EXPR_RECOVERY_SET: TokenSet = TokenSet::new(&[T![let]]);
 
-const ATOM_EXPR_FIRST: TokenSet = LITERAL_FIRST.union(PATH_FIRST).union(token_set![
+const ATOM_EXPR_FIRST: TokenSet = LITERAL_FIRST.union(PATH_FIRST).union(TokenSet::new(&[
     IDENT,
     T!['('],
     T!['{'],
@@ -15,9 +15,9 @@ const ATOM_EXPR_FIRST: TokenSet = LITERAL_FIRST.union(PATH_FIRST).union(token_se
     T![return],
     T![break],
     T![while],
-]);
+]));
 
-const LHS_FIRST: TokenSet = ATOM_EXPR_FIRST.union(token_set![EXCLAMATION, MINUS]);
+const LHS_FIRST: TokenSet = ATOM_EXPR_FIRST.union(TokenSet::new(&[T![!], T![-]]));
 
 const EXPR_FIRST: TokenSet = LHS_FIRST;
 
diff --git a/crates/mun_syntax/src/parsing/grammar/paths.rs b/crates/mun_syntax/src/parsing/grammar/paths.rs
index cc22c9691..a7c1fe49b 100644
--- a/crates/mun_syntax/src/parsing/grammar/paths.rs
+++ b/crates/mun_syntax/src/parsing/grammar/paths.rs
@@ -1,9 +1,10 @@
 use super::*;
 
-pub(super) const PATH_FIRST: TokenSet = token_set![IDENT, SELF_KW, SUPER_KW, COLONCOLON];
+pub(super) const PATH_FIRST: TokenSet =
+    TokenSet::new(&[IDENT, T![super], T![self], T![package], T![::]]);
 
 pub(super) fn is_path_start(p: &Parser) -> bool {
-    matches!(p.current(), IDENT | T![::])
+    matches!(p.current(), IDENT | T![self] | T![super] | T![package])
 }
 
 pub(super) fn type_path(p: &mut Parser) {
@@ -21,14 +22,14 @@ enum Mode {
 
 fn path(p: &mut Parser, mode: Mode) {
     let path = p.start();
-    path_segment(p, mode, true);
+    path_segment(p, mode);
     let mut qualifier = path.complete(p, PATH);
     loop {
         let import_tree = matches!(p.nth(1), T![*] | T!['{']);
         if p.at(T![::]) && !import_tree {
             let path = qualifier.precede(p);
             p.bump(T![::]);
-            path_segment(p, mode, false);
+            path_segment(p, mode);
             let path = path.complete(p, PATH);
             qualifier = path;
         } else {
@@ -37,16 +38,13 @@ fn path(p: &mut Parser, mode: Mode) {
     }
 }
 
-fn path_segment(p: &mut Parser, _mode: Mode, first: bool) {
+fn path_segment(p: &mut Parser, _mode: Mode) {
     let m = p.start();
-    if first {
-        p.eat(T![::]);
-    }
     match p.current() {
         IDENT => {
             name_ref(p);
         }
-        T![self] | T![super] => p.bump_any(),
+        T![self] | T![super] | T![package] => p.bump_any(),
         _ => p.error_recover(
             "expected identifier",
             declarations::DECLARATION_RECOVERY_SET,
diff --git a/crates/mun_syntax/src/parsing/grammar/patterns.rs b/crates/mun_syntax/src/parsing/grammar/patterns.rs
index 97be71f25..d7850bb1e 100644
--- a/crates/mun_syntax/src/parsing/grammar/patterns.rs
+++ b/crates/mun_syntax/src/parsing/grammar/patterns.rs
@@ -2,7 +2,7 @@ use super::*;
 
 pub(super) const PATTERN_FIRST: TokenSet = expressions::LITERAL_FIRST
     .union(paths::PATH_FIRST)
-    .union(token_set![MINUS, UNDERSCORE]);
+    .union(TokenSet::new(&[T![-], T![_]]));
 
 pub(super) fn pattern(p: &mut Parser) {
     pattern_r(p, PATTERN_FIRST);
diff --git a/crates/mun_syntax/src/parsing/grammar/types.rs b/crates/mun_syntax/src/parsing/grammar/types.rs
index 410e27085..c66fcd1a0 100644
--- a/crates/mun_syntax/src/parsing/grammar/types.rs
+++ b/crates/mun_syntax/src/parsing/grammar/types.rs
@@ -1,8 +1,8 @@
 use super::*;
 
-pub(super) const TYPE_FIRST: TokenSet = paths::PATH_FIRST.union(token_set![T![never],]);
+pub(super) const TYPE_FIRST: TokenSet = paths::PATH_FIRST.union(TokenSet::new(&[T![never]]));
 
-pub(super) const TYPE_RECOVERY_SET: TokenSet = token_set![R_PAREN, COMMA];
+pub(super) const TYPE_RECOVERY_SET: TokenSet = TokenSet::new(&[T!['('], T![,], T![pub]]);
 
 pub(super) fn ascription(p: &mut Parser) {
     p.expect(T![:]);
diff --git a/crates/mun_syntax/src/parsing/token_set.rs b/crates/mun_syntax/src/parsing/token_set.rs
index 314064591..ae8a7a102 100644
--- a/crates/mun_syntax/src/parsing/token_set.rs
+++ b/crates/mun_syntax/src/parsing/token_set.rs
@@ -9,8 +9,14 @@ impl TokenSet {
         TokenSet(0)
     }
 
-    pub(crate) const fn singleton(kind: SyntaxKind) -> TokenSet {
-        TokenSet(mask(kind))
+    pub(crate) const fn new(kinds: &[SyntaxKind]) -> TokenSet {
+        let mut res = 0u128;
+        let mut i = 0;
+        while i < kinds.len() {
+            res |= mask(kinds[i]);
+            i += 1
+        }
+        TokenSet(res)
     }
 
     pub(crate) const fn union(self, other: TokenSet) -> TokenSet {
@@ -26,16 +32,10 @@ const fn mask(kind: SyntaxKind) -> u128 {
     1u128 << (kind as usize)
 }
 
-#[macro_export]
-macro_rules! token_set {
-    ($($t:expr),*) => { TokenSet::empty()$(.union(TokenSet::singleton($t)))* };
-    ($($t:expr),* ,) => { token_set!($($t),*) };
-}
-
 #[test]
 fn token_set_works_for_tokens() {
     use crate::SyntaxKind::*;
-    let ts = token_set![EOF, EQ];
+    let ts = TokenSet::new(&[EOF, EQ]);
     assert!(ts.contains(EOF));
     assert!(ts.contains(T![=]));
     assert!(!ts.contains(PLUS));


Test patch:
diff --git a/crates/mun/tests/integration.rs b/crates/mun/tests/integration.rs
index 9885b5104..60ff9ba7e 100644
--- a/crates/mun/tests/integration.rs
+++ b/crates/mun/tests/integration.rs
@@ -48,7 +48,7 @@ fn build_and_run(project: impl AsRef<Path>) {
     ];
     assert_eq!(run_with_args(args).unwrap(), mun::ExitStatus::Success);
 
-    let library_path = project.as_ref().join("target/main.munlib");
+    let library_path = project.as_ref().join("target/mod.munlib");
     assert!(library_path.is_file());
 
     let runtime = RuntimeBuilder::new(&library_path).spawn().unwrap();
diff --git a/crates/mun_codegen/src/test.rs b/crates/mun_codegen/src/test.rs
index 4a21dfd74..631a847aa 100644
--- a/crates/mun_codegen/src/test.rs
+++ b/crates/mun_codegen/src/test.rs
@@ -809,12 +809,14 @@ fn incremental_compilation() {
     db.set_optimization_level(OptimizationLevel::Default);
     db.set_target(Target::host_target().unwrap());
 
+    let module = Module::from_file(&db, file_id).unwrap();
+
     {
         let events = db.log_executed(|| {
-            db.assembly(file_id);
+            db.assembly(module);
         });
         assert!(
-            format!("{:?}", events).contains("module_data"),
+            format!("{:?}", events).contains("package_defs"),
             "{:#?}",
             events
         );
@@ -829,11 +831,11 @@ fn incremental_compilation() {
 
     {
         let events = db.log_executed(|| {
-            db.assembly(file_id);
+            db.assembly(module);
         });
         println!("events: {:?}", events);
         assert!(
-            !format!("{:?}", events).contains("module_data"),
+            !format!("{:?}", events).contains("package_defs"),
             "{:#?}",
             events
         );
@@ -971,7 +973,8 @@ fn test_snapshot_with_optimization(text: &str, opt: OptimizationLevel) {
             diag.message()
         ));
     });
-    Module::from(file_id).diagnostics(&db, &mut sink);
+    let module = Module::from_file(&db, file_id).unwrap();
+    module.diagnostics(&db, &mut sink);
     drop(sink);
     let messages = messages.into_inner();
 
@@ -987,8 +990,8 @@ fn test_snapshot_with_optimization(text: &str, opt: OptimizationLevel) {
     let (group_ir_value, file_ir_value) = if !messages.is_empty() {
         ("".to_owned(), messages.join("\n"))
     } else {
-        let group_ir = gen_file_group_ir(&code_gen, file_id);
-        let file_ir = gen_file_ir(&code_gen, &group_ir, file_id);
+        let group_ir = gen_file_group_ir(&code_gen, module);
+        let file_ir = gen_file_ir(&code_gen, &group_ir, module);
 
         (
             format!("{}", group_ir.llvm_module.print_to_string().to_string(),),
@@ -999,7 +1002,7 @@ fn test_snapshot_with_optimization(text: &str, opt: OptimizationLevel) {
     // To ensure that we test symbol generation
     if messages.is_empty() {
         let module_builder =
-            ModuleBuilder::new(&code_gen, file_id).expect("Failed to initialize module builder");
+            ModuleBuilder::new(&code_gen, module).expect("Failed to initialize module builder");
         let _obj_file = module_builder.build().expect("Failed to build object file");
     }
 
diff --git a/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__expected_function.snap b/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__expected_function.snap
new file mode 100644
index 000000000..3e71965d7
--- /dev/null
+++ b/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__expected_function.snap
@@ -0,0 +1,10 @@
+---
+source: crates/mun_compiler/src/diagnostics.rs
+expression: "compilation_errors(\"\\n\\nfn foo() { let a = 3; a(); }\")"
+---
+error: expected function, found `{integer}`
+ --> main.mun:3:23
+  |
+3 | fn foo() { let a = 3; a(); }
+  |                       ^ not a function
+  |
diff --git a/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__expected_function_error.snap b/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__expected_function_error.snap
index c3c46346e..95e8d17ed 100644
--- a/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__expected_function_error.snap
+++ b/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__expected_function_error.snap
@@ -7,19 +7,9 @@ error: cannot find value `Foo` in this scope
   |
 4 | let a = Foo();
   |         ^^^ not found in this scope
-  |error: expected function, found `{unknown}`
- --> main.mun:4:9
-  |
-4 | let a = Foo();
-  |         ^^^ not a function
   |error: cannot find value `Bar` in this scope
  --> main.mun:6:9
   |
 6 | let b = Bar();
   |         ^^^ not found in this scope
-  |error: expected function, found `{unknown}`
- --> main.mun:6:9
-  |
-6 | let b = Bar();
-  |         ^^^ not a function
   |
diff --git a/crates/mun_hir/src/expr/validator/tests.rs b/crates/mun_hir/src/expr/validator/tests.rs
index 245cff823..ddadae891 100644
--- a/crates/mun_hir/src/expr/validator/tests.rs
+++ b/crates/mun_hir/src/expr/validator/tests.rs
@@ -1,10 +1,9 @@
 use crate::{
-    db::DefDatabase,
     diagnostics::DiagnosticSink,
     expr::validator::{ExprValidator, TypeAliasValidator},
     fixture::WithFixture,
     mock::MockDatabase,
-    ModuleDef,
+    ModuleDef, Package,
 };
 use std::fmt::Write;
 
@@ -80,7 +79,7 @@ fn test_free_type_alias_without_type_ref() {
 }
 
 fn diagnostics(content: &str) -> String {
-    let (db, file_id) = MockDatabase::with_single_file(content);
+    let (db, _file_id) = MockDatabase::with_single_file(content);
 
     let mut diags = String::new();
 
@@ -88,13 +87,17 @@ fn diagnostics(content: &str) -> String {
         write!(diags, "{}: {}\n", diag.highlight_range(), diag.message()).unwrap();
     });
 
-    for item in db.module_data(file_id).definitions() {
+    for item in Package::all(&db)
+        .iter()
+        .flat_map(|pkg| pkg.modules(&db))
+        .flat_map(|module| module.declarations(&db))
+    {
         match item {
             ModuleDef::Function(item) => {
-                ExprValidator::new(*item, &db).validate_body(&mut diag_sink);
+                ExprValidator::new(item, &db).validate_body(&mut diag_sink);
             }
             ModuleDef::TypeAlias(item) => {
-                TypeAliasValidator::new(*item, &db).validate_target_type_existence(&mut diag_sink);
+                TypeAliasValidator::new(item, &db).validate_target_type_existence(&mut diag_sink);
             }
             _ => {}
         }
diff --git a/crates/mun_hir/src/item_tree/snapshots/mun_hir__item_tree__tests__top_level_items.snap b/crates/mun_hir/src/item_tree/snapshots/mun_hir__item_tree__tests__top_level_items.snap
index c53a7880c..6cf819d3c 100644
--- a/crates/mun_hir/src/item_tree/snapshots/mun_hir__item_tree__tests__top_level_items.snap
+++ b/crates/mun_hir/src/item_tree/snapshots/mun_hir__item_tree__tests__top_level_items.snap
@@ -1,25 +1,26 @@
 ---
 source: crates/mun_hir/src/item_tree/tests.rs
-expression: "print_item_tree(r#\"\n    fn foo(a:i32, b:u8, c:String) -> i32 {}\n    fn bar(a:i32, b:u8, c:String) ->  {}\n    fn baz(a:i32, b:, c:String) ->  {}\n    extern fn eval(a:String) -> bool;\n\n    struct Foo {\n        a: i32,\n        b: u8,\n        c: String,\n    }\n    struct Foo2 {\n        a: i32,\n        b: ,\n        c: String,\n    }\n    struct Bar (i32, u32, String)\n    struct Baz;\n\n    type FooBar = Foo;\n    type FooBar = package::Foo;\n    \"#).unwrap()"
+expression: "print_item_tree(r#\"\n    fn foo(a:i32, b:u8, c:String) -> i32 {}\n    pub fn bar(a:i32, b:u8, c:String) ->  {}\n    pub(super) fn bar(a:i32, b:u8, c:String) ->  {}\n    pub(package) fn baz(a:i32, b:, c:String) ->  {}\n    extern fn eval(a:String) -> bool;\n\n    struct Foo {\n        a: i32,\n        b: u8,\n        c: String,\n    }\n    struct Foo2 {\n        a: i32,\n        b: ,\n        c: String,\n    }\n    struct Bar (i32, u32, String)\n    struct Baz;\n\n    type FooBar = Foo;\n    type FooBar = package::Foo;\n    \"#).unwrap()"
 ---
 top-level items:
-Function { name: Name(Text("foo")), is_extern: false, params: [Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("i32")) }] }), Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("u8")) }] }), Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("String")) }] })], ret_type: Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("i32")) }] }), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(0), _ty: PhantomData } }
-Function { name: Name(Text("bar")), is_extern: false, params: [Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("i32")) }] }), Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("u8")) }] }), Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("String")) }] })], ret_type: Empty, ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(1), _ty: PhantomData } }
-Function { name: Name(Text("baz")), is_extern: false, params: [Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("i32")) }] }), Error, Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("String")) }] })], ret_type: Empty, ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(2), _ty: PhantomData } }
-Function { name: Name(Text("eval")), is_extern: true, params: [Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("String")) }] })], ret_type: Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("bool")) }] }), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(3), _ty: PhantomData } }
-Struct { name: Name(Text("Foo")), fields: Record(IdRange::<mun_hir::item_tree::Field>(0..3)), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(4), _ty: PhantomData }, kind: Record }
-> Field { name: Name(Text("a")), type_ref: Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("i32")) }] }) }
-> Field { name: Name(Text("b")), type_ref: Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("u8")) }] }) }
-> Field { name: Name(Text("c")), type_ref: Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("String")) }] }) }
-Struct { name: Name(Text("Foo2")), fields: Record(IdRange::<mun_hir::item_tree::Field>(3..6)), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(5), _ty: PhantomData }, kind: Record }
-> Field { name: Name(Text("a")), type_ref: Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("i32")) }] }) }
+Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), is_extern: false, params: [Path(Path { kind: Plain, segments: [Name(Text("i32"))] }), Path(Path { kind: Plain, segments: [Name(Text("u8"))] }), Path(Path { kind: Plain, segments: [Name(Text("String"))] })], ret_type: Path(Path { kind: Plain, segments: [Name(Text("i32"))] }), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(0), _ty: PhantomData } }
+Function { name: Name(Text("bar")), visibility: RawVisibilityId("pub"), is_extern: false, params: [Path(Path { kind: Plain, segments: [Name(Text("i32"))] }), Path(Path { kind: Plain, segments: [Name(Text("u8"))] }), Path(Path { kind: Plain, segments: [Name(Text("String"))] })], ret_type: Empty, ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(1), _ty: PhantomData } }
+Function { name: Name(Text("bar")), visibility: RawVisibilityId("pub(super)"), is_extern: false, params: [Path(Path { kind: Plain, segments: [Name(Text("i32"))] }), Path(Path { kind: Plain, segments: [Name(Text("u8"))] }), Path(Path { kind: Plain, segments: [Name(Text("String"))] })], ret_type: Empty, ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(2), _ty: PhantomData } }
+Function { name: Name(Text("baz")), visibility: RawVisibilityId("pub(package)"), is_extern: false, params: [Path(Path { kind: Plain, segments: [Name(Text("i32"))] }), Error, Path(Path { kind: Plain, segments: [Name(Text("String"))] })], ret_type: Empty, ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(3), _ty: PhantomData } }
+Function { name: Name(Text("eval")), visibility: RawVisibilityId("pub(self)"), is_extern: true, params: [Path(Path { kind: Plain, segments: [Name(Text("String"))] })], ret_type: Path(Path { kind: Plain, segments: [Name(Text("bool"))] }), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(4), _ty: PhantomData } }
+Struct { name: Name(Text("Foo")), visibility: RawVisibilityId("pub(self)"), fields: Record(IdRange::<mun_hir::item_tree::Field>(0..3)), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(5), _ty: PhantomData }, kind: Record }
+> Field { name: Name(Text("a")), type_ref: Path(Path { kind: Plain, segments: [Name(Text("i32"))] }) }
+> Field { name: Name(Text("b")), type_ref: Path(Path { kind: Plain, segments: [Name(Text("u8"))] }) }
+> Field { name: Name(Text("c")), type_ref: Path(Path { kind: Plain, segments: [Name(Text("String"))] }) }
+Struct { name: Name(Text("Foo2")), visibility: RawVisibilityId("pub(self)"), fields: Record(IdRange::<mun_hir::item_tree::Field>(3..6)), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(6), _ty: PhantomData }, kind: Record }
+> Field { name: Name(Text("a")), type_ref: Path(Path { kind: Plain, segments: [Name(Text("i32"))] }) }
 > Field { name: Name(Text("b")), type_ref: Error }
-> Field { name: Name(Text("c")), type_ref: Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("String")) }] }) }
-Struct { name: Name(Text("Bar")), fields: Tuple(IdRange::<mun_hir::item_tree::Field>(6..9)), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(6), _ty: PhantomData }, kind: Tuple }
-> Field { name: Name(TupleField(0)), type_ref: Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("i32")) }] }) }
-> Field { name: Name(TupleField(1)), type_ref: Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("u32")) }] }) }
-> Field { name: Name(TupleField(2)), type_ref: Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("String")) }] }) }
-Struct { name: Name(Text("Baz")), fields: Unit, ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(7), _ty: PhantomData }, kind: Unit }
-TypeAlias { name: Name(Text("FooBar")), type_ref: Some(Path(Path { kind: Plain, segments: [PathSegment { name: Name(Text("Foo")) }] })), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(8), _ty: PhantomData } }
-TypeAlias { name: Name(Text("FooBar")), type_ref: None, ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(9), _ty: PhantomData } }
+> Field { name: Name(Text("c")), type_ref: Path(Path { kind: Plain, segments: [Name(Text("String"))] }) }
+Struct { name: Name(Text("Bar")), visibility: RawVisibilityId("pub(self)"), fields: Tuple(IdRange::<mun_hir::item_tree::Field>(6..9)), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(7), _ty: PhantomData }, kind: Tuple }
+> Field { name: Name(TupleField(0)), type_ref: Path(Path { kind: Plain, segments: [Name(Text("i32"))] }) }
+> Field { name: Name(TupleField(1)), type_ref: Path(Path { kind: Plain, segments: [Name(Text("u32"))] }) }
+> Field { name: Name(TupleField(2)), type_ref: Path(Path { kind: Plain, segments: [Name(Text("String"))] }) }
+Struct { name: Name(Text("Baz")), visibility: RawVisibilityId("pub(self)"), fields: Unit, ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(8), _ty: PhantomData }, kind: Unit }
+TypeAlias { name: Name(Text("FooBar")), visibility: RawVisibilityId("pub(self)"), type_ref: Some(Path(Path { kind: Plain, segments: [Name(Text("Foo"))] })), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(9), _ty: PhantomData } }
+TypeAlias { name: Name(Text("FooBar")), visibility: RawVisibilityId("pub(self)"), type_ref: Some(Path(Path { kind: Package, segments: [Name(Text("Foo"))] })), ast_id: FileAstId { raw: Idx::<SyntaxNodePtr>(10), _ty: PhantomData } }
 
diff --git a/crates/mun_hir/src/item_tree/tests.rs b/crates/mun_hir/src/item_tree/tests.rs
index f6ec62ace..1b72d48d6 100644
--- a/crates/mun_hir/src/item_tree/tests.rs
+++ b/crates/mun_hir/src/item_tree/tests.rs
@@ -58,8 +58,9 @@ fn top_level_items() {
     insta::assert_snapshot!(print_item_tree(
         r#"
     fn foo(a:i32, b:u8, c:String) -> i32 {}
-    fn bar(a:i32, b:u8, c:String) ->  {}
-    fn baz(a:i32, b:, c:String) ->  {}
+    pub fn bar(a:i32, b:u8, c:String) ->  {}
+    pub(super) fn bar(a:i32, b:u8, c:String) ->  {}
+    pub(package) fn baz(a:i32, b:, c:String) ->  {}
     extern fn eval(a:String) -> bool;
 
     struct Foo {
diff --git a/crates/mun_hir/src/snapshots/mun_hir__module_tree__test__module_tree.snap b/crates/mun_hir/src/snapshots/mun_hir__module_tree__test__module_tree.snap
new file mode 100644
index 000000000..235cdffc6
--- /dev/null
+++ b/crates/mun_hir/src/snapshots/mun_hir__module_tree__test__module_tree.snap
@@ -0,0 +1,116 @@
+---
+source: crates/mun_hir/src/module_tree.rs
+expression: module_tree
+---
+ModuleTree {
+    root: Idx::<ModuleData>(0),
+    modules: Arena {
+        len: 6,
+        data: [
+            ModuleData {
+                parent: None,
+                children: {
+                    Name(
+                        Text(
+                            "baz",
+                        ),
+                    ): Idx::<ModuleData>(1),
+                    Name(
+                        Text(
+                            "foo",
+                        ),
+                    ): Idx::<ModuleData>(3),
+                },
+                file: Some(
+                    FileId(
+                        0,
+                    ),
+                ),
+            },
+            ModuleData {
+                parent: Some(
+                    Idx::<ModuleData>(0),
+                ),
+                children: {
+                    Name(
+                        Text(
+                            "foo",
+                        ),
+                    ): Idx::<ModuleData>(2),
+                },
+                file: None,
+            },
+            ModuleData {
+                parent: Some(
+                    Idx::<ModuleData>(1),
+                ),
+                children: {},
+                file: Some(
+                    FileId(
+                        5,
+                    ),
+                ),
+            },
+            ModuleData {
+                parent: Some(
+                    Idx::<ModuleData>(0),
+                ),
+                children: {
+                    Name(
+                        Text(
+                            "bar",
+                        ),
+                    ): Idx::<ModuleData>(4),
+                    Name(
+                        Text(
+                            "baz",
+                        ),
+                    ): Idx::<ModuleData>(5),
+                },
+                file: Some(
+                    FileId(
+                        1,
+                    ),
+                ),
+            },
+            ModuleData {
+                parent: Some(
+                    Idx::<ModuleData>(3),
+                ),
+                children: {},
+                file: Some(
+                    FileId(
+                        3,
+                    ),
+                ),
+            },
+            ModuleData {
+                parent: Some(
+                    Idx::<ModuleData>(3),
+                ),
+                children: {},
+                file: Some(
+                    FileId(
+                        4,
+                    ),
+                ),
+            },
+        ],
+    },
+    package: PackageId(
+        0,
+    ),
+    diagnostics: [
+        DuplicateModuleFile(
+            Idx::<ModuleData>(3),
+            [
+                FileId(
+                    2,
+                ),
+                FileId(
+                    1,
+                ),
+            ],
+        ),
+    ],
+}
diff --git a/crates/mun_hir/src/tests.rs b/crates/mun_hir/src/tests.rs
index ec23813ea..6260c9c09 100644
--- a/crates/mun_hir/src/tests.rs
+++ b/crates/mun_hir/src/tests.rs
@@ -2,13 +2,14 @@ use crate::{
     db::{DefDatabase, SourceDatabase},
     fixture::WithFixture,
     mock::MockDatabase,
+    PackageId,
 };
 use std::sync::Arc;
 
 /// This function tests that the ModuleData of a module does not change if the contents of a function
 /// is changed.
 #[test]
-fn check_module_data_does_not_change() {
+fn check_package_defs_does_not_change() {
     let (mut db, file_id) = MockDatabase::with_single_file(
         r#"
     fn foo()->i32 {
@@ -19,10 +20,10 @@ fn check_module_data_does_not_change() {
 
     {
         let events = db.log_executed(|| {
-            db.module_data(file_id);
+            db.package_defs(PackageId(0));
         });
         assert!(
-            format!("{:?}", events).contains("module_data"),
+            format!("{:?}", events).contains("package_defs"),
             "{:#?}",
             events
         )
@@ -40,10 +41,10 @@ fn check_module_data_does_not_change() {
     );
     {
         let events = db.log_executed(|| {
-            db.module_data(file_id);
+            db.package_defs(PackageId(0));
         });
         assert!(
-            !format!("{:?}", events).contains("module_data"),
+            !format!("{:?}", events).contains("package_defs"),
             "{:#?}",
             events
         )
diff --git a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__private_access.snap b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__private_access.snap
new file mode 100644
index 000000000..23d88e3d4
--- /dev/null
+++ b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__private_access.snap
@@ -0,0 +1,107 @@
+---
+source: crates/mun_hir/src/ty/tests.rs
+expression: "//- /foo.mun\nstruct Foo {};\nstruct Bar(i64);\nstruct Baz;\nfn foo() {}\ntype FooBar = Foo;\n\npub struct PubFoo {};\npub struct PubBar(i64);\npub struct PubBaz;\npub fn pub_foo() {}\npub type PubFooBar = PubFoo;\n\npub(super) struct PubSupFoo {};\npub(super) struct PubSupBar(i64);\npub(super) struct PubSupBaz;\npub(super) fn pub_sup_foo() {}\npub(super) type PubSupFooBar = PubSupFoo;\n\npub(package) struct PubPackageFoo {};\npub(package) struct PubPackageBar(i64);\npub(package) struct PubPackageBaz;\npub(package) fn pub_package_foo() {}\npub(package) type PubPackageFooBar = PubPackageFoo;\n\n//- /bar.mun\nfn main() {\n    let a = package::foo::Foo {}; // private access\n    let a = package::foo::Bar(3); // private access\n    let a = package::foo::Baz; // private access\n    let a = package::foo::FooBar{}}; // private access\n\n    let a = super::foo::Foo {}; // private access\n    let a = super::foo::Bar(3); // private access\n    let a = super::foo::Baz; // private access\n    let a = super::foo::FooBar{}; // private access\n\n    package::foo::foo(); // private access\n    super::foo::foo(); // private access\n\n    let a = package::foo::PubFoo {};\n    let a = package::foo::PubBar(3);\n    let a = package::foo::PubBaz;\n    let a = package::foo::PubFooBar{};\n\n    let a = super::foo::PubFoo {};\n    let a = super::foo::PubBar(3);\n    let a = super::foo::PubBaz;\n    let a = super::foo::PubFooBar{};\n\n    package::foo::pub_foo();\n    super::foo::pub_foo();\n\n    let a = package::foo::PubSupFoo {};\n    let a = package::foo::PubSupBar(3);\n    let a = package::foo::PubSupBaz;\n    let a = package::foo::PubSupFooBar{};\n\n    let a = super::foo::PubSupFoo {};\n    let a = super::foo::PubSupBar(3);\n    let a = super::foo::PubSupBaz;\n    let a = super::foo::PubSupFooBar{};\n\n    package::foo::pub_sup_foo();\n    super::foo::pub_sup_foo();\n\n    let a = package::foo::PubPackageFoo {};\n    let a = package::foo::PubPackageBar(3);\n    let a = package::foo::PubPackageBaz;\n    let a = package::foo::PubPackageFooBar{};\n\n    let a = super::foo::PubPackageFoo {};\n    let a = super::foo::PubPackageBar(3);\n    let a = super::foo::PubPackageBaz;\n    let a = super::foo::PubPackageFooBar{};\n\n    package::foo::pub_package_foo();\n    super::foo::pub_package_foo();\n}\n\n//- /foo/baz.mun\nfn main() {\n    let a = package::foo::Foo {};\n    let a = package::foo::Bar(3);\n    let a = package::foo::Baz;\n    let a = package::foo::FooBar{};\n\n    let a = super::Foo {};\n    let a = super::Bar(3);\n    let a = super::Baz;\n    let a = super::FooBar{};\n\n    package::foo::foo();\n    super::foo();\n}\n\n//- /mod.mun\nfn main() {\n    let a = package::foo::Foo {}; // private access\n    let a = package::foo::Bar(3); // private access\n    let a = package::foo::Baz; // private access\n    let a = package::foo::FooBar{}; // private access\n\n    let a = foo::Foo {}; // private access\n    let a = foo::Bar(3); // private access\n    let a = foo::Baz; // private access\n    let a = foo::FooBar{}; // private access\n\n    package::foo::foo(); // private access\n    foo::foo(); // private access\n\n    let a = package::foo::PubSupFoo {};\n    let a = package::foo::PubSupBar(3);\n    let a = package::foo::PubSupBaz;\n    let a = package::foo::PubSupFooBar{};\n\n    let a = foo::PubSupFoo {};\n    let a = foo::PubSupBar(3);\n    let a = foo::PubSupBaz;\n    let a = foo::PubSupFooBar{};\n\n    package::foo::pub_sup_foo();\n    foo::pub_sup_foo();\n}"
+---
+[24; 41): access of private type
+[76; 93): access of private type
+[128; 145): access of private type
+[177; 197): access of private type
+[232; 240): access of private type
+[275; 283): access of private type
+[318; 326): access of private type
+[358; 369): access of private type
+[396; 413): access of private type
+[439; 447): access of private type
+[24; 41): access of private type
+[76; 93): access of private type
+[128; 145): access of private type
+[177; 197): access of private type
+[10; 812) '{     ...o(); }': nothing
+[20; 21) 'a': Foo
+[24; 44) 'packag...Foo {}': Foo
+[72; 73) 'a': Bar
+[76; 93) 'packag...o::Bar': ctor Bar(i64) -> Bar
+[76; 96) 'packag...Bar(3)': Bar
+[94; 95) '3': i64
+[124; 125) 'a': Baz
+[128; 145) 'packag...o::Baz': Baz
+[173; 174) 'a': Foo
+[177; 199) 'packag...oBar{}': Foo
+[228; 229) 'a': Foo
+[232; 243) 'foo::Foo {}': Foo
+[271; 272) 'a': Bar
+[275; 283) 'foo::Bar': ctor Bar(i64) -> Bar
+[275; 286) 'foo::Bar(3)': Bar
+[284; 285) '3': i64
+[314; 315) 'a': Baz
+[318; 326) 'foo::Baz': Baz
+[354; 355) 'a': Foo
+[358; 371) 'foo::FooBar{}': Foo
+[396; 413) 'packag...o::foo': function foo() -> nothing
+[396; 415) 'packag...:foo()': nothing
+[439; 447) 'foo::foo': function foo() -> nothing
+[439; 449) 'foo::foo()': nothing
+[478; 479) 'a': PubSupFoo
+[482; 508) 'packag...Foo {}': PubSupFoo
+[518; 519) 'a': PubSupBar
+[522; 545) 'packag...SupBar': ctor PubSupBar(i64) -> PubSupBar
+[522; 548) 'packag...Bar(3)': PubSupBar
+[546; 547) '3': i64
+[558; 559) 'a': PubSupBaz
+[562; 585) 'packag...SupBaz': PubSupBaz
+[595; 596) 'a': PubSupFoo
+[599; 627) 'packag...oBar{}': PubSupFoo
+[638; 639) 'a': PubSupFoo
+[642; 659) 'foo::P...Foo {}': PubSupFoo
+[669; 670) 'a': PubSupBar
+[673; 687) 'foo::PubSupBar': ctor PubSupBar(i64) -> PubSupBar
+[673; 690) 'foo::P...Bar(3)': PubSupBar
+[688; 689) '3': i64
+[700; 701) 'a': PubSupBaz
+[704; 718) 'foo::PubSupBaz': PubSupBaz
+[728; 729) 'a': PubSupFoo
+[732; 751) 'foo::P...oBar{}': PubSupFoo
+[758; 783) 'packag...up_foo': function pub_sup_foo() -> nothing
+[758; 785) 'packag..._foo()': nothing
+[791; 807) 'foo::p...up_foo': function pub_sup_foo() -> nothing
+[791; 809) 'foo::p..._foo()': nothing
+[10; 200) '{     ...Bar{}}': nothing
+[20; 21) 'a': Foo
+[24; 44) 'packag...Foo {}': Foo
+[72; 73) 'a': Bar
+[76; 93) 'packag...o::Bar': ctor Bar(i64) -> Bar
+[76; 96) 'packag...Bar(3)': Bar
+[94; 95) '3': i64
+[124; 125) 'a': Baz
+[128; 145) 'packag...o::Baz': Baz
+[173; 174) 'a': Foo
+[177; 199) 'packag...oBar{}': Foo
+[53; 55) '{}': nothing
+[158; 160) '{}': nothing
+[314; 316) '{}': nothing
+[507; 509) '{}': nothing
+[10; 300) '{     ...o(); }': nothing
+[20; 21) 'a': Foo
+[24; 44) 'packag...Foo {}': Foo
+[54; 55) 'a': Bar
+[58; 75) 'packag...o::Bar': ctor Bar(i64) -> Bar
+[58; 78) 'packag...Bar(3)': Bar
+[76; 77) '3': i64
+[88; 89) 'a': Baz
+[92; 109) 'packag...o::Baz': Baz
+[119; 120) 'a': Foo
+[123; 145) 'packag...oBar{}': Foo
+[156; 157) 'a': Foo
+[160; 173) 'super::Foo {}': Foo
+[183; 184) 'a': Bar
+[187; 197) 'super::Bar': ctor Bar(i64) -> Bar
+[187; 200) 'super::Bar(3)': Bar
+[198; 199) '3': i64
+[210; 211) 'a': Baz
+[214; 224) 'super::Baz': Baz
+[234; 235) 'a': Foo
+[238; 253) 'super::FooBar{}': Foo
+[260; 277) 'packag...o::foo': function foo() -> nothing
+[260; 279) 'packag...:foo()': nothing
+[285; 295) 'super::foo': function foo() -> nothing
+[285; 297) 'super::foo()': nothing
diff --git a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__scoped_path.snap b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__scoped_path.snap
new file mode 100644
index 000000000..372f6f478
--- /dev/null
+++ b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__scoped_path.snap
@@ -0,0 +1,19 @@
+---
+source: crates/mun_hir/src/ty/tests.rs
+expression: "//- /mod.mun\nstruct Foo;\n\nfn main() -> self::Foo {\n    Foo\n}\n\nfn bar() -> Foo {\n    super::Foo  // undefined value\n}\n\nfn baz() -> Foo {\n    package::Foo\n}\n\n//- /foo.mun\nstruct Foo;\n\nfn bar() -> Foo {\n    super::Foo  // mismatched type\n}\n\nfn baz() -> package::Foo {\n    super::Foo\n}\n\nfn nested() -> self::Foo {\n    package::foo::Foo\n}"
+---
+[71; 81): undefined value
+[35; 45): mismatched type
+[29; 67): mismatched type
+[36; 47) '{     Foo }': Foo
+[42; 45) 'Foo': Foo
+[65; 103) '{     ...alue }': Foo
+[71; 81) 'super::Foo': {unknown}
+[121; 141) '{     ...:Foo }': Foo
+[127; 139) 'package::Foo': Foo
+[29; 67) '{     ...type }': Foo
+[35; 45) 'super::Foo': Foo
+[94; 112) '{     ...:Foo }': Foo
+[100; 110) 'super::Foo': Foo
+[139; 164) '{     ...:Foo }': Foo
+[145; 162) 'packag...o::Foo': Foo
diff --git a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__struct_lit.snap b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__struct_lit.snap
index c4a529eea..971c7dd45 100644
--- a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__struct_lit.snap
+++ b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__struct_lit.snap
@@ -5,7 +5,7 @@ expression: "struct Foo;\nstruct Bar {\n    a: f64,\n}\nstruct Baz(f64, i32);\n\
 [170; 175): mismatched struct literal kind. expected `unit struct`, found `record`
 [270; 275): mismatched struct literal kind. expected `unit struct`, found `tuple`
 [369; 372): mismatched struct literal kind. expected `record`, found `unit struct`
-[467; 472): mismatched struct literal kind. expected `record`, found `tuple`
+[467; 470): mismatched struct literal kind. expected `record`, found `tuple`
 [561; 566): missing record fields:
 - a
 
diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs
index 851f7d9b5..5d8dacf74 100644
--- a/crates/mun_hir/src/ty/tests.rs
+++ b/crates/mun_hir/src/ty/tests.rs
@@ -1,10 +1,179 @@
 use crate::fixture::WithFixture;
 use crate::{
-    db::DefDatabase, diagnostics::DiagnosticSink, expr::BodySourceMap, mock::MockDatabase,
-    HirDisplay, InferenceResult, ModuleDef,
+    diagnostics::DiagnosticSink, expr::BodySourceMap, mock::MockDatabase, HirDisplay,
+    InferenceResult, ModuleDef, Package,
 };
 use std::{fmt::Write, sync::Arc};
 
+#[test]
+fn private_access() {
+    infer_snapshot(
+        r#"
+    //- /foo.mun
+    struct Foo {};
+    struct Bar(i64);
+    struct Baz;
+    fn foo() {}
+    type FooBar = Foo;
+
+    pub struct PubFoo {};
+    pub struct PubBar(i64);
+    pub struct PubBaz;
+    pub fn pub_foo() {}
+    pub type PubFooBar = PubFoo;
+
+    pub(super) struct PubSupFoo {};
+    pub(super) struct PubSupBar(i64);
+    pub(super) struct PubSupBaz;
+    pub(super) fn pub_sup_foo() {}
+    pub(super) type PubSupFooBar = PubSupFoo;
+
+    pub(package) struct PubPackageFoo {};
+    pub(package) struct PubPackageBar(i64);
+    pub(package) struct PubPackageBaz;
+    pub(package) fn pub_package_foo() {}
+    pub(package) type PubPackageFooBar = PubPackageFoo;
+
+    //- /bar.mun
+    fn main() {
+        let a = package::foo::Foo {}; // private access
+        let a = package::foo::Bar(3); // private access
+        let a = package::foo::Baz; // private access
+        let a = package::foo::FooBar{}}; // private access
+
+        let a = super::foo::Foo {}; // private access
+        let a = super::foo::Bar(3); // private access
+        let a = super::foo::Baz; // private access
+        let a = super::foo::FooBar{}; // private access
+
+        package::foo::foo(); // private access
+        super::foo::foo(); // private access
+
+        let a = package::foo::PubFoo {};
+        let a = package::foo::PubBar(3);
+        let a = package::foo::PubBaz;
+        let a = package::foo::PubFooBar{};
+
+        let a = super::foo::PubFoo {};
+        let a = super::foo::PubBar(3);
+        let a = super::foo::PubBaz;
+        let a = super::foo::PubFooBar{};
+
+        package::foo::pub_foo();
+        super::foo::pub_foo();
+
+        let a = package::foo::PubSupFoo {};
+        let a = package::foo::PubSupBar(3);
+        let a = package::foo::PubSupBaz;
+        let a = package::foo::PubSupFooBar{};
+
+        let a = super::foo::PubSupFoo {};
+        let a = super::foo::PubSupBar(3);
+        let a = super::foo::PubSupBaz;
+        let a = super::foo::PubSupFooBar{};
+
+        package::foo::pub_sup_foo();
+        super::foo::pub_sup_foo();
+
+        let a = package::foo::PubPackageFoo {};
+        let a = package::foo::PubPackageBar(3);
+        let a = package::foo::PubPackageBaz;
+        let a = package::foo::PubPackageFooBar{};
+
+        let a = super::foo::PubPackageFoo {};
+        let a = super::foo::PubPackageBar(3);
+        let a = super::foo::PubPackageBaz;
+        let a = super::foo::PubPackageFooBar{};
+
+        package::foo::pub_package_foo();
+        super::foo::pub_package_foo();
+    }
+
+    //- /foo/baz.mun
+    fn main() {
+        let a = package::foo::Foo {};
+        let a = package::foo::Bar(3);
+        let a = package::foo::Baz;
+        let a = package::foo::FooBar{};
+
+        let a = super::Foo {};
+        let a = super::Bar(3);
+        let a = super::Baz;
+        let a = super::FooBar{};
+
+        package::foo::foo();
+        super::foo();
+    }
+
+    //- /mod.mun
+    fn main() {
+        let a = package::foo::Foo {}; // private access
+        let a = package::foo::Bar(3); // private access
+        let a = package::foo::Baz; // private access
+        let a = package::foo::FooBar{}; // private access
+
+        let a = foo::Foo {}; // private access
+        let a = foo::Bar(3); // private access
+        let a = foo::Baz; // private access
+        let a = foo::FooBar{}; // private access
+
+        package::foo::foo(); // private access
+        foo::foo(); // private access
+
+        let a = package::foo::PubSupFoo {};
+        let a = package::foo::PubSupBar(3);
+        let a = package::foo::PubSupBaz;
+        let a = package::foo::PubSupFooBar{};
+
+        let a = foo::PubSupFoo {};
+        let a = foo::PubSupBar(3);
+        let a = foo::PubSupBaz;
+        let a = foo::PubSupFooBar{};
+
+        package::foo::pub_sup_foo();
+        foo::pub_sup_foo();
+    }
+    "#,
+    )
+}
+
+#[test]
+fn scoped_path() {
+    infer_snapshot(
+        r"
+    //- /mod.mun
+    struct Foo;
+
+    fn main() -> self::Foo {
+        Foo
+    }
+
+    fn bar() -> Foo {
+        super::Foo  // undefined value
+    }
+
+    fn baz() -> Foo {
+        package::Foo
+    }
+
+    //- /foo.mun
+    struct Foo;
+
+    fn bar() -> Foo {
+        super::Foo  // mismatched type
+    }
+
+    fn baz() -> package::Foo {
+        super::Foo
+    }
+
+    fn nested() -> self::Foo {
+        package::foo::Foo
+    }
+    ",
+    )
+}
+
 #[test]
 fn comparison_not_implemented_for_struct() {
     infer_snapshot(
@@ -429,7 +598,7 @@ fn infer_snapshot(text: &str) {
 }
 
 fn infer(content: &str) -> String {
-    let (db, file_id) = MockDatabase::with_single_file(content);
+    let db = MockDatabase::with_files(content);
 
     let mut acc = String::new();
 
@@ -483,7 +652,11 @@ fn infer(content: &str) -> String {
         write!(diags, "{}: {}\n", diag.highlight_range(), diag.message()).unwrap();
     });
 
-    for item in db.module_data(file_id).definitions() {
+    for item in Package::all(&db)
+        .iter()
+        .flat_map(|pkg| pkg.modules(&db))
+        .flat_map(|module| module.declarations(&db))
+    {
         match item {
             ModuleDef::Function(fun) => {
                 let source_map = fun.body_source_map(&db);
diff --git a/crates/mun_runtime_capi/src/tests.rs b/crates/mun_runtime_capi/src/tests.rs
index e94ac3830..473a41e81 100644
--- a/crates/mun_runtime_capi/src/tests.rs
+++ b/crates/mun_runtime_capi/src/tests.rs
@@ -26,15 +26,15 @@ impl TestDriver {
             ..Config::default()
         };
         let input = PathOrInline::Inline {
-            rel_path: RelativePathBuf::from("main.mun"),
+            rel_path: RelativePathBuf::from("mod.mun"),
             contents: text.to_owned(),
         };
         let (mut driver, file_id) = Driver::with_file(config, input).unwrap();
         if driver.emit_diagnostics(&mut stderr()).unwrap() {
             panic!("compiler errors..")
         }
-        let out_path = driver.assembly_output_path(file_id);
-        driver.write_assembly(file_id, true).unwrap();
+        let out_path = driver.assembly_output_path_from_file(file_id);
+        driver.write_all_assemblies(false).unwrap();
         let runtime = make_runtime(&out_path);
         TestDriver {
             _temp_dir: temp_dir,
diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs
index c4c814f7c..9b51f092f 100644
--- a/crates/mun_syntax/src/tests/parser.rs
+++ b/crates/mun_syntax/src/tests/parser.rs
@@ -365,3 +365,14 @@ fn type_alias_def() {
     "#,
     )
 }
+#[test]
+fn function_return_path() {
+    snapshot_test(
+        r#"
+        fn main() -> self::Foo {}
+        fn main1() -> super::Foo {}
+        fn main2() -> package::Foo {}
+        fn main3() -> package::foo::Foo {}
+    "#,
+    );
+}
diff --git a/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__function_return_path.snap b/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__function_return_path.snap
new file mode 100644
index 000000000..70e13f730
--- /dev/null
+++ b/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__function_return_path.snap
@@ -0,0 +1,114 @@
+---
+source: crates/mun_syntax/src/tests/parser.rs
+expression: "fn main() -> self::Foo {}\n    fn main1() -> super::Foo {}\n    fn main2() -> package::Foo {}\n    fn main3() -> package::foo::Foo {}"
+---
+SOURCE_FILE@[0; 130)
+  FUNCTION_DEF@[0; 25)
+    FN_KW@[0; 2) "fn"
+    WHITESPACE@[2; 3) " "
+    NAME@[3; 7)
+      IDENT@[3; 7) "main"
+    PARAM_LIST@[7; 9)
+      L_PAREN@[7; 8) "("
+      R_PAREN@[8; 9) ")"
+    WHITESPACE@[9; 10) " "
+    RET_TYPE@[10; 22)
+      THIN_ARROW@[10; 12) "->"
+      WHITESPACE@[12; 13) " "
+      PATH_TYPE@[13; 22)
+        PATH@[13; 22)
+          PATH@[13; 17)
+            PATH_SEGMENT@[13; 17)
+              SELF_KW@[13; 17) "self"
+          COLONCOLON@[17; 19) "::"
+          PATH_SEGMENT@[19; 22)
+            NAME_REF@[19; 22)
+              IDENT@[19; 22) "Foo"
+    WHITESPACE@[22; 23) " "
+    BLOCK_EXPR@[23; 25)
+      L_CURLY@[23; 24) "{"
+      R_CURLY@[24; 25) "}"
+  FUNCTION_DEF@[25; 57)
+    WHITESPACE@[25; 30) "\n    "
+    FN_KW@[30; 32) "fn"
+    WHITESPACE@[32; 33) " "
+    NAME@[33; 38)
+      IDENT@[33; 38) "main1"
+    PARAM_LIST@[38; 40)
+      L_PAREN@[38; 39) "("
+      R_PAREN@[39; 40) ")"
+    WHITESPACE@[40; 41) " "
+    RET_TYPE@[41; 54)
+      THIN_ARROW@[41; 43) "->"
+      WHITESPACE@[43; 44) " "
+      PATH_TYPE@[44; 54)
+        PATH@[44; 54)
+          PATH@[44; 49)
+            PATH_SEGMENT@[44; 49)
+              SUPER_KW@[44; 49) "super"
+          COLONCOLON@[49; 51) "::"
+          PATH_SEGMENT@[51; 54)
+            NAME_REF@[51; 54)
+              IDENT@[51; 54) "Foo"
+    WHITESPACE@[54; 55) " "
+    BLOCK_EXPR@[55; 57)
+      L_CURLY@[55; 56) "{"
+      R_CURLY@[56; 57) "}"
+  FUNCTION_DEF@[57; 91)
+    WHITESPACE@[57; 62) "\n    "
+    FN_KW@[62; 64) "fn"
+    WHITESPACE@[64; 65) " "
+    NAME@[65; 70)
+      IDENT@[65; 70) "main2"
+    PARAM_LIST@[70; 72)
+      L_PAREN@[70; 71) "("
+      R_PAREN@[71; 72) ")"
+    WHITESPACE@[72; 73) " "
+    RET_TYPE@[73; 88)
+      THIN_ARROW@[73; 75) "->"
+      WHITESPACE@[75; 76) " "
+      PATH_TYPE@[76; 88)
+        PATH@[76; 88)
+          PATH@[76; 83)
+            PATH_SEGMENT@[76; 83)
+              PACKAGE_KW@[76; 83) "package"
+          COLONCOLON@[83; 85) "::"
+          PATH_SEGMENT@[85; 88)
+            NAME_REF@[85; 88)
+              IDENT@[85; 88) "Foo"
+    WHITESPACE@[88; 89) " "
+    BLOCK_EXPR@[89; 91)
+      L_CURLY@[89; 90) "{"
+      R_CURLY@[90; 91) "}"
+  FUNCTION_DEF@[91; 130)
+    WHITESPACE@[91; 96) "\n    "
+    FN_KW@[96; 98) "fn"
+    WHITESPACE@[98; 99) " "
+    NAME@[99; 104)
+      IDENT@[99; 104) "main3"
+    PARAM_LIST@[104; 106)
+      L_PAREN@[104; 105) "("
+      R_PAREN@[105; 106) ")"
+    WHITESPACE@[106; 107) " "
+    RET_TYPE@[107; 127)
+      THIN_ARROW@[107; 109) "->"
+      WHITESPACE@[109; 110) " "
+      PATH_TYPE@[110; 127)
+        PATH@[110; 127)
+          PATH@[110; 122)
+            PATH@[110; 117)
+              PATH_SEGMENT@[110; 117)
+                PACKAGE_KW@[110; 117) "package"
+            COLONCOLON@[117; 119) "::"
+            PATH_SEGMENT@[119; 122)
+              NAME_REF@[119; 122)
+                IDENT@[119; 122) "foo"
+          COLONCOLON@[122; 124) "::"
+          PATH_SEGMENT@[124; 127)
+            NAME_REF@[124; 127)
+              IDENT@[124; 127) "Foo"
+    WHITESPACE@[127; 128) " "
+    BLOCK_EXPR@[128; 130)
+      L_CURLY@[128; 129) "{"
+      R_CURLY@[129; 130) "}"
+
diff --git a/crates/mun_test/src/driver.rs b/crates/mun_test/src/driver.rs
index 877613515..714901d07 100644
--- a/crates/mun_test/src/driver.rs
+++ b/crates/mun_test/src/driver.rs
@@ -28,7 +28,7 @@ impl CompileTestDriver {
             ..Config::default()
         };
         let input = PathOrInline::Inline {
-            rel_path: RelativePathBuf::from("main.mun"),
+            rel_path: RelativePathBuf::from("mod.mun"),
             contents: text.to_owned(),
         };
         let (mut driver, file_id) = Driver::with_file(config, input).unwrap();
@@ -43,8 +43,10 @@ impl CompileTestDriver {
                     .expect("compiler errors are not UTF-8 formatted")
             )
         }
-        let out_path = driver.assembly_output_path(file_id);
-        driver.write_assembly(file_id, true).unwrap();
+
+        driver.write_all_assemblies(true).unwrap();
+        let out_path = driver.assembly_output_path_from_file(file_id);
+
         CompileTestDriver {
             _temp_dir: temp_dir,
             driver,
@@ -69,8 +71,8 @@ impl CompileTestDriver {
                     .expect("compiler errors are not UTF-8 formatted")
             )
         }
-        let out_path = self.driver.assembly_output_path(self.file_id);
-        self.driver.write_assembly(self.file_id, true).unwrap();
+        self.driver.write_all_assemblies(true).unwrap();
+        let out_path = self.driver.assembly_output_path_from_file(self.file_id);
         assert_eq!(
             &out_path, &self.out_path,
             "recompiling did not result in the same assembly"
diff --git a/crates/mun_test/src/fixture.rs b/crates/mun_test/src/fixture.rs
index b2ad41de1..040c646a7 100644
--- a/crates/mun_test/src/fixture.rs
+++ b/crates/mun_test/src/fixture.rs
@@ -1,7 +1,7 @@
 use compiler::RelativePathBuf;
 use itertools::Itertools;
 
-const DEFAULT_FILE_NAME: &str = "main.mun";
+const DEFAULT_FILE_NAME: &str = "mod.mun";
 const META_LINE: &str = "//-";
 
 /// A `Fixture` describes an single file in a project workspace. `Fixture`s can be parsed from a