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

Issue:
feat: add type aliases
We want to introduce type aliases, as follows:

```rust
type Typename = OtherTypeName;
```

In large, this change builds on existing architecture, but it will require work in several places of our stack:
- [x] lexer: add the `type` keyword ([src](https://github.com/mun-lang/mun/blob/master/crates/mun_syntax/src/grammar.ron), [example](https://github.com/rust-analyzer/rust-analyzer/blob/363718ba42d1eb906577a0a03c14248a0962a06a/crates/ra_syntax/src/grammar.ron#L90))
- [x] parsing: add parsing of `type TypeName = OtherTypeName` ([example#1](https://github.com/rust-analyzer/rust-analyzer/blob/363718ba42d1eb906577a0a03c14248a0962a06a/crates/ra_syntax/src/grammar.ron#L139), [example#2](https://github.com/rust-analyzer/rust-analyzer/blob/363718ba42d1eb906577a0a03c14248a0962a06a/crates/ra_parser/src/grammar/items.rs#L336))
- [x] hir: add type aliases as type definition ([src](https://github.com/mun-lang/mun/tree/master/crates/mun_hir/src))
- [x] tests: add test for all of the above stages

**Good first issue process:**

- [x] [read our contributing guidelines](https://github.com/mun-lang/mun/blob/master/CONTRIBUTING.md)
- [x] claim this issue (assign yourself or comment below)
- [x] implement the feature and start a Pull Request with in the description: closes #110. 

If this is your first PR, welcome 🎉 😄



Discussion / hints:
Hey,
I think that this issue will make more sense if generics feature lands. Or are there any use cases in current language state?
Yes there was previously a case where this was needed. But that was no longer the case so this has much lower priority.
I have a question about mun-lang syntax. Should type aliases be definable in a block?
For example, should Example 2 work the same as Example 1?
Example1:

```rust
type OtherTypename = i32;
type Typename = OtherTypename;
fn main() {
    let a: Typename = 10
}
```

Example2:

```rust
type OtherTypeName = i32;
fn main() {
    type Typename = OtherTypeName;  // <- Does mun-lang expect this?
    let a: Typename = 10;
}
```
Yes, [Rust supports this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=bda07dc4a1e5bbaee0a2d67e9c9300e6) and my expectation for Mun was to mimick this.

@Wodann
Hi, may I create a PR to support only [Example1](https://github.com/mun-lang/mun/issues/110#issuecomment-671688006)?

rust-analyzer does not yet support Example2.
> The current code in rust-analyzer just completely ignores local items and builds a Resolver instance for top-level scope.

https://github.com/rust-analyzer/rust-analyzer/issues/1165
I think it would be better to wait a little longer for the implementation of Example 2 to reuse their consideration and implementation. 


Definitely! We prefer smaller increments.

Fix patch:
diff --git a/crates/mun_codegen/src/ir/file_group.rs b/crates/mun_codegen/src/ir/file_group.rs
index 0443e9dc7..83bc5defb 100644
--- a/crates/mun_codegen/src/ir/file_group.rs
+++ b/crates/mun_codegen/src/ir/file_group.rs
@@ -65,6 +65,7 @@ pub(crate) fn gen_file_group_ir<'db, 'ink>(
             ModuleDef::Function(_) => (), // TODO: Extern types?
             ModuleDef::Struct(_) => (),
             ModuleDef::BuiltinType(_) => (),
+            ModuleDef::TypeAlias(_) => (),
         }
     }
 
@@ -118,7 +119,7 @@ pub(crate) fn gen_file_group_ir<'db, 'ink>(
             ModuleDef::Function(f) => {
                 type_table_builder.collect_fn(*f);
             }
-            ModuleDef::BuiltinType(_) => (),
+            ModuleDef::BuiltinType(_) | ModuleDef::TypeAlias(_) => (),
         }
     }
 
diff --git a/crates/mun_compiler/src/diagnostics.rs b/crates/mun_compiler/src/diagnostics.rs
index 622f764cc..ff7da5166 100644
--- a/crates/mun_compiler/src/diagnostics.rs
+++ b/crates/mun_compiler/src/diagnostics.rs
@@ -79,4 +79,19 @@ mod tests {
             "\n\nstruct Foo {\ni: bool\n}\n\nfn main() {\nlet a = Foo { i: false };\nlet b = a.t;\n}"
         ));
     }
+
+    #[test]
+    fn test_free_type_alias_error() {
+        insta::assert_display_snapshot!(compilation_errors("\n\ntype Foo;"));
+    }
+
+    #[test]
+    fn test_type_alias_target_undeclared_error() {
+        insta::assert_display_snapshot!(compilation_errors("\n\ntype Foo = UnknownType;"));
+    }
+
+    #[test]
+    fn test_cyclic_type_alias_error() {
+        insta::assert_display_snapshot!(compilation_errors("\n\ntype Foo = Foo;"));
+    }
 }
diff --git a/crates/mun_hir/src/adt.rs b/crates/mun_hir/src/adt.rs
index 42f934311..f7e58a9ff 100644
--- a/crates/mun_hir/src/adt.rs
+++ b/crates/mun_hir/src/adt.rs
@@ -3,7 +3,7 @@ use std::{fmt, sync::Arc};
 use crate::type_ref::{TypeRefBuilder, TypeRefId, TypeRefMap, TypeRefSourceMap};
 use crate::{
     arena::{Arena, RawId},
-    ids::{AstItemDef, StructId},
+    ids::{AstItemDef, StructId, TypeAliasId},
     AsName, DefDatabase, Name,
 };
 use mun_syntax::ast::{self, NameOwner, TypeAscriptionOwner};
@@ -121,3 +121,42 @@ impl StructData {
         &self.type_ref_map
     }
 }
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct TypeAliasData {
+    pub name: Name,
+    pub type_ref_id: TypeRefId,
+    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 src = id.source(db);
+        let name = src
+            .value
+            .name()
+            .map(|n| n.as_name())
+            .unwrap_or_else(Name::missing);
+        let mut type_ref_builder = TypeRefBuilder::default();
+        let type_ref_opt = src.value.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,
+            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
+    }
+}
diff --git a/crates/mun_hir/src/code_model.rs b/crates/mun_hir/src/code_model.rs
index fd818f9c9..83cd5db95 100644
--- a/crates/mun_hir/src/code_model.rs
+++ b/crates/mun_hir/src/code_model.rs
@@ -1,11 +1,11 @@
 pub(crate) mod src;
 
 use self::src::HasSource;
-use crate::adt::{StructData, StructFieldId};
+use crate::adt::{StructData, StructFieldId, TypeAliasData};
 use crate::builtin_type::BuiltinType;
 use crate::code_model::diagnostics::ModuleDefinitionDiagnostic;
 use crate::diagnostics::DiagnosticSink;
-use crate::expr::validator::ExprValidator;
+use crate::expr::validator::{ExprValidator, TypeAliasValidator};
 use crate::expr::{Body, BodySourceMap};
 use crate::ids::AstItemDef;
 use crate::ids::LocationCtx;
@@ -15,7 +15,7 @@ use crate::resolve::{Resolution, Resolver};
 use crate::ty::{lower::LowerBatchResult, InferenceResult};
 use crate::type_ref::{TypeRefBuilder, TypeRefId, TypeRefMap, TypeRefSourceMap};
 use crate::{
-    ids::{FunctionId, StructId},
+    ids::{FunctionId, StructId, TypeAliasId},
     AsName, DefDatabase, FileId, HirDatabase, Name, Ty,
 };
 use mun_syntax::ast::{ExternOwner, NameOwner, TypeAscriptionOwner, VisibilityOwner};
@@ -52,11 +52,11 @@ impl Module {
             diag.add_to(db.upcast(), self, sink);
         }
         for decl in self.declarations(db) {
-            #[allow(clippy::single_match)]
             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(_) => (),
             }
         }
     }
@@ -104,6 +104,11 @@ impl ModuleData {
                                 id: StructId::from_ast_id(loc_ctx, ast_id),
                             }))
                         }
+                        DefKind::TypeAlias(ast_id) => {
+                            data.definitions.push(ModuleDef::TypeAlias(TypeAlias {
+                                id: TypeAliasId::from_ast_id(loc_ctx, ast_id),
+                            }))
+                        }
                     }
                 }
             };
@@ -121,6 +126,7 @@ pub enum ModuleDef {
     Function(Function),
     BuiltinType(BuiltinType),
     Struct(Struct),
+    TypeAlias(TypeAlias),
 }
 
 impl From<Function> for ModuleDef {
@@ -328,7 +334,8 @@ impl Function {
     }
 
     pub fn ty(self, db: &dyn HirDatabase) -> Ty {
-        db.type_for_def(self.into(), Namespace::Values)
+        // TODO: Add detection of cyclick types
+        db.type_for_def(self.into(), Namespace::Values).0
     }
 
     pub fn infer(self, db: &dyn HirDatabase) -> Arc<InferenceResult> {
@@ -418,7 +425,8 @@ impl Struct {
     }
 
     pub fn ty(self, db: &dyn HirDatabase) -> Ty {
-        db.type_for_def(self.into(), Namespace::Types)
+        // TODO: Add detection of cyclick types
+        db.type_for_def(self.into(), Namespace::Types).0
     }
 
     pub fn lower(self, db: &dyn HirDatabase) -> Arc<LowerBatchResult> {
@@ -442,6 +450,54 @@ impl Struct {
     }
 }
 
+#[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.file_id(db),
+        }
+    }
+
+    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) -> TypeRefId {
+        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};
@@ -466,6 +522,9 @@ mod diagnostics {
             DefKind::Struct(id) => {
                 SyntaxNodePtr::new(id.with_file_id(owner.file_id).to_node(db).syntax())
             }
+            DefKind::TypeAlias(id) => {
+                SyntaxNodePtr::new(id.with_file_id(owner.file_id).to_node(db).syntax())
+            }
         }
     }
 
diff --git a/crates/mun_hir/src/code_model/src.rs b/crates/mun_hir/src/code_model/src.rs
index 00eab5ec7..b24eb2458 100644
--- a/crates/mun_hir/src/code_model/src.rs
+++ b/crates/mun_hir/src/code_model/src.rs
@@ -1,4 +1,4 @@
-use crate::code_model::{Function, Struct, StructField};
+use crate::code_model::{Function, Struct, StructField, TypeAlias};
 use crate::ids::AstItemDef;
 use crate::in_file::InFile;
 use crate::DefDatabase;
@@ -45,3 +45,10 @@ impl HasSource for StructField {
         InFile::new(file_id, ast)
     }
 }
+
+impl HasSource for TypeAlias {
+    type Ast = ast::TypeAliasDef;
+    fn source(self, db: &dyn DefDatabase) -> InFile<ast::TypeAliasDef> {
+        self.id.source(db)
+    }
+}
diff --git a/crates/mun_hir/src/db.rs b/crates/mun_hir/src/db.rs
index 2b90e4ebd..5f9734a75 100644
--- a/crates/mun_hir/src/db.rs
+++ b/crates/mun_hir/src/db.rs
@@ -5,13 +5,13 @@ use crate::name_resolution::Namespace;
 use crate::ty::lower::LowerBatchResult;
 use crate::ty::{CallableDef, FnSig, Ty, TypableDef};
 use crate::{
-    adt::StructData,
+    adt::{StructData, TypeAliasData},
     code_model::{DefWithBody, FnData, Function, ModuleData},
     ids,
     line_index::LineIndex,
     name_resolution::ModuleScope,
     ty::InferenceResult,
-    AstIdMap, ExprScopes, FileId, RawItems, Struct,
+    AstIdMap, ExprScopes, FileId, RawItems, Struct, TypeAlias,
 };
 use mun_syntax::{ast, Parse, SourceFile};
 use mun_target::abi;
@@ -66,6 +66,9 @@ pub trait DefDatabase: SourceDatabase + Upcast<dyn SourceDatabase> {
     #[salsa::invoke(StructData::struct_data_query)]
     fn struct_data(&self, id: ids::StructId) -> Arc<StructData>;
 
+    #[salsa::invoke(TypeAliasData::type_alias_data_query)]
+    fn type_alias_data(&self, id: ids::TypeAliasId) -> Arc<TypeAliasData>;
+
     #[salsa::invoke(crate::FnData::fn_data_query)]
     fn fn_data(&self, func: Function) -> Arc<FnData>;
 
@@ -80,6 +83,10 @@ pub trait DefDatabase: SourceDatabase + Upcast<dyn SourceDatabase> {
     /// Interns a struct definition
     #[salsa::interned]
     fn intern_struct(&self, loc: ids::ItemLoc<ast::StructDef>) -> ids::StructId;
+
+    /// Interns a type alias definition
+    #[salsa::interned]
+    fn intern_type_alias(&self, loc: ids::ItemLoc<ast::TypeAliasDef>) -> ids::TypeAliasId;
 }
 
 #[salsa::query_group(HirDatabaseStorage)]
@@ -104,11 +111,15 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
     #[salsa::invoke(crate::ty::lower::lower_struct_query)]
     fn lower_struct(&self, def: Struct) -> Arc<LowerBatchResult>;
 
+    #[salsa::invoke(crate::ty::lower::lower_type_alias_query)]
+    fn lower_type_alias(&self, def: TypeAlias) -> Arc<LowerBatchResult>;
+
     #[salsa::invoke(crate::ty::callable_item_sig)]
     fn callable_sig(&self, def: CallableDef) -> FnSig;
 
     #[salsa::invoke(crate::ty::type_for_def)]
-    fn type_for_def(&self, def: TypableDef, ns: Namespace) -> Ty;
+    #[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>;
diff --git a/crates/mun_hir/src/diagnostics.rs b/crates/mun_hir/src/diagnostics.rs
index 633d978db..3e0808e62 100644
--- a/crates/mun_hir/src/diagnostics.rs
+++ b/crates/mun_hir/src/diagnostics.rs
@@ -117,6 +117,26 @@ impl Diagnostic for UnresolvedType {
     }
 }
 
+#[derive(Debug)]
+pub struct CyclicType {
+    pub file: FileId,
+    pub type_ref: AstPtr<ast::TypeRef>,
+}
+
+impl Diagnostic for CyclicType {
+    fn message(&self) -> String {
+        "cyclic type".to_string()
+    }
+
+    fn source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.type_ref.syntax_node_ptr())
+    }
+
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
+
 #[derive(Debug)]
 pub struct ExpectedFunction {
     pub file: FileId,
@@ -687,3 +707,22 @@ impl Diagnostic for InvalidLiteral {
         self
     }
 }
+
+#[derive(Debug)]
+pub struct FreeTypeAliasWithoutTypeRef {
+    pub type_alias_def: InFile<SyntaxNodePtr>,
+}
+
+impl Diagnostic for FreeTypeAliasWithoutTypeRef {
+    fn message(&self) -> String {
+        "free type alias without type ref".to_string()
+    }
+
+    fn source(&self) -> InFile<SyntaxNodePtr> {
+        self.type_alias_def
+    }
+
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
diff --git a/crates/mun_hir/src/expr/validator.rs b/crates/mun_hir/src/expr/validator.rs
index d80b3750f..aa9febc93 100644
--- a/crates/mun_hir/src/expr/validator.rs
+++ b/crates/mun_hir/src/expr/validator.rs
@@ -1,8 +1,12 @@
 use crate::code_model::src::HasSource;
-use crate::diagnostics::{ExternCannotHaveBody, ExternNonPrimitiveParam};
+use crate::diagnostics::{
+    ExternCannotHaveBody, ExternNonPrimitiveParam, FreeTypeAliasWithoutTypeRef,
+};
 use crate::expr::BodySourceMap;
 use crate::in_file::InFile;
-use crate::{diagnostics::DiagnosticSink, Body, Expr, Function, HirDatabase, InferenceResult};
+use crate::{
+    diagnostics::DiagnosticSink, Body, Expr, Function, HirDatabase, InferenceResult, TypeAlias,
+};
 use mun_syntax::{AstNode, SyntaxNodePtr};
 use std::sync::Arc;
 
@@ -83,3 +87,25 @@ impl<'a> ExprValidator<'a> {
         }
     }
 }
+
+pub struct TypeAliasValidator<'a> {
+    type_alias: TypeAlias,
+    db: &'a dyn HirDatabase,
+}
+
+impl<'a> TypeAliasValidator<'a> {
+    /// Constructs a validator for the provided `TypeAlias`.
+    pub fn new(type_alias: TypeAlias, db: &'a dyn HirDatabase) -> Self {
+        TypeAliasValidator { type_alias, db }
+    }
+
+    /// Validates that the provided `TypeAlias` has a target type of alias.
+    pub fn validate_target_type_existence(&self, sink: &mut DiagnosticSink) {
+        let src = self.type_alias.source(self.db.upcast());
+        if src.value.type_ref().is_none() {
+            sink.push(FreeTypeAliasWithoutTypeRef {
+                type_alias_def: src.map(|t| SyntaxNodePtr::new(t.syntax())),
+            })
+        }
+    }
+}
diff --git a/crates/mun_hir/src/ids.rs b/crates/mun_hir/src/ids.rs
index 5ea811d0b..22d756395 100644
--- a/crates/mun_hir/src/ids.rs
+++ b/crates/mun_hir/src/ids.rs
@@ -119,3 +119,16 @@ impl AstItemDef<ast::StructDef> for StructId {
         db.lookup_intern_struct(self)
     }
 }
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct TypeAliasId(salsa::InternId);
+impl_intern_key!(TypeAliasId);
+
+impl AstItemDef<ast::TypeAliasDef> for TypeAliasId {
+    fn intern(db: &dyn DefDatabase, loc: ItemLoc<ast::TypeAliasDef>) -> Self {
+        db.intern_type_alias(loc)
+    }
+    fn lookup_intern(self, db: &dyn DefDatabase) -> ItemLoc<ast::TypeAliasDef> {
+        db.lookup_intern_type_alias(self)
+    }
+}
diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs
index 604cd4401..3d766e00c 100644
--- a/crates/mun_hir/src/lib.rs
+++ b/crates/mun_hir/src/lib.rs
@@ -74,4 +74,4 @@ use crate::{
 };
 
 pub use self::adt::StructMemoryKind;
-pub use self::code_model::{FnData, Function, Module, ModuleDef, Struct, Visibility};
+pub use self::code_model::{FnData, Function, Module, ModuleDef, Struct, TypeAlias, Visibility};
diff --git a/crates/mun_hir/src/name_resolution.rs b/crates/mun_hir/src/name_resolution.rs
index c8a734de9..bfac749ce 100644
--- a/crates/mun_hir/src/name_resolution.rs
+++ b/crates/mun_hir/src/name_resolution.rs
@@ -64,7 +64,15 @@ pub(crate) fn module_scope_query(db: &dyn HirDatabase, file_id: FileId) -> Arc<M
                     },
                 );
             }
-            _ => {}
+            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/raw.rs b/crates/mun_hir/src/raw.rs
index d22585ff6..9fc6cdc99 100644
--- a/crates/mun_hir/src/raw.rs
+++ b/crates/mun_hir/src/raw.rs
@@ -27,6 +27,7 @@ pub(super) struct DefData {
 pub(super) enum DefKind {
     Function(FileAstId<ast::FunctionDef>),
     Struct(FileAstId<ast::StructDef>),
+    TypeAlias(FileAstId<ast::TypeAliasDef>),
 }
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -58,6 +59,9 @@ impl RawItems {
                 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
diff --git a/crates/mun_hir/src/ty.rs b/crates/mun_hir/src/ty.rs
index e5781b5a7..93cb5e533 100644
--- a/crates/mun_hir/src/ty.rs
+++ b/crates/mun_hir/src/ty.rs
@@ -8,10 +8,12 @@ use crate::display::{HirDisplay, HirFormatter};
 use crate::ty::infer::InferTy;
 use crate::ty::lower::fn_sig_for_struct_constructor;
 use crate::utils::make_mut_slice;
-use crate::{HirDatabase, Struct, StructMemoryKind};
+use crate::{HirDatabase, Struct, StructMemoryKind, TypeAlias};
 pub(crate) use infer::infer_query;
 pub use infer::InferenceResult;
-pub(crate) use lower::{callable_item_sig, fn_sig_for_fn, type_for_def, CallableDef, TypableDef};
+pub(crate) use lower::{
+    callable_item_sig, fn_sig_for_fn, type_for_cycle_recover, type_for_def, CallableDef, TypableDef,
+};
 pub use primitives::{FloatTy, IntTy};
 pub use resolve::ResolveBitness;
 use std::ops::{Deref, DerefMut};
@@ -65,6 +67,9 @@ pub enum TypeCtor {
     /// TODO: Add tuples and enumerations
     Struct(Struct),
 
+    /// A type alias
+    TypeAlias(TypeAlias),
+
     /// The never type `never`.
     Never,
 
@@ -272,6 +277,7 @@ impl HirDisplay for ApplicationTy {
             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::Never => write!(f, "never"),
             TypeCtor::FnDef(CallableDef::Function(def)) => {
                 let sig = fn_sig_for_fn(f.db, def);
diff --git a/crates/mun_hir/src/ty/infer.rs b/crates/mun_hir/src/ty/infer.rs
index 6d25ba431..339f23368 100644
--- a/crates/mun_hir/src/ty/infer.rs
+++ b/crates/mun_hir/src/ty/infer.rs
@@ -196,6 +196,7 @@ impl<'a> InferenceResultBuilder<'a> {
                 LowerDiagnostic::UnresolvedType { id } => {
                     InferenceDiagnostic::UnresolvedType { id }
                 }
+                LowerDiagnostic::CyclicType { id } => InferenceDiagnostic::CyclicType { id },
             };
             self.diagnostics.push(diag);
         }
@@ -700,7 +701,8 @@ impl<'a> InferenceResultBuilder<'a> {
             Resolution::Def(def) => {
                 let typable: Option<TypableDef> = def.into();
                 let typable = typable?;
-                let ty = self.db.type_for_def(typable, Namespace::Values);
+                // 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);
@@ -774,7 +776,9 @@ impl<'a> InferenceResultBuilder<'a> {
                 if let Some(typable) = def.into() {
                     match typable {
                         TypableDef::Struct(s) => (s.ty(self.db), Some(s.into())),
-                        TypableDef::BuiltinType(_) | TypableDef::Function(_) => (Ty::Unknown, None),
+                        TypableDef::BuiltinType(_)
+                        | TypableDef::Function(_)
+                        | TypableDef::TypeAlias(_) => (Ty::Unknown, None),
                     }
                 } else {
                     unreachable!();
@@ -1005,7 +1009,7 @@ mod diagnostics {
     use crate::{
         adt::StructKind,
         code_model::src::HasSource,
-        diagnostics::{DiagnosticSink, UnresolvedType, UnresolvedValue},
+        diagnostics::{CyclicType, DiagnosticSink, UnresolvedType, UnresolvedValue},
         ty::infer::ExprOrPatId,
         type_ref::TypeRefId,
         ExprId, Function, HirDatabase, IntTy, Name, Ty,
@@ -1019,6 +1023,9 @@ mod diagnostics {
         UnresolvedType {
             id: TypeRefId,
         },
+        CyclicType {
+            id: TypeRefId,
+        },
         ExpectedFunction {
             id: ExprId,
             found: Ty,
@@ -1126,6 +1133,10 @@ 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(UnresolvedType { file, type_ref });
                 }
+                InferenceDiagnostic::CyclicType { 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(CyclicType { file, type_ref });
+                }
                 InferenceDiagnostic::ParameterCountMismatch {
                     id,
                     expected,
diff --git a/crates/mun_hir/src/ty/lower.rs b/crates/mun_hir/src/ty/lower.rs
index f784a617c..5a22e03b3 100644
--- a/crates/mun_hir/src/ty/lower.rs
+++ b/crates/mun_hir/src/ty/lower.rs
@@ -7,7 +7,7 @@ use crate::name_resolution::Namespace;
 use crate::resolve::{Resolution, Resolver};
 use crate::ty::{FnSig, Ty, TypeCtor};
 use crate::type_ref::{TypeRef, TypeRefId, TypeRefMap, TypeRefSourceMap};
-use crate::{FileId, Function, HirDatabase, ModuleDef, Path, Struct};
+use crate::{FileId, Function, HirDatabase, ModuleDef, Path, Struct, TypeAlias};
 use std::ops::Index;
 use std::sync::Arc;
 
@@ -67,11 +67,14 @@ impl Ty {
     ) -> Ty {
         let res = match &type_ref_map[type_ref] {
             TypeRef::Path(path) => Ty::from_hir_path(db, resolver, path),
-            TypeRef::Error => Some(Ty::Unknown),
-            TypeRef::Empty => Some(Ty::Empty),
-            TypeRef::Never => Some(Ty::simple(TypeCtor::Never)),
+            TypeRef::Error => Some((Ty::Unknown, false)),
+            TypeRef::Empty => Some((Ty::Empty, false)),
+            TypeRef::Never => Some((Ty::simple(TypeCtor::Never), false)),
         };
-        if let Some(ty) = res {
+        if let Some((ty, is_cyclic)) = res {
+            if is_cyclic {
+                diagnostics.push(LowerDiagnostic::CyclicType { id: type_ref })
+            }
             ty
         } else {
             diagnostics.push(LowerDiagnostic::UnresolvedType { id: type_ref });
@@ -83,7 +86,7 @@ impl Ty {
         db: &dyn HirDatabase,
         resolver: &Resolver,
         path: &Path,
-    ) -> Option<Self> {
+    ) -> Option<(Self, bool)> {
         let resolution = resolver
             .resolve_path_without_assoc_items(db, path)
             .take_types();
@@ -102,8 +105,7 @@ impl Ty {
             Some(it) => it,
         };
 
-        let ty = db.type_for_def(typable, Namespace::Types);
-        Some(ty)
+        Some(db.type_for_def(typable, Namespace::Types))
     }
 }
 
@@ -129,11 +131,17 @@ pub fn lower_struct_query(db: &dyn HirDatabase, s: Struct) -> Arc<LowerBatchResu
     types_from_hir(db, &s.resolver(db), 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())
+}
+
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub enum TypableDef {
     Function(Function),
     BuiltinType(BuiltinType),
     Struct(Struct),
+    TypeAlias(TypeAlias),
 }
 
 impl From<Function> for TypableDef {
@@ -160,6 +168,7 @@ impl From<ModuleDef> for Option<TypableDef> {
             ModuleDef::Function(f) => Some(TypableDef::Function(f)),
             ModuleDef::BuiltinType(t) => Some(TypableDef::BuiltinType(t)),
             ModuleDef::Struct(t) => Some(TypableDef::Struct(t)),
+            ModuleDef::TypeAlias(t) => Some(TypableDef::TypeAlias(t)),
         }
     }
 }
@@ -191,17 +200,30 @@ impl CallableDef {
 /// `struct Foo(usize)`, we have two types: The type of the struct itself, and
 /// the constructor function `(usize) -> Foo` which lives in the values
 /// namespace.
-pub(crate) fn type_for_def(db: &dyn HirDatabase, def: TypableDef, ns: Namespace) -> Ty {
-    match (def, ns) {
+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::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::TypeAlias(_), Namespace::Values) => Ty::Unknown,
+    };
+    (ty, false)
+}
+
+/// Recover with an unknown type when a cycle is detected in the salsa database.
+pub(crate) fn type_for_cycle_recover(
+    _db: &dyn HirDatabase,
+    _cycle: &[String],
+    _def: &TypableDef,
+    _ns: &Namespace,
+) -> (Ty, bool) {
+    (Ty::Unknown, true)
 }
 
 /// Build the declared type of a static.
@@ -264,8 +286,15 @@ fn type_for_struct(_db: &dyn HirDatabase, def: Struct) -> Ty {
     Ty::simple(TypeCtor::Struct(def))
 }
 
+fn type_for_type_alias(db: &dyn HirDatabase, def: TypeAlias) -> Ty {
+    let data = def.data(db.upcast());
+    let resolver = def.resolver(db);
+    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::UnresolvedType;
+    use crate::diagnostics::{CyclicType, UnresolvedType};
     use crate::{
         diagnostics::DiagnosticSink,
         type_ref::{TypeRefId, TypeRefSourceMap},
@@ -275,6 +304,7 @@ pub mod diagnostics {
     #[derive(Debug, PartialEq, Eq, Clone)]
     pub(crate) enum LowerDiagnostic {
         UnresolvedType { id: TypeRefId },
+        CyclicType { id: TypeRefId },
     }
 
     impl LowerDiagnostic {
@@ -290,6 +320,10 @@ pub mod diagnostics {
                     file: file_id,
                     type_ref: source_map.type_ref_syntax(*id).unwrap(),
                 }),
+                LowerDiagnostic::CyclicType { id } => sink.push(CyclicType {
+                    file: file_id,
+                    type_ref: source_map.type_ref_syntax(*id).unwrap(),
+                }),
             }
         }
     }
diff --git a/crates/mun_syntax/src/ast/generated.rs b/crates/mun_syntax/src/ast/generated.rs
index bf3a9be38..266a0052e 100644
--- a/crates/mun_syntax/src/ast/generated.rs
+++ b/crates/mun_syntax/src/ast/generated.rs
@@ -645,7 +645,7 @@ pub struct ModuleItem {
 impl AstNode for ModuleItem {
     fn can_cast(kind: SyntaxKind) -> bool {
         match kind {
-            FUNCTION_DEF | STRUCT_DEF => true,
+            FUNCTION_DEF | STRUCT_DEF | TYPE_ALIAS_DEF => true,
             _ => false,
         }
     }
@@ -664,6 +664,7 @@ impl AstNode for ModuleItem {
 pub enum ModuleItemKind {
     FunctionDef(FunctionDef),
     StructDef(StructDef),
+    TypeAliasDef(TypeAliasDef),
 }
 impl From<FunctionDef> for ModuleItem {
     fn from(n: FunctionDef) -> ModuleItem {
@@ -675,6 +676,11 @@ impl From<StructDef> for ModuleItem {
         ModuleItem { syntax: n.syntax }
     }
 }
+impl From<TypeAliasDef> for ModuleItem {
+    fn from(n: TypeAliasDef) -> ModuleItem {
+        ModuleItem { syntax: n.syntax }
+    }
+}
 
 impl ModuleItem {
     pub fn kind(&self) -> ModuleItemKind {
@@ -683,6 +689,9 @@ impl ModuleItem {
                 ModuleItemKind::FunctionDef(FunctionDef::cast(self.syntax.clone()).unwrap())
             }
             STRUCT_DEF => ModuleItemKind::StructDef(StructDef::cast(self.syntax.clone()).unwrap()),
+            TYPE_ALIAS_DEF => {
+                ModuleItemKind::TypeAliasDef(TypeAliasDef::cast(self.syntax.clone()).unwrap())
+            }
             _ => unreachable!(),
         }
     }
@@ -1514,6 +1523,39 @@ impl TupleFieldDefList {
     }
 }
 
+// TypeAliasDef
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct TypeAliasDef {
+    pub(crate) syntax: SyntaxNode,
+}
+
+impl AstNode for TypeAliasDef {
+    fn can_cast(kind: SyntaxKind) -> bool {
+        match kind {
+            TYPE_ALIAS_DEF => true,
+            _ => false,
+        }
+    }
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(TypeAliasDef { syntax })
+        } else {
+            None
+        }
+    }
+    fn syntax(&self) -> &SyntaxNode {
+        &self.syntax
+    }
+}
+impl ast::NameOwner for TypeAliasDef {}
+impl ast::DocCommentsOwner for TypeAliasDef {}
+impl TypeAliasDef {
+    pub fn type_ref(&self) -> Option<TypeRef> {
+        super::child_opt(self)
+    }
+}
+
 // TypeRef
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
diff --git a/crates/mun_syntax/src/grammar.ron b/crates/mun_syntax/src/grammar.ron
index 5e4041898..75fdace2f 100644
--- a/crates/mun_syntax/src/grammar.ron
+++ b/crates/mun_syntax/src/grammar.ron
@@ -91,6 +91,7 @@ Grammar(
         "struct",
         "never",
         "pub",
+        "type",
 
         "package",
         "super",
@@ -126,6 +127,7 @@ Grammar(
         "PARAM",
 
         "STRUCT_DEF",
+        "TYPE_ALIAS_DEF",
         "MEMORY_TYPE_SPECIFIER",
         "RECORD_FIELD_DEF_LIST",
         "RECORD_FIELD_DEF",
@@ -173,7 +175,7 @@ Grammar(
             traits: [ "ModuleItemOwner", "FunctionDefOwner" ],
         ),
         "ModuleItem": (
-            enum: ["FunctionDef", "StructDef"]
+            enum: ["FunctionDef", "StructDef", "TypeAliasDef"]
         ),
         "Visibility": (),
         "FunctionDef": (
@@ -205,6 +207,13 @@ Grammar(
                 "DocCommentsOwner",
             ]
         ),
+        "TypeAliasDef": (
+            options: ["TypeRef"],
+            traits: [
+                "NameOwner",
+                "DocCommentsOwner",
+            ]
+        ),
         "MemoryTypeSpecifier": (),
         "RecordFieldDefList": (collections: [("fields", "RecordFieldDef")]),
         "RecordFieldDef": (
diff --git a/crates/mun_syntax/src/lib.rs b/crates/mun_syntax/src/lib.rs
index 01d1f3acf..2db682283 100644
--- a/crates/mun_syntax/src/lib.rs
+++ b/crates/mun_syntax/src/lib.rs
@@ -163,6 +163,7 @@ fn api_walkthrough() {
         match item.kind() {
             ast::ModuleItemKind::FunctionDef(f) => func = Some(f),
             ast::ModuleItemKind::StructDef(_) => (),
+            ast::ModuleItemKind::TypeAliasDef(_) => (),
         }
     }
 
diff --git a/crates/mun_syntax/src/parsing/grammar/adt.rs b/crates/mun_syntax/src/parsing/grammar/adt.rs
index 202ee8460..69a68fdc3 100644
--- a/crates/mun_syntax/src/parsing/grammar/adt.rs
+++ b/crates/mun_syntax/src/parsing/grammar/adt.rs
@@ -18,6 +18,17 @@ pub(super) fn struct_def(p: &mut Parser, m: Marker) {
     m.complete(p, STRUCT_DEF);
 }
 
+pub(super) fn type_alias_def(p: &mut Parser, m: Marker) {
+    assert!(p.at(T![type]));
+    p.bump(T![type]);
+    name(p);
+    if p.eat(T![=]) {
+        types::type_(p);
+    }
+    p.expect(T![;]);
+    m.complete(p, TYPE_ALIAS_DEF);
+}
+
 pub(super) fn record_field_def_list(p: &mut Parser) {
     assert!(p.at(T!['{']));
     let m = p.start();
diff --git a/crates/mun_syntax/src/parsing/grammar/declarations.rs b/crates/mun_syntax/src/parsing/grammar/declarations.rs
index c73213a1d..84c17cde1 100644
--- a/crates/mun_syntax/src/parsing/grammar/declarations.rs
+++ b/crates/mun_syntax/src/parsing/grammar/declarations.rs
@@ -65,6 +65,9 @@ fn declarations_without_modifiers(p: &mut Parser, m: Marker) -> Result<(), Marke
         T![struct] => {
             adt::struct_def(p, m);
         }
+        T![type] => {
+            adt::type_alias_def(p, m);
+        }
         _ => return Err(m),
     };
     Ok(())
diff --git a/crates/mun_syntax/src/syntax_kind/generated.rs b/crates/mun_syntax/src/syntax_kind/generated.rs
index b0e0b6833..1d9b58173 100644
--- a/crates/mun_syntax/src/syntax_kind/generated.rs
+++ b/crates/mun_syntax/src/syntax_kind/generated.rs
@@ -84,6 +84,7 @@ pub enum SyntaxKind {
     STRUCT_KW,
     NEVER_KW,
     PUB_KW,
+    TYPE_KW,
     PACKAGE_KW,
     SUPER_KW,
     SELF_KW,
@@ -106,6 +107,7 @@ pub enum SyntaxKind {
     PARAM_LIST,
     PARAM,
     STRUCT_DEF,
+    TYPE_ALIAS_DEF,
     MEMORY_TYPE_SPECIFIER,
     RECORD_FIELD_DEF_LIST,
     RECORD_FIELD_DEF,
@@ -345,6 +347,9 @@ macro_rules! T {
     (pub) => {
         $crate::SyntaxKind::PUB_KW
     };
+    (type) => {
+        $crate::SyntaxKind::TYPE_KW
+    };
     (package) => {
         $crate::SyntaxKind::PACKAGE_KW
     };
@@ -395,6 +400,7 @@ impl SyntaxKind {
             | STRUCT_KW
             | NEVER_KW
             | PUB_KW
+            | TYPE_KW
             | PACKAGE_KW
             | SUPER_KW
             | SELF_KW
@@ -539,6 +545,7 @@ impl SyntaxKind {
             STRUCT_KW => &SyntaxInfo { name: "STRUCT_KW" },
             NEVER_KW => &SyntaxInfo { name: "NEVER_KW" },
             PUB_KW => &SyntaxInfo { name: "PUB_KW" },
+            TYPE_KW => &SyntaxInfo { name: "TYPE_KW" },
             PACKAGE_KW => &SyntaxInfo { name: "PACKAGE_KW" },
             SUPER_KW => &SyntaxInfo { name: "SUPER_KW" },
             SELF_KW => &SyntaxInfo { name: "SELF_KW" },
@@ -561,6 +568,7 @@ impl SyntaxKind {
             PARAM_LIST => &SyntaxInfo { name: "PARAM_LIST" },
             PARAM => &SyntaxInfo { name: "PARAM" },
             STRUCT_DEF => &SyntaxInfo { name: "STRUCT_DEF" },
+            TYPE_ALIAS_DEF => &SyntaxInfo { name: "TYPE_ALIAS_DEF" },
             MEMORY_TYPE_SPECIFIER => &SyntaxInfo { name: "MEMORY_TYPE_SPECIFIER" },
             RECORD_FIELD_DEF_LIST => &SyntaxInfo { name: "RECORD_FIELD_DEF_LIST" },
             RECORD_FIELD_DEF => &SyntaxInfo { name: "RECORD_FIELD_DEF" },
@@ -621,6 +629,7 @@ impl SyntaxKind {
             "struct" => STRUCT_KW,
             "never" => NEVER_KW,
             "pub" => PUB_KW,
+            "type" => TYPE_KW,
             "package" => PACKAGE_KW,
             "super" => SUPER_KW,
             "self" => SELF_KW,


Test patch:
diff --git a/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__cyclic_type_alias_error.snap b/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__cyclic_type_alias_error.snap
new file mode 100644
index 000000000..0f822fc91
--- /dev/null
+++ b/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__cyclic_type_alias_error.snap
@@ -0,0 +1,10 @@
+---
+source: crates/mun_compiler/src/diagnostics.rs
+expression: "compilation_errors(\"\\n\\ntype Foo = Foo;\")"
+---
+error: cyclic type
+ --> main.mun:3:12
+  |
+3 | type Foo = Foo;
+  |            ^^^ cyclic type
+  |
diff --git a/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__free_type_alias_error.snap b/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__free_type_alias_error.snap
new file mode 100644
index 000000000..b50572480
--- /dev/null
+++ b/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__free_type_alias_error.snap
@@ -0,0 +1,10 @@
+---
+source: crates/mun_compiler/src/diagnostics.rs
+expression: "compilation_errors(\"\\n\\ntype Foo;\")"
+---
+error: free type alias without type ref
+ --> main.mun:3:1
+  |
+3 | type Foo;
+  | ^^^^^^^^^ free type alias without type ref
+  |
diff --git a/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__type_alias_target_undeclared_error.snap b/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__type_alias_target_undeclared_error.snap
new file mode 100644
index 000000000..3bcace238
--- /dev/null
+++ b/crates/mun_compiler/src/snapshots/mun_compiler__diagnostics__tests__type_alias_target_undeclared_error.snap
@@ -0,0 +1,10 @@
+---
+source: crates/mun_compiler/src/diagnostics.rs
+expression: "compilation_errors(\"\\n\\ntype Foo = UnknownType;\")"
+---
+error: cannot find type `UnknownType` in this scope
+ --> main.mun:3:12
+  |
+3 | type Foo = UnknownType;
+  |            ^^^^^^^^^^^ not found in this scope
+  |
diff --git a/crates/mun_hir/src/expr/validator/snapshots/mun_hir__expr__validator__tests__free_type_alias_without_type_ref.snap b/crates/mun_hir/src/expr/validator/snapshots/mun_hir__expr__validator__tests__free_type_alias_without_type_ref.snap
new file mode 100644
index 000000000..344b52be4
--- /dev/null
+++ b/crates/mun_hir/src/expr/validator/snapshots/mun_hir__expr__validator__tests__free_type_alias_without_type_ref.snap
@@ -0,0 +1,6 @@
+---
+source: crates/mun_hir/src/expr/validator/tests.rs
+expression: "type Foo; // `Foo` must have a target type"
+---
+[0; 9): free type alias without type ref
+
diff --git a/crates/mun_hir/src/expr/validator/tests.rs b/crates/mun_hir/src/expr/validator/tests.rs
index 6341c6442..0f5e0518e 100644
--- a/crates/mun_hir/src/expr/validator/tests.rs
+++ b/crates/mun_hir/src/expr/validator/tests.rs
@@ -1,6 +1,8 @@
 use crate::db::{SourceDatabase, Upcast};
-use crate::expr::validator::ExprValidator;
-use crate::{diagnostics::DiagnosticSink, ids::LocationCtx, mock::MockDatabase, Function};
+use crate::expr::validator::{ExprValidator, TypeAliasValidator};
+use crate::{
+    diagnostics::DiagnosticSink, ids::LocationCtx, mock::MockDatabase, Function, TypeAlias,
+};
 use mun_syntax::{ast, AstNode};
 use std::fmt::Write;
 
@@ -66,6 +68,15 @@ fn test_uninitialized_access_while() {
     )
 }
 
+#[test]
+fn test_free_type_alias_without_type_ref() {
+    diagnostics_snapshot(
+        r#"
+    type Foo; // `Foo` must have a target type
+    "#,
+    )
+}
+
 fn diagnostics(content: &str) -> String {
     let (db, file_id) = MockDatabase::with_single_file(content);
     let source_file = db.parse(file_id).ok().unwrap();
@@ -84,6 +95,12 @@ fn diagnostics(content: &str) -> String {
             };
             ExprValidator::new(fun, &db).validate_body(&mut diag_sink);
         }
+        if let Some(def) = ast::TypeAliasDef::cast(node.clone()) {
+            let type_alias = TypeAlias {
+                id: ctx.to_def(&def),
+            };
+            TypeAliasValidator::new(type_alias, &db).validate_target_type_existence(&mut diag_sink);
+        }
     }
     drop(diag_sink);
     diags
diff --git a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_type_alias.snap b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_type_alias.snap
new file mode 100644
index 000000000..38fb10555
--- /dev/null
+++ b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__infer_type_alias.snap
@@ -0,0 +1,9 @@
+---
+source: crates/mun_hir/src/ty/tests.rs
+expression: "type Foo = i32;\ntype Bar = Foo;\ntype Baz = UnknownType;  // error: undefined type\n\nfn main(a: Foo) {\n    let b: Bar = a;\n}"
+---
+[43; 54): undefined type
+[91; 92) 'a': i32
+[99; 122) '{     ...= a; }': nothing
+[109; 110) 'b': i32
+[118; 119) 'a': i32
diff --git a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__recursive_alias.snap b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__recursive_alias.snap
new file mode 100644
index 000000000..2e68fdb9a
--- /dev/null
+++ b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__recursive_alias.snap
@@ -0,0 +1,14 @@
+---
+source: crates/mun_hir/src/ty/tests.rs
+expression: "struct Foo {}\ntype Foo = Foo;\n\ntype A = B;\ntype B = A;\n\nfn main() {\n    let a: Foo;  // error: unknown type\n    let b: A;    // error: unknown type\n    let c: B;    // error: unknown type\n}"
+---
+[25; 28): cyclic type
+[40; 41): cyclic type
+[52; 53): cyclic type
+[79; 82): cyclic type
+[119; 120): cyclic type
+[159; 160): cyclic type
+[66; 189) '{     ...type }': nothing
+[76; 77) 'a': {unknown}
+[116; 117) 'b': {unknown}
+[156; 157) 'c': {unknown}
diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs
index 3fd7192b3..adb246cae 100644
--- a/crates/mun_hir/src/ty/tests.rs
+++ b/crates/mun_hir/src/ty/tests.rs
@@ -3,7 +3,7 @@ use crate::diagnostics::DiagnosticSink;
 use crate::expr::BodySourceMap;
 use crate::ids::LocationCtx;
 use crate::mock::MockDatabase;
-use crate::{Function, HirDisplay, InferenceResult};
+use crate::{Function, HirDisplay, InferenceResult, TypeAlias};
 use mun_syntax::{ast, AstNode};
 use std::fmt::Write;
 use std::sync::Arc;
@@ -392,6 +392,40 @@ fn extern_fn() {
     )
 }
 
+#[test]
+fn infer_type_alias() {
+    infer_snapshot(
+        r#"
+    type Foo = i32;
+    type Bar = Foo;
+    type Baz = UnknownType;  // error: undefined type
+
+    fn main(a: Foo) {
+        let b: Bar = a;
+    }
+    "#,
+    )
+}
+
+#[test]
+fn recursive_alias() {
+    infer_snapshot(
+        r#"
+    struct Foo {}
+    type Foo = Foo;
+
+    type A = B;
+    type B = A;
+
+    fn main() {
+        let a: Foo;  // error: unknown type
+        let b: A;    // error: unknown type
+        let c: B;    // error: unknown type
+    }
+    "#,
+    )
+}
+
 fn infer_snapshot(text: &str) {
     let text = text.trim().replace("\n    ", "\n");
     insta::assert_snapshot!(insta::_macro_support::AutoName, infer(&text), &text);
@@ -466,6 +500,12 @@ fn infer(content: &str) -> String {
 
             infer_def(infer_result, source_map);
         }
+        if let Some(def) = ast::TypeAliasDef::cast(node.clone()) {
+            let type_alias = TypeAlias {
+                id: ctx.to_def(&def),
+            };
+            type_alias.diagnostics(&db, &mut diag_sink);
+        }
     }
 
     drop(diag_sink);
diff --git a/crates/mun_syntax/src/tests/lexer.rs b/crates/mun_syntax/src/tests/lexer.rs
index 1610b67a7..4116a882d 100644
--- a/crates/mun_syntax/src/tests/lexer.rs
+++ b/crates/mun_syntax/src/tests/lexer.rs
@@ -118,7 +118,7 @@ fn keywords() {
         r#"
     break do else false for fn if in nil
     return true while let mut struct class
-    never loop pub super self package
+    never loop pub super self package type
     "#,
     )
 }
diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs
index 53dc33798..c4c814f7c 100644
--- a/crates/mun_syntax/src/tests/parser.rs
+++ b/crates/mun_syntax/src/tests/parser.rs
@@ -355,3 +355,13 @@ fn extern_fn() {
     "#,
     )
 }
+
+#[test]
+fn type_alias_def() {
+    snapshot_test(
+        r#"
+    type Foo = i32;
+    type Bar = Foo;
+    "#,
+    )
+}
diff --git a/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__lexer__keywords.snap b/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__lexer__keywords.snap
index 73a0b2ad3..0eb40602c 100644
--- a/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__lexer__keywords.snap
+++ b/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__lexer__keywords.snap
@@ -1,6 +1,6 @@
 ---
 source: crates/mun_syntax/src/tests/lexer.rs
-expression: "break do else false for fn if in nil\nreturn true while let mut struct class\nnever loop pub super self package"
+expression: "break do else false for fn if in nil\nreturn true while let mut struct class\nnever loop pub super self package type"
 ---
 BREAK_KW 5 "break"
 WHITESPACE 1 " "
@@ -45,4 +45,6 @@ WHITESPACE 1 " "
 SELF_KW 4 "self"
 WHITESPACE 1 " "
 PACKAGE_KW 7 "package"
+WHITESPACE 1 " "
+TYPE_KW 4 "type"
 
diff --git a/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__type_alias_def.snap b/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__type_alias_def.snap
new file mode 100644
index 000000000..a17cb47c7
--- /dev/null
+++ b/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__type_alias_def.snap
@@ -0,0 +1,35 @@
+---
+source: crates/mun_syntax/src/tests/parser.rs
+expression: "type Foo = i32;\ntype Bar = Foo;"
+---
+SOURCE_FILE@[0; 31)
+  TYPE_ALIAS_DEF@[0; 15)
+    TYPE_KW@[0; 4) "type"
+    WHITESPACE@[4; 5) " "
+    NAME@[5; 8)
+      IDENT@[5; 8) "Foo"
+    WHITESPACE@[8; 9) " "
+    EQ@[9; 10) "="
+    WHITESPACE@[10; 11) " "
+    PATH_TYPE@[11; 14)
+      PATH@[11; 14)
+        PATH_SEGMENT@[11; 14)
+          NAME_REF@[11; 14)
+            IDENT@[11; 14) "i32"
+    SEMI@[14; 15) ";"
+  WHITESPACE@[15; 16) "\n"
+  TYPE_ALIAS_DEF@[16; 31)
+    TYPE_KW@[16; 20) "type"
+    WHITESPACE@[20; 21) " "
+    NAME@[21; 24)
+      IDENT@[21; 24) "Bar"
+    WHITESPACE@[24; 25) " "
+    EQ@[25; 26) "="
+    WHITESPACE@[26; 27) " "
+    PATH_TYPE@[27; 30)
+      PATH@[27; 30)
+        PATH_SEGMENT@[27; 30)
+          NAME_REF@[27; 30)
+            IDENT@[27; 30) "Foo"
+    SEMI@[30; 31) ";"
+