diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index 4109b2ea84f..8e77a4a417f 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -31,9 +31,9 @@ use hir_ty::{
     display::{write_bounds_like_dyn_trait_with_prefix, HirDisplayError, HirFormatter},
     method_resolution,
     traits::{FnTrait, Solution, SolutionVariables},
-    ApplicationTy, BoundVar, CallableDefId, Canonical, DebruijnIndex, FnSig, GenericPredicate,
-    InEnvironment, Obligation, ProjectionPredicate, ProjectionTy, Scalar, Substs, TraitEnvironment,
-    Ty, TyDefId, TyKind, TypeCtor,
+    BoundVar, CallableDefId, Canonical, DebruijnIndex, FnSig, GenericPredicate, InEnvironment,
+    Obligation, ProjectionPredicate, ProjectionTy, Scalar, Substs, TraitEnvironment, Ty, TyDefId,
+    TyKind,
 };
 use rustc_hash::FxHashSet;
 use stdx::{format_to, impl_from};
@@ -1547,28 +1547,19 @@ impl Type {
     }
 
     pub fn is_unit(&self) -> bool {
-        matches!(
-            self.ty.value,
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::Tuple { cardinality: 0 }, .. })
-        )
+        matches!(self.ty.value, Ty::Tuple { cardinality: 0, .. })
     }
     pub fn is_bool(&self) -> bool {
-        matches!(
-            self.ty.value,
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::Scalar(Scalar::Bool), .. })
-        )
+        matches!(self.ty.value, Ty::Scalar(Scalar::Bool))
     }
 
     pub fn is_mutable_reference(&self) -> bool {
-        matches!(
-            self.ty.value,
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::Ref(Mutability::Mut), .. })
-        )
+        matches!(self.ty.value, Ty::Ref(Mutability::Mut, ..))
     }
 
     pub fn remove_ref(&self) -> Option<Type> {
-        if let Ty::Apply(ApplicationTy { ctor: TypeCtor::Ref(_), .. }) = self.ty.value {
-            self.ty.value.substs().map(|substs| self.derived(substs[0].clone()))
+        if let Ty::Ref(.., substs) = &self.ty.value {
+            Some(self.derived(substs[0].clone()))
         } else {
             None
         }
@@ -1688,7 +1679,7 @@ impl Type {
 
     pub fn as_callable(&self, db: &dyn HirDatabase) -> Option<Callable> {
         let def = match self.ty.value {
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::FnDef(def), parameters: _ }) => Some(def),
+            Ty::FnDef(def, _) => Some(def),
             _ => None,
         };
 
@@ -1697,20 +1688,16 @@ impl Type {
     }
 
     pub fn is_closure(&self) -> bool {
-        matches!(&self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::Closure { .. }, .. }))
+        matches!(&self.ty.value, Ty::Closure { .. })
     }
 
     pub fn is_fn(&self) -> bool {
-        matches!(
-            &self.ty.value,
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::FnDef(..), .. })
-                | Ty::Apply(ApplicationTy { ctor: TypeCtor::FnPtr { .. }, .. })
-        )
+        matches!(&self.ty.value, Ty::FnDef(..) | Ty::FnPtr { .. })
     }
 
     pub fn is_packed(&self, db: &dyn HirDatabase) -> bool {
         let adt_id = match self.ty.value {
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(adt_id), .. }) => adt_id,
+            Ty::Adt(adt_id, ..) => adt_id,
             _ => return false,
         };
 
@@ -1722,7 +1709,7 @@ impl Type {
     }
 
     pub fn is_raw_ptr(&self) -> bool {
-        matches!(&self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. }))
+        matches!(&self.ty.value, Ty::RawPtr(..))
     }
 
     pub fn contains_unknown(&self) -> bool {
@@ -1731,44 +1718,34 @@ impl Type {
         fn go(ty: &Ty) -> bool {
             match ty {
                 Ty::Unknown => true,
-                Ty::Apply(a_ty) => a_ty.parameters.iter().any(go),
-                _ => false,
+                _ => ty.substs().map_or(false, |substs| substs.iter().any(go)),
             }
         }
     }
 
     pub fn fields(&self, db: &dyn HirDatabase) -> Vec<(Field, Type)> {
-        if let Ty::Apply(a_ty) = &self.ty.value {
-            let variant_id = match a_ty.ctor {
-                TypeCtor::Adt(AdtId::StructId(s)) => s.into(),
-                TypeCtor::Adt(AdtId::UnionId(u)) => u.into(),
-                _ => return Vec::new(),
-            };
-
-            return db
-                .field_types(variant_id)
-                .iter()
-                .map(|(local_id, ty)| {
-                    let def = Field { parent: variant_id.into(), id: local_id };
-                    let ty = ty.clone().subst(&a_ty.parameters);
-                    (def, self.derived(ty))
-                })
-                .collect();
+        let (variant_id, substs) = match self.ty.value {
+            Ty::Adt(AdtId::StructId(s), ref substs) => (s.into(), substs),
+            Ty::Adt(AdtId::UnionId(u), ref substs) => (u.into(), substs),
+            _ => return Vec::new(),
         };
-        Vec::new()
+
+        db.field_types(variant_id)
+            .iter()
+            .map(|(local_id, ty)| {
+                let def = Field { parent: variant_id.into(), id: local_id };
+                let ty = ty.clone().subst(substs);
+                (def, self.derived(ty))
+            })
+            .collect()
     }
 
     pub fn tuple_fields(&self, _db: &dyn HirDatabase) -> Vec<Type> {
-        let mut res = Vec::new();
-        if let Ty::Apply(a_ty) = &self.ty.value {
-            if let TypeCtor::Tuple { .. } = a_ty.ctor {
-                for ty in a_ty.parameters.iter() {
-                    let ty = ty.clone();
-                    res.push(self.derived(ty));
-                }
-            }
-        };
-        res
+        if let Ty::Tuple { substs, .. } = &self.ty.value {
+            substs.iter().map(|ty| self.derived(ty.clone())).collect()
+        } else {
+            Vec::new()
+        }
     }
 
     pub fn autoderef<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Type> + 'a {
@@ -1805,15 +1782,13 @@ impl Type {
     }
 
     pub fn type_parameters(&self) -> impl Iterator<Item = Type> + '_ {
-        let ty = self.ty.value.strip_references();
-        let substs = match ty {
-            Ty::Apply(apply_ty) => &apply_ty.parameters,
-            Ty::Opaque(opaque_ty) => &opaque_ty.parameters,
-            _ => return Either::Left(iter::empty()),
-        };
-
-        let iter = substs.iter().map(move |ty| self.derived(ty.clone()));
-        Either::Right(iter)
+        self.ty
+            .value
+            .strip_references()
+            .substs()
+            .into_iter()
+            .flat_map(|substs| substs.iter())
+            .map(move |ty| self.derived(ty.clone()))
     }
 
     pub fn iterate_method_candidates<T>(
@@ -1903,17 +1878,8 @@ impl Type {
 
     // FIXME: provide required accessors such that it becomes implementable from outside.
     pub fn is_equal_for_find_impls(&self, other: &Type) -> bool {
-        match (&self.ty.value, &other.ty.value) {
-            (Ty::Apply(a_original_ty), Ty::Apply(ApplicationTy { ctor, parameters })) => match ctor
-            {
-                TypeCtor::Ref(..) => match parameters.as_single() {
-                    Ty::Apply(a_ty) => a_original_ty.ctor == a_ty.ctor,
-                    _ => false,
-                },
-                _ => a_original_ty.ctor == *ctor,
-            },
-            _ => false,
-        }
+        let rref = other.remove_ref();
+        self.ty.value.equals_ctor(rref.as_ref().map_or(&other.ty.value, |it| &it.ty.value))
     }
 
     fn derived(&self, ty: Ty) -> Type {
@@ -1958,25 +1924,20 @@ impl Type {
         fn walk_type(db: &dyn HirDatabase, type_: &Type, cb: &mut impl FnMut(Type)) {
             let ty = type_.ty.value.strip_references();
             match ty {
-                Ty::Apply(ApplicationTy { ctor, parameters }) => {
-                    match ctor {
-                        TypeCtor::Adt(_) => {
-                            cb(type_.derived(ty.clone()));
-                        }
-                        TypeCtor::AssociatedType(_) => {
-                            if let Some(_) = ty.associated_type_parent_trait(db) {
-                                cb(type_.derived(ty.clone()));
-                            }
-                        }
-                        TypeCtor::OpaqueType(..) => {
-                            if let Some(bounds) = ty.impl_trait_bounds(db) {
-                                walk_bounds(db, &type_.derived(ty.clone()), &bounds, cb);
-                            }
-                        }
-                        _ => (),
+                Ty::Adt(_, parameters) => {
+                    cb(type_.derived(ty.clone()));
+                    walk_substs(db, type_, parameters, cb);
+                }
+                Ty::AssociatedType(_, parameters) => {
+                    if let Some(_) = ty.associated_type_parent_trait(db) {
+                        cb(type_.derived(ty.clone()));
+                    }
+                    walk_substs(db, type_, parameters, cb);
+                }
+                Ty::OpaqueType(_, parameters) => {
+                    if let Some(bounds) = ty.impl_trait_bounds(db) {
+                        walk_bounds(db, &type_.derived(ty.clone()), &bounds, cb);
                     }
-
-                    // adt params, tuples, etc...
                     walk_substs(db, type_, parameters, cb);
                 }
                 Ty::Opaque(opaque_ty) => {
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index dc21f6051a5..64ce4add159 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -20,7 +20,7 @@ use hir_def::{
 use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile};
 use hir_ty::{
     diagnostics::{record_literal_missing_fields, record_pattern_missing_fields},
-    InferenceResult, Substs, Ty,
+    InferenceResult, Substs,
 };
 use syntax::{
     ast::{self, AstNode},
@@ -299,14 +299,11 @@ impl SourceAnalyzer {
         let infer = self.infer.as_ref()?;
 
         let expr_id = self.expr_id(db, &literal.clone().into())?;
-        let substs = match &infer.type_of_expr[expr_id] {
-            Ty::Apply(a_ty) => &a_ty.parameters,
-            _ => return None,
-        };
+        let substs = infer.type_of_expr[expr_id].substs()?;
 
         let (variant, missing_fields, _exhaustive) =
             record_literal_missing_fields(db, infer, expr_id, &body[expr_id])?;
-        let res = self.missing_fields(db, krate, substs, variant, missing_fields);
+        let res = self.missing_fields(db, krate, &substs, variant, missing_fields);
         Some(res)
     }
 
@@ -320,14 +317,11 @@ impl SourceAnalyzer {
         let infer = self.infer.as_ref()?;
 
         let pat_id = self.pat_id(&pattern.clone().into())?;
-        let substs = match &infer.type_of_pat[pat_id] {
-            Ty::Apply(a_ty) => &a_ty.parameters,
-            _ => return None,
-        };
+        let substs = infer.type_of_pat[pat_id].substs()?;
 
         let (variant, missing_fields, _exhaustive) =
             record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?;
-        let res = self.missing_fields(db, krate, substs, variant, missing_fields);
+        let res = self.missing_fields(db, krate, &substs, variant, missing_fields);
         Some(res)
     }
 
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index d740b726555..66a88e2b628 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -17,7 +17,7 @@ use crate::{
         MissingPatFields, RemoveThisSemicolon,
     },
     utils::variant_data,
-    ApplicationTy, InferenceResult, Ty, TypeCtor,
+    InferenceResult, Ty,
 };
 
 pub(crate) use hir_def::{
@@ -381,14 +381,11 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
             _ => return,
         };
 
-        let core_result_ctor = TypeCtor::Adt(AdtId::EnumId(core_result_enum));
-        let core_option_ctor = TypeCtor::Adt(AdtId::EnumId(core_option_enum));
-
-        let (params, required) = match &mismatch.expected {
-            Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_result_ctor => {
+        let (params, required) = match mismatch.expected {
+            Ty::Adt(AdtId::EnumId(enum_id), ref parameters) if enum_id == core_result_enum => {
                 (parameters, "Ok".to_string())
             }
-            Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_option_ctor => {
+            Ty::Adt(AdtId::EnumId(enum_id), ref parameters) if enum_id == core_option_enum => {
                 (parameters, "Some".to_string())
             }
             _ => return,
diff --git a/crates/hir_ty/src/diagnostics/match_check.rs b/crates/hir_ty/src/diagnostics/match_check.rs
index 1c1423fbfef..86fee0050f2 100644
--- a/crates/hir_ty/src/diagnostics/match_check.rs
+++ b/crates/hir_ty/src/diagnostics/match_check.rs
@@ -227,7 +227,7 @@ use hir_def::{
 use la_arena::Idx;
 use smallvec::{smallvec, SmallVec};
 
-use crate::{db::HirDatabase, ApplicationTy, InferenceResult, Ty, TypeCtor};
+use crate::{db::HirDatabase, InferenceResult, Ty};
 
 #[derive(Debug, Clone, Copy)]
 /// Either a pattern from the source code being analyzed, represented as
@@ -627,14 +627,12 @@ pub(super) fn is_useful(
     // - `!` type
     // In those cases, no match arm is useful.
     match cx.infer[cx.match_expr].strip_references() {
-        Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(AdtId::EnumId(enum_id)), .. }) => {
+        Ty::Adt(AdtId::EnumId(enum_id), ..) => {
             if cx.db.enum_data(*enum_id).variants.is_empty() {
                 return Ok(Usefulness::NotUseful);
             }
         }
-        Ty::Apply(ApplicationTy { ctor: TypeCtor::Never, .. }) => {
-            return Ok(Usefulness::NotUseful);
-        }
+        Ty::Never => return Ok(Usefulness::NotUseful),
         _ => (),
     }
 
diff --git a/crates/hir_ty/src/diagnostics/unsafe_check.rs b/crates/hir_ty/src/diagnostics/unsafe_check.rs
index 9c506112da2..b439915c7ad 100644
--- a/crates/hir_ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir_ty/src/diagnostics/unsafe_check.rs
@@ -11,9 +11,7 @@ use hir_def::{
 };
 use hir_expand::diagnostics::DiagnosticSink;
 
-use crate::{
-    db::HirDatabase, diagnostics::MissingUnsafe, ApplicationTy, InferenceResult, Ty, TypeCtor,
-};
+use crate::{db::HirDatabase, diagnostics::MissingUnsafe, InferenceResult, Ty};
 
 pub(super) struct UnsafeValidator<'a, 'b: 'a> {
     owner: DefWithBodyId,
@@ -112,7 +110,7 @@ fn walk_unsafe(
             }
         }
         Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
-            if let Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. }) = &infer[*expr] {
+            if let Ty::RawPtr(..) = &infer[*expr] {
                 unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block });
             }
         }
diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs
index b4801cb2158..1a3b9095a72 100644
--- a/crates/hir_ty/src/display.rs
+++ b/crates/hir_ty/src/display.rs
@@ -3,9 +3,8 @@
 use std::{borrow::Cow, fmt};
 
 use crate::{
-    db::HirDatabase, primitive, utils::generics, ApplicationTy, CallableDefId, FnSig,
-    GenericPredicate, Lifetime, Obligation, OpaqueTy, OpaqueTyId, ProjectionTy, Scalar, Substs,
-    TraitRef, Ty, TypeCtor,
+    db::HirDatabase, primitive, utils::generics, CallableDefId, FnSig, GenericPredicate, Lifetime,
+    Obligation, OpaqueTy, OpaqueTyId, ProjectionTy, Scalar, Substs, TraitRef, Ty,
 };
 use arrayvec::ArrayVec;
 use hir_def::{
@@ -235,39 +234,62 @@ impl HirDisplay for &Ty {
     }
 }
 
-impl HirDisplay for ApplicationTy {
+impl HirDisplay for ProjectionTy {
     fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
         if f.should_truncate() {
             return write!(f, "{}", TYPE_HINT_TRUNCATION);
         }
 
-        match self.ctor {
-            TypeCtor::Scalar(Scalar::Bool) => write!(f, "bool")?,
-            TypeCtor::Scalar(Scalar::Char) => write!(f, "char")?,
-            TypeCtor::Scalar(Scalar::Float(t)) => {
-                write!(f, "{}", primitive::float_ty_to_string(t))?
-            }
-            TypeCtor::Scalar(Scalar::Int(t)) => write!(f, "{}", primitive::int_ty_to_string(t))?,
-            TypeCtor::Scalar(Scalar::Uint(t)) => write!(f, "{}", primitive::uint_ty_to_string(t))?,
-            TypeCtor::Str => write!(f, "str")?,
-            TypeCtor::Slice => {
-                let t = self.parameters.as_single();
+        let trait_ = f.db.trait_data(self.trait_(f.db));
+        let first_parameter = self.parameters[0].into_displayable(
+            f.db,
+            f.max_size,
+            f.omit_verbose_types,
+            f.display_target,
+        );
+        write!(f, "<{} as {}", first_parameter, trait_.name)?;
+        if self.parameters.len() > 1 {
+            write!(f, "<")?;
+            f.write_joined(&self.parameters[1..], ", ")?;
+            write!(f, ">")?;
+        }
+        write!(f, ">::{}", f.db.type_alias_data(self.associated_ty).name)?;
+        Ok(())
+    }
+}
+
+impl HirDisplay for Ty {
+    fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
+        if f.should_truncate() {
+            return write!(f, "{}", TYPE_HINT_TRUNCATION);
+        }
+
+        match self {
+            Ty::Never => write!(f, "!")?,
+            Ty::Str => write!(f, "str")?,
+            Ty::Scalar(Scalar::Bool) => write!(f, "bool")?,
+            Ty::Scalar(Scalar::Char) => write!(f, "char")?,
+            &Ty::Scalar(Scalar::Float(t)) => write!(f, "{}", primitive::float_ty_to_string(t))?,
+            &Ty::Scalar(Scalar::Int(t)) => write!(f, "{}", primitive::int_ty_to_string(t))?,
+            &Ty::Scalar(Scalar::Uint(t)) => write!(f, "{}", primitive::uint_ty_to_string(t))?,
+            Ty::Slice(parameters) => {
+                let t = parameters.as_single();
                 write!(f, "[")?;
                 t.hir_fmt(f)?;
                 write!(f, "]")?;
             }
-            TypeCtor::Array => {
-                let t = self.parameters.as_single();
+            Ty::Array(parameters) => {
+                let t = parameters.as_single();
                 write!(f, "[")?;
                 t.hir_fmt(f)?;
                 write!(f, "; _]")?;
             }
-            TypeCtor::RawPtr(m) | TypeCtor::Ref(m) => {
-                let t = self.parameters.as_single();
+            Ty::RawPtr(m, parameters) | Ty::Ref(m, parameters) => {
+                let t = parameters.as_single();
                 let ty_display =
                     t.into_displayable(f.db, f.max_size, f.omit_verbose_types, f.display_target);
 
-                if matches!(self.ctor, TypeCtor::RawPtr(_)) {
+                if matches!(self, Ty::RawPtr(..)) {
                     write!(f, "*{}", m.as_keyword_for_ptr())?;
                 } else {
                     write!(f, "&{}", m.as_keyword_for_ref())?;
@@ -308,25 +330,23 @@ impl HirDisplay for ApplicationTy {
                     write!(f, "{}", ty_display)?;
                 }
             }
-            TypeCtor::Never => write!(f, "!")?,
-            TypeCtor::Tuple { .. } => {
-                let ts = &self.parameters;
-                if ts.len() == 1 {
+            Ty::Tuple { substs, .. } => {
+                if substs.len() == 1 {
                     write!(f, "(")?;
-                    ts[0].hir_fmt(f)?;
+                    substs[0].hir_fmt(f)?;
                     write!(f, ",)")?;
                 } else {
                     write!(f, "(")?;
-                    f.write_joined(&*ts.0, ", ")?;
+                    f.write_joined(&*substs.0, ", ")?;
                     write!(f, ")")?;
                 }
             }
-            TypeCtor::FnPtr { is_varargs, .. } => {
-                let sig = FnSig::from_fn_ptr_substs(&self.parameters, is_varargs);
+            &Ty::FnPtr { is_varargs, ref substs, .. } => {
+                let sig = FnSig::from_fn_ptr_substs(&substs, is_varargs);
                 sig.hir_fmt(f)?;
             }
-            TypeCtor::FnDef(def) => {
-                let sig = f.db.callable_item_signature(def).subst(&self.parameters);
+            &Ty::FnDef(def, ref parameters) => {
+                let sig = f.db.callable_item_signature(def).subst(parameters);
                 match def {
                     CallableDefId::FunctionId(ff) => {
                         write!(f, "fn {}", f.db.function_data(ff).name)?
@@ -336,7 +356,7 @@ impl HirDisplay for ApplicationTy {
                         write!(f, "{}", f.db.enum_data(e.parent).variants[e.local_id].name)?
                     }
                 };
-                if self.parameters.len() > 0 {
+                if parameters.len() > 0 {
                     let generics = generics(f.db.upcast(), def.into());
                     let (parent_params, self_param, type_params, _impl_trait_params) =
                         generics.provenance_split();
@@ -344,7 +364,7 @@ impl HirDisplay for ApplicationTy {
                     // We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self?
                     if total_len > 0 {
                         write!(f, "<")?;
-                        f.write_joined(&self.parameters.0[..total_len], ", ")?;
+                        f.write_joined(&parameters.0[..total_len], ", ")?;
                         write!(f, ">")?;
                     }
                 }
@@ -363,7 +383,7 @@ impl HirDisplay for ApplicationTy {
                     write!(f, " -> {}", ret_display)?;
                 }
             }
-            TypeCtor::Adt(def_id) => {
+            &Ty::Adt(def_id, ref parameters) => {
                 match f.display_target {
                     DisplayTarget::Diagnostics | DisplayTarget::Test => {
                         let name = match def_id {
@@ -388,19 +408,18 @@ impl HirDisplay for ApplicationTy {
                     }
                 }
 
-                if self.parameters.len() > 0 {
+                if parameters.len() > 0 {
                     let parameters_to_write =
                         if f.display_target.is_source_code() || f.omit_verbose_types() {
                             match self
-                                .ctor
                                 .as_generic_def()
                                 .map(|generic_def_id| f.db.generic_defaults(generic_def_id))
                                 .filter(|defaults| !defaults.is_empty())
                             {
-                                None => self.parameters.0.as_ref(),
+                                None => parameters.0.as_ref(),
                                 Some(default_parameters) => {
                                     let mut default_from = 0;
-                                    for (i, parameter) in self.parameters.iter().enumerate() {
+                                    for (i, parameter) in parameters.iter().enumerate() {
                                         match (parameter, default_parameters.get(i)) {
                                             (&Ty::Unknown, _) | (_, None) => {
                                                 default_from = i + 1;
@@ -408,18 +427,18 @@ impl HirDisplay for ApplicationTy {
                                             (_, Some(default_parameter)) => {
                                                 let actual_default = default_parameter
                                                     .clone()
-                                                    .subst(&self.parameters.prefix(i));
+                                                    .subst(&parameters.prefix(i));
                                                 if parameter != &actual_default {
                                                     default_from = i + 1;
                                                 }
                                             }
                                         }
                                     }
-                                    &self.parameters.0[0..default_from]
+                                    &parameters.0[0..default_from]
                                 }
                             }
                         } else {
-                            self.parameters.0.as_ref()
+                            parameters.0.as_ref()
                         };
                     if !parameters_to_write.is_empty() {
                         write!(f, "<")?;
@@ -428,7 +447,7 @@ impl HirDisplay for ApplicationTy {
                     }
                 }
             }
-            TypeCtor::AssociatedType(type_alias) => {
+            &Ty::AssociatedType(type_alias, ref parameters) => {
                 let trait_ = match type_alias.lookup(f.db.upcast()).container {
                     AssocContainerId::TraitId(it) => it,
                     _ => panic!("not an associated type"),
@@ -439,30 +458,28 @@ impl HirDisplay for ApplicationTy {
                 // Use placeholder associated types when the target is test (https://rust-lang.github.io/chalk/book/clauses/type_equality.html#placeholder-associated-types)
                 if f.display_target.is_test() {
                     write!(f, "{}::{}", trait_.name, type_alias_data.name)?;
-                    if self.parameters.len() > 0 {
+                    if parameters.len() > 0 {
                         write!(f, "<")?;
-                        f.write_joined(&*self.parameters.0, ", ")?;
+                        f.write_joined(&*parameters.0, ", ")?;
                         write!(f, ">")?;
                     }
                 } else {
-                    let projection_ty = ProjectionTy {
-                        associated_ty: type_alias,
-                        parameters: self.parameters.clone(),
-                    };
+                    let projection_ty =
+                        ProjectionTy { associated_ty: type_alias, parameters: parameters.clone() };
 
                     projection_ty.hir_fmt(f)?;
                 }
             }
-            TypeCtor::ForeignType(type_alias) => {
+            &Ty::ForeignType(type_alias, ref parameters) => {
                 let type_alias = f.db.type_alias_data(type_alias);
                 write!(f, "{}", type_alias.name)?;
-                if self.parameters.len() > 0 {
+                if parameters.len() > 0 {
                     write!(f, "<")?;
-                    f.write_joined(&*self.parameters.0, ", ")?;
+                    f.write_joined(&*parameters.0, ", ")?;
                     write!(f, ">")?;
                 }
             }
-            TypeCtor::OpaqueType(opaque_ty_id) => {
+            &Ty::OpaqueType(opaque_ty_id, ref parameters) => {
                 match opaque_ty_id {
                     OpaqueTyId::ReturnTypeImplTrait(func, idx) => {
                         let datas =
@@ -470,19 +487,19 @@ impl HirDisplay for ApplicationTy {
                         let data = (*datas)
                             .as_ref()
                             .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
-                        let bounds = data.subst(&self.parameters);
+                        let bounds = data.subst(&parameters);
                         write_bounds_like_dyn_trait_with_prefix("impl", &bounds.value, f)?;
                         // FIXME: it would maybe be good to distinguish this from the alias type (when debug printing), and to show the substitution
                     }
                     OpaqueTyId::AsyncBlockTypeImplTrait(..) => {
                         write!(f, "impl Future<Output = ")?;
-                        self.parameters[0].hir_fmt(f)?;
+                        parameters[0].hir_fmt(f)?;
                         write!(f, ">")?;
                     }
                 }
             }
-            TypeCtor::Closure { .. } => {
-                let sig = self.parameters[0].callable_sig(f.db);
+            Ty::Closure { substs, .. } => {
+                let sig = substs[0].callable_sig(f.db);
                 if let Some(sig) = sig {
                     if sig.params().is_empty() {
                         write!(f, "||")?;
@@ -505,43 +522,6 @@ impl HirDisplay for ApplicationTy {
                     write!(f, "{{closure}}")?;
                 }
             }
-        }
-        Ok(())
-    }
-}
-
-impl HirDisplay for ProjectionTy {
-    fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
-        if f.should_truncate() {
-            return write!(f, "{}", TYPE_HINT_TRUNCATION);
-        }
-
-        let trait_ = f.db.trait_data(self.trait_(f.db));
-        let first_parameter = self.parameters[0].into_displayable(
-            f.db,
-            f.max_size,
-            f.omit_verbose_types,
-            f.display_target,
-        );
-        write!(f, "<{} as {}", first_parameter, trait_.name)?;
-        if self.parameters.len() > 1 {
-            write!(f, "<")?;
-            f.write_joined(&self.parameters[1..], ", ")?;
-            write!(f, ">")?;
-        }
-        write!(f, ">::{}", f.db.type_alias_data(self.associated_ty).name)?;
-        Ok(())
-    }
-}
-
-impl HirDisplay for Ty {
-    fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
-        if f.should_truncate() {
-            return write!(f, "{}", TYPE_HINT_TRUNCATION);
-        }
-
-        match self {
-            Ty::Apply(a_ty) => a_ty.hir_fmt(f)?,
             Ty::Projection(p_ty) => p_ty.hir_fmt(f)?,
             Ty::Placeholder(id) => {
                 let generics = generics(f.db.upcast(), id.parent);
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs
index 657f011d26a..a1769729ffc 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -38,7 +38,7 @@ use syntax::SmolStr;
 use super::{
     primitive::{FloatTy, IntTy},
     traits::{Guidance, Obligation, ProjectionPredicate, Solution},
-    InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk,
+    InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeWalk,
 };
 use crate::{
     db::HirDatabase, infer::diagnostics::InferenceDiagnostic, lower::ImplTraitLoweringMode, Scalar,
@@ -46,15 +46,6 @@ use crate::{
 
 pub(crate) use unify::unify;
 
-macro_rules! ty_app {
-    ($ctor:pat, $param:pat) => {
-        crate::Ty::Apply(crate::ApplicationTy { ctor: $ctor, parameters: $param })
-    };
-    ($ctor:pat) => {
-        ty_app!($ctor, _)
-    };
-}
-
 mod unify;
 mod path;
 mod expr;
@@ -684,9 +675,9 @@ impl InferTy {
     fn fallback_value(self) -> Ty {
         match self {
             InferTy::TypeVar(..) => Ty::Unknown,
-            InferTy::IntVar(..) => Ty::simple(TypeCtor::Scalar(Scalar::Int(IntTy::I32))),
-            InferTy::FloatVar(..) => Ty::simple(TypeCtor::Scalar(Scalar::Float(FloatTy::F64))),
-            InferTy::MaybeNeverTypeVar(..) => Ty::simple(TypeCtor::Never),
+            InferTy::IntVar(..) => Ty::Scalar(Scalar::Int(IntTy::I32)),
+            InferTy::FloatVar(..) => Ty::Scalar(Scalar::Float(FloatTy::F64)),
+            InferTy::MaybeNeverTypeVar(..) => Ty::Never,
         }
     }
 }
diff --git a/crates/hir_ty/src/infer/coerce.rs b/crates/hir_ty/src/infer/coerce.rs
index 32c7c57cd61..cd5fb325287 100644
--- a/crates/hir_ty/src/infer/coerce.rs
+++ b/crates/hir_ty/src/infer/coerce.rs
@@ -7,7 +7,7 @@
 use hir_def::{lang_item::LangItemTarget, type_ref::Mutability};
 use test_utils::mark;
 
-use crate::{autoderef, traits::Solution, Obligation, Substs, TraitRef, Ty, TypeCtor};
+use crate::{autoderef, traits::Solution, Obligation, Substs, TraitRef, Ty};
 
 use super::{unify::TypeVarValue, InEnvironment, InferTy, InferenceContext};
 
@@ -33,7 +33,7 @@ impl<'a> InferenceContext<'a> {
         } else if self.coerce(ty2, ty1) {
             ty1.clone()
         } else {
-            if let (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnDef(_))) = (ty1, ty2) {
+            if let (Ty::FnDef(..), Ty::FnDef(..)) = (ty1, ty2) {
                 mark::hit!(coerce_fn_reification);
                 // Special case: two function types. Try to coerce both to
                 // pointers to have a chance at getting a match. See
@@ -53,12 +53,12 @@ impl<'a> InferenceContext<'a> {
     fn coerce_inner(&mut self, mut from_ty: Ty, to_ty: &Ty) -> bool {
         match (&from_ty, to_ty) {
             // Never type will make type variable to fallback to Never Type instead of Unknown.
-            (ty_app!(TypeCtor::Never), Ty::Infer(InferTy::TypeVar(tv))) => {
+            (Ty::Never, Ty::Infer(InferTy::TypeVar(tv))) => {
                 let var = self.table.new_maybe_never_type_var();
                 self.table.var_unification_table.union_value(*tv, TypeVarValue::Known(var));
                 return true;
             }
-            (ty_app!(TypeCtor::Never), _) => return true,
+            (Ty::Never, _) => return true,
 
             // Trivial cases, this should go after `never` check to
             // avoid infer result type to be never
@@ -71,38 +71,33 @@ impl<'a> InferenceContext<'a> {
 
         // Pointer weakening and function to pointer
         match (&mut from_ty, to_ty) {
-            // `*mut T`, `&mut T, `&T`` -> `*const T`
+            // `*mut T` -> `*const T`
             // `&mut T` -> `&T`
-            // `&mut T` -> `*mut T`
-            (ty_app!(c1@TypeCtor::RawPtr(_)), ty_app!(c2@TypeCtor::RawPtr(Mutability::Shared)))
-            | (ty_app!(c1@TypeCtor::Ref(_)), ty_app!(c2@TypeCtor::RawPtr(Mutability::Shared)))
-            | (ty_app!(c1@TypeCtor::Ref(_)), ty_app!(c2@TypeCtor::Ref(Mutability::Shared)))
-            | (ty_app!(c1@TypeCtor::Ref(Mutability::Mut)), ty_app!(c2@TypeCtor::RawPtr(_))) => {
-                *c1 = *c2;
+            (Ty::RawPtr(m1, ..), Ty::RawPtr(m2 @ Mutability::Shared, ..))
+            | (Ty::Ref(m1, ..), Ty::Ref(m2 @ Mutability::Shared, ..)) => {
+                *m1 = *m2;
+            }
+            // `&T` -> `*const T`
+            // `&mut T` -> `*mut T`/`*const T`
+            (Ty::Ref(.., substs), &Ty::RawPtr(m2 @ Mutability::Shared, ..))
+            | (Ty::Ref(Mutability::Mut, substs), &Ty::RawPtr(m2, ..)) => {
+                from_ty = Ty::RawPtr(m2, substs.clone());
             }
 
-            // Illegal mutablity conversion
-            (
-                ty_app!(TypeCtor::RawPtr(Mutability::Shared)),
-                ty_app!(TypeCtor::RawPtr(Mutability::Mut)),
-            )
-            | (
-                ty_app!(TypeCtor::Ref(Mutability::Shared)),
-                ty_app!(TypeCtor::Ref(Mutability::Mut)),
-            ) => return false,
+            // Illegal mutability conversion
+            (Ty::RawPtr(Mutability::Shared, ..), Ty::RawPtr(Mutability::Mut, ..))
+            | (Ty::Ref(Mutability::Shared, ..), Ty::Ref(Mutability::Mut, ..)) => return false,
 
             // `{function_type}` -> `fn()`
-            (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnPtr { .. })) => {
-                match from_ty.callable_sig(self.db) {
-                    None => return false,
-                    Some(sig) => {
-                        from_ty = Ty::fn_ptr(sig);
-                    }
+            (Ty::FnDef(..), Ty::FnPtr { .. }) => match from_ty.callable_sig(self.db) {
+                None => return false,
+                Some(sig) => {
+                    from_ty = Ty::fn_ptr(sig);
                 }
-            }
+            },
 
-            (ty_app!(TypeCtor::Closure { .. }, params), ty_app!(TypeCtor::FnPtr { .. })) => {
-                from_ty = params[0].clone();
+            (Ty::Closure { substs, .. }, Ty::FnPtr { .. }) => {
+                from_ty = substs[0].clone();
             }
 
             _ => {}
@@ -115,9 +110,7 @@ impl<'a> InferenceContext<'a> {
         // Auto Deref if cannot coerce
         match (&from_ty, to_ty) {
             // FIXME: DerefMut
-            (ty_app!(TypeCtor::Ref(_), st1), ty_app!(TypeCtor::Ref(_), st2)) => {
-                self.unify_autoderef_behind_ref(&st1[0], &st2[0])
-            }
+            (Ty::Ref(_, st1), Ty::Ref(_, st2)) => self.unify_autoderef_behind_ref(&st1[0], &st2[0]),
 
             // Otherwise, normal unify
             _ => self.unify(&from_ty, to_ty),
@@ -178,17 +171,17 @@ impl<'a> InferenceContext<'a> {
             },
         ) {
             let derefed_ty = canonicalized.decanonicalize_ty(derefed_ty.value);
-            match (&*self.resolve_ty_shallow(&derefed_ty), &*to_ty) {
-                // Stop when constructor matches.
-                (ty_app!(from_ctor, st1), ty_app!(to_ctor, st2)) if from_ctor == to_ctor => {
-                    // It will not recurse to `coerce`.
-                    return self.table.unify_substs(st1, st2, 0);
-                }
-                _ => {
-                    if self.table.unify_inner_trivial(&derefed_ty, &to_ty, 0) {
-                        return true;
-                    }
-                }
+            let from_ty = self.resolve_ty_shallow(&derefed_ty);
+            // Stop when constructor matches.
+            if from_ty.equals_ctor(&to_ty) {
+                // It will not recurse to `coerce`.
+                return match (from_ty.substs(), to_ty.substs()) {
+                    (Some(st1), Some(st2)) => self.table.unify_substs(st1, st2, 0),
+                    (None, None) => true,
+                    _ => false,
+                };
+            } else if self.table.unify_inner_trivial(&derefed_ty, &to_ty, 0) {
+                return true;
             }
         }
 
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index 3fec0e43184..2369c9bef02 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -18,8 +18,8 @@ use crate::{
     primitive::{self, UintTy},
     traits::{FnTrait, InEnvironment},
     utils::{generics, variant_data, Generics},
-    ApplicationTy, Binders, CallableDefId, InferTy, Mutability, Obligation, OpaqueTyId, Rawness,
-    Scalar, Substs, TraitRef, Ty, TypeCtor,
+    Binders, CallableDefId, InferTy, Mutability, Obligation, OpaqueTyId, Rawness, Scalar, Substs,
+    TraitRef, Ty,
 };
 
 use super::{
@@ -82,10 +82,7 @@ impl<'a> InferenceContext<'a> {
             arg_tys.push(arg);
         }
         let parameters = param_builder.build();
-        let arg_ty = Ty::Apply(ApplicationTy {
-            ctor: TypeCtor::Tuple { cardinality: num_args as u16 },
-            parameters,
-        });
+        let arg_ty = Ty::Tuple { cardinality: num_args as u16, substs: parameters };
         let substs =
             Substs::build_for_generics(&generic_params).push(ty.clone()).push(arg_ty).build();
 
@@ -120,10 +117,7 @@ impl<'a> InferenceContext<'a> {
             Expr::Missing => Ty::Unknown,
             Expr::If { condition, then_branch, else_branch } => {
                 // if let is desugared to match, so this is always simple if
-                self.infer_expr(
-                    *condition,
-                    &Expectation::has_type(Ty::simple(TypeCtor::Scalar(Scalar::Bool))),
-                );
+                self.infer_expr(*condition, &Expectation::has_type(Ty::Scalar(Scalar::Bool)));
 
                 let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
                 let mut both_arms_diverge = Diverges::Always;
@@ -178,7 +172,7 @@ impl<'a> InferenceContext<'a> {
                 // existenail type AsyncBlockImplTrait<InnerType>: Future<Output = InnerType>
                 let inner_ty = self.infer_expr(*body, &Expectation::none());
                 let opaque_ty_id = OpaqueTyId::AsyncBlockTypeImplTrait(self.owner, *body);
-                Ty::apply_one(TypeCtor::OpaqueType(opaque_ty_id), inner_ty)
+                Ty::OpaqueType(opaque_ty_id, Substs::single(inner_ty))
             }
             Expr::Loop { body, label } => {
                 self.breakables.push(BreakableContext {
@@ -196,7 +190,7 @@ impl<'a> InferenceContext<'a> {
                 if ctxt.may_break {
                     ctxt.break_ty
                 } else {
-                    Ty::simple(TypeCtor::Never)
+                    Ty::Never
                 }
             }
             Expr::While { condition, body, label } => {
@@ -206,10 +200,7 @@ impl<'a> InferenceContext<'a> {
                     label: label.map(|label| self.body[label].name.clone()),
                 });
                 // while let is desugared to a match loop, so this is always simple while
-                self.infer_expr(
-                    *condition,
-                    &Expectation::has_type(Ty::simple(TypeCtor::Scalar(Scalar::Bool))),
-                );
+                self.infer_expr(*condition, &Expectation::has_type(Ty::Scalar(Scalar::Bool)));
                 self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
                 let _ctxt = self.breakables.pop().expect("breakable stack broken");
                 // the body may not run, so it diverging doesn't mean we diverge
@@ -256,12 +247,13 @@ impl<'a> InferenceContext<'a> {
                     None => self.table.new_type_var(),
                 };
                 sig_tys.push(ret_ty.clone());
-                let sig_ty = Ty::apply(
-                    TypeCtor::FnPtr { num_args: sig_tys.len() as u16 - 1, is_varargs: false },
-                    Substs(sig_tys.clone().into()),
-                );
+                let sig_ty = Ty::FnPtr {
+                    num_args: sig_tys.len() as u16 - 1,
+                    is_varargs: false,
+                    substs: Substs(sig_tys.clone().into()),
+                };
                 let closure_ty =
-                    Ty::apply_one(TypeCtor::Closure { def: self.owner, expr: tgt_expr }, sig_ty);
+                    Ty::Closure { def: self.owner, expr: tgt_expr, substs: Substs::single(sig_ty) };
 
                 // Eagerly try to relate the closure type with the expected
                 // type, otherwise we often won't have enough information to
@@ -312,11 +304,8 @@ impl<'a> InferenceContext<'a> {
             Expr::Match { expr, arms } => {
                 let input_ty = self.infer_expr(*expr, &Expectation::none());
 
-                let mut result_ty = if arms.is_empty() {
-                    Ty::simple(TypeCtor::Never)
-                } else {
-                    self.table.new_type_var()
-                };
+                let mut result_ty =
+                    if arms.is_empty() { Ty::Never } else { self.table.new_type_var() };
 
                 let matchee_diverges = self.diverges;
                 let mut all_arms_diverge = Diverges::Always;
@@ -327,7 +316,7 @@ impl<'a> InferenceContext<'a> {
                     if let Some(guard_expr) = arm.guard {
                         self.infer_expr(
                             guard_expr,
-                            &Expectation::has_type(Ty::simple(TypeCtor::Scalar(Scalar::Bool))),
+                            &Expectation::has_type(Ty::Scalar(Scalar::Bool)),
                         );
                     }
 
@@ -345,7 +334,7 @@ impl<'a> InferenceContext<'a> {
                 let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr);
                 self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or(Ty::Unknown)
             }
-            Expr::Continue { .. } => Ty::simple(TypeCtor::Never),
+            Expr::Continue { .. } => Ty::Never,
             Expr::Break { expr, label } => {
                 let val_ty = if let Some(expr) = expr {
                     self.infer_expr(*expr, &Expectation::none())
@@ -370,8 +359,7 @@ impl<'a> InferenceContext<'a> {
                         expr: tgt_expr,
                     });
                 }
-
-                Ty::simple(TypeCtor::Never)
+                Ty::Never
             }
             Expr::Return { expr } => {
                 if let Some(expr) = expr {
@@ -380,14 +368,14 @@ impl<'a> InferenceContext<'a> {
                     let unit = Ty::unit();
                     self.coerce(&unit, &self.return_ty.clone());
                 }
-                Ty::simple(TypeCtor::Never)
+                Ty::Never
             }
             Expr::Yield { expr } => {
                 // FIXME: track yield type for coercion
                 if let Some(expr) = expr {
                     self.infer_expr(*expr, &Expectation::none());
                 }
-                Ty::simple(TypeCtor::Never)
+                Ty::Never
             }
             Expr::RecordLit { path, fields, spread } => {
                 let (ty, def_id) = self.resolve_variant(path.as_ref());
@@ -397,7 +385,7 @@ impl<'a> InferenceContext<'a> {
 
                 self.unify(&ty, &expected.ty);
 
-                let substs = ty.substs().unwrap_or_else(Substs::empty);
+                let substs = ty.substs().cloned().unwrap_or_else(Substs::empty);
                 let field_types = def_id.map(|it| self.db.field_types(it)).unwrap_or_default();
                 let variant_data = def_id.map(|it| variant_data(self.db.upcast(), it));
                 for (field_idx, field) in fields.iter().enumerate() {
@@ -436,30 +424,23 @@ impl<'a> InferenceContext<'a> {
                     },
                 )
                 .find_map(|derefed_ty| match canonicalized.decanonicalize_ty(derefed_ty.value) {
-                    Ty::Apply(a_ty) => match a_ty.ctor {
-                        TypeCtor::Tuple { .. } => name
-                            .as_tuple_index()
-                            .and_then(|idx| a_ty.parameters.0.get(idx).cloned()),
-                        TypeCtor::Adt(AdtId::StructId(s)) => {
-                            self.db.struct_data(s).variant_data.field(name).map(|local_id| {
-                                let field = FieldId { parent: s.into(), local_id };
-                                self.write_field_resolution(tgt_expr, field);
-                                self.db.field_types(s.into())[field.local_id]
-                                    .clone()
-                                    .subst(&a_ty.parameters)
-                            })
-                        }
-                        TypeCtor::Adt(AdtId::UnionId(u)) => {
-                            self.db.union_data(u).variant_data.field(name).map(|local_id| {
-                                let field = FieldId { parent: u.into(), local_id };
-                                self.write_field_resolution(tgt_expr, field);
-                                self.db.field_types(u.into())[field.local_id]
-                                    .clone()
-                                    .subst(&a_ty.parameters)
-                            })
-                        }
-                        _ => None,
-                    },
+                    Ty::Tuple { substs, .. } => {
+                        name.as_tuple_index().and_then(|idx| substs.0.get(idx).cloned())
+                    }
+                    Ty::Adt(AdtId::StructId(s), parameters) => {
+                        self.db.struct_data(s).variant_data.field(name).map(|local_id| {
+                            let field = FieldId { parent: s.into(), local_id };
+                            self.write_field_resolution(tgt_expr, field);
+                            self.db.field_types(s.into())[field.local_id].clone().subst(&parameters)
+                        })
+                    }
+                    Ty::Adt(AdtId::UnionId(u), parameters) => {
+                        self.db.union_data(u).variant_data.field(name).map(|local_id| {
+                            let field = FieldId { parent: u.into(), local_id };
+                            self.write_field_resolution(tgt_expr, field);
+                            self.db.field_types(u.into())[field.local_id].clone().subst(&parameters)
+                        })
+                    }
                     _ => None,
                 })
                 .unwrap_or(Ty::Unknown);
@@ -497,19 +478,18 @@ impl<'a> InferenceContext<'a> {
                     Expectation::none()
                 };
                 let inner_ty = self.infer_expr_inner(*expr, &expectation);
-                let ty = match rawness {
-                    Rawness::RawPtr => TypeCtor::RawPtr(*mutability),
-                    Rawness::Ref => TypeCtor::Ref(*mutability),
-                };
-                Ty::apply_one(ty, inner_ty)
+                match rawness {
+                    Rawness::RawPtr => Ty::RawPtr(*mutability, Substs::single(inner_ty)),
+                    Rawness::Ref => Ty::Ref(*mutability, Substs::single(inner_ty)),
+                }
             }
             Expr::Box { expr } => {
                 let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
                 if let Some(box_) = self.resolve_boxed_box() {
-                    let mut sb = Substs::build_for_type_ctor(self.db, TypeCtor::Adt(box_));
+                    let mut sb = Substs::builder(generics(self.db.upcast(), box_.into()).len());
                     sb = sb.push(inner_ty);
                     sb = sb.fill(repeat_with(|| self.table.new_type_var()));
-                    Ty::apply(TypeCtor::Adt(box_), sb.build())
+                    Ty::Adt(box_, sb.build())
                 } else {
                     Ty::Unknown
                 }
@@ -539,14 +519,9 @@ impl<'a> InferenceContext<'a> {
                     UnaryOp::Neg => {
                         match &inner_ty {
                             // Fast path for builtins
-                            Ty::Apply(ApplicationTy {
-                                ctor: TypeCtor::Scalar(Scalar::Int(_)),
-                                ..
-                            })
-                            | Ty::Apply(ApplicationTy {
-                                ctor: TypeCtor::Scalar(Scalar::Float(_)),
-                                ..
-                            })
+                            Ty::Scalar(Scalar::Int(_))
+                            | Ty::Scalar(Scalar::Uint(_))
+                            | Ty::Scalar(Scalar::Float(_))
                             | Ty::Infer(InferTy::IntVar(..))
                             | Ty::Infer(InferTy::FloatVar(..)) => inner_ty,
                             // Otherwise we resolve via the std::ops::Neg trait
@@ -557,18 +532,9 @@ impl<'a> InferenceContext<'a> {
                     UnaryOp::Not => {
                         match &inner_ty {
                             // Fast path for builtins
-                            Ty::Apply(ApplicationTy {
-                                ctor: TypeCtor::Scalar(Scalar::Bool),
-                                ..
-                            })
-                            | Ty::Apply(ApplicationTy {
-                                ctor: TypeCtor::Scalar(Scalar::Int(_)),
-                                ..
-                            })
-                            | Ty::Apply(ApplicationTy {
-                                ctor: TypeCtor::Scalar(Scalar::Uint(_)),
-                                ..
-                            })
+                            Ty::Scalar(Scalar::Bool)
+                            | Ty::Scalar(Scalar::Int(_))
+                            | Ty::Scalar(Scalar::Uint(_))
                             | Ty::Infer(InferTy::IntVar(..)) => inner_ty,
                             // Otherwise we resolve via the std::ops::Not trait
                             _ => self
@@ -580,9 +546,7 @@ impl<'a> InferenceContext<'a> {
             Expr::BinaryOp { lhs, rhs, op } => match op {
                 Some(op) => {
                     let lhs_expectation = match op {
-                        BinaryOp::LogicOp(..) => {
-                            Expectation::has_type(Ty::simple(TypeCtor::Scalar(Scalar::Bool)))
-                        }
+                        BinaryOp::LogicOp(..) => Expectation::has_type(Ty::Scalar(Scalar::Bool)),
                         _ => Expectation::none(),
                     };
                     let lhs_ty = self.infer_expr(*lhs, &lhs_expectation);
@@ -613,31 +577,31 @@ impl<'a> InferenceContext<'a> {
                 let rhs_ty = rhs.map(|e| self.infer_expr(e, &rhs_expect));
                 match (range_type, lhs_ty, rhs_ty) {
                     (RangeOp::Exclusive, None, None) => match self.resolve_range_full() {
-                        Some(adt) => Ty::simple(TypeCtor::Adt(adt)),
+                        Some(adt) => Ty::Adt(adt, Substs::empty()),
                         None => Ty::Unknown,
                     },
                     (RangeOp::Exclusive, None, Some(ty)) => match self.resolve_range_to() {
-                        Some(adt) => Ty::apply_one(TypeCtor::Adt(adt), ty),
+                        Some(adt) => Ty::Adt(adt, Substs::single(ty)),
                         None => Ty::Unknown,
                     },
                     (RangeOp::Inclusive, None, Some(ty)) => {
                         match self.resolve_range_to_inclusive() {
-                            Some(adt) => Ty::apply_one(TypeCtor::Adt(adt), ty),
+                            Some(adt) => Ty::Adt(adt, Substs::single(ty)),
                             None => Ty::Unknown,
                         }
                     }
                     (RangeOp::Exclusive, Some(_), Some(ty)) => match self.resolve_range() {
-                        Some(adt) => Ty::apply_one(TypeCtor::Adt(adt), ty),
+                        Some(adt) => Ty::Adt(adt, Substs::single(ty)),
                         None => Ty::Unknown,
                     },
                     (RangeOp::Inclusive, Some(_), Some(ty)) => {
                         match self.resolve_range_inclusive() {
-                            Some(adt) => Ty::apply_one(TypeCtor::Adt(adt), ty),
+                            Some(adt) => Ty::Adt(adt, Substs::single(ty)),
                             None => Ty::Unknown,
                         }
                     }
                     (RangeOp::Exclusive, Some(ty), None) => match self.resolve_range_from() {
-                        Some(adt) => Ty::apply_one(TypeCtor::Adt(adt), ty),
+                        Some(adt) => Ty::Adt(adt, Substs::single(ty)),
                         None => Ty::Unknown,
                     },
                     (RangeOp::Inclusive, _, None) => Ty::Unknown,
@@ -671,7 +635,7 @@ impl<'a> InferenceContext<'a> {
             }
             Expr::Tuple { exprs } => {
                 let mut tys = match &expected.ty {
-                    ty_app!(TypeCtor::Tuple { .. }, st) => st
+                    Ty::Tuple { substs, .. } => substs
                         .iter()
                         .cloned()
                         .chain(repeat_with(|| self.table.new_type_var()))
@@ -684,15 +648,11 @@ impl<'a> InferenceContext<'a> {
                     self.infer_expr_coerce(*expr, &Expectation::has_type(ty.clone()));
                 }
 
-                Ty::apply(TypeCtor::Tuple { cardinality: tys.len() as u16 }, Substs(tys.into()))
+                Ty::Tuple { cardinality: tys.len() as u16, substs: Substs(tys.into()) }
             }
             Expr::Array(array) => {
                 let elem_ty = match &expected.ty {
-                    // FIXME: remove when https://github.com/rust-lang/rust/issues/80501 is fixed
-                    #[allow(unreachable_patterns)]
-                    ty_app!(TypeCtor::Array, st) | ty_app!(TypeCtor::Slice, st) => {
-                        st.as_single().clone()
-                    }
+                    Ty::Array(st) | Ty::Slice(st) => st.as_single().clone(),
                     _ => self.table.new_type_var(),
                 };
 
@@ -709,42 +669,38 @@ impl<'a> InferenceContext<'a> {
                         );
                         self.infer_expr(
                             *repeat,
-                            &Expectation::has_type(Ty::simple(TypeCtor::Scalar(Scalar::Uint(
-                                UintTy::Usize,
-                            )))),
+                            &Expectation::has_type(Ty::Scalar(Scalar::Uint(UintTy::Usize))),
                         );
                     }
                 }
 
-                Ty::apply_one(TypeCtor::Array, elem_ty)
+                Ty::Array(Substs::single(elem_ty))
             }
             Expr::Literal(lit) => match lit {
-                Literal::Bool(..) => Ty::simple(TypeCtor::Scalar(Scalar::Bool)),
-                Literal::String(..) => {
-                    Ty::apply_one(TypeCtor::Ref(Mutability::Shared), Ty::simple(TypeCtor::Str))
-                }
+                Literal::Bool(..) => Ty::Scalar(Scalar::Bool),
+                Literal::String(..) => Ty::Ref(Mutability::Shared, Substs::single(Ty::Str)),
                 Literal::ByteString(..) => {
-                    let byte_type = Ty::simple(TypeCtor::Scalar(Scalar::Uint(UintTy::U8)));
-                    let array_type = Ty::apply_one(TypeCtor::Array, byte_type);
-                    Ty::apply_one(TypeCtor::Ref(Mutability::Shared), array_type)
+                    let byte_type = Ty::Scalar(Scalar::Uint(UintTy::U8));
+                    let array_type = Ty::Array(Substs::single(byte_type));
+                    Ty::Ref(Mutability::Shared, Substs::single(array_type))
                 }
-                Literal::Char(..) => Ty::simple(TypeCtor::Scalar(Scalar::Char)),
+                Literal::Char(..) => Ty::Scalar(Scalar::Char),
                 Literal::Int(_v, ty) => match ty {
-                    Some(int_ty) => Ty::simple(TypeCtor::Scalar(Scalar::Int(
-                        primitive::int_ty_from_builtin(*int_ty),
-                    ))),
+                    Some(int_ty) => {
+                        Ty::Scalar(Scalar::Int(primitive::int_ty_from_builtin(*int_ty)))
+                    }
                     None => self.table.new_integer_var(),
                 },
                 Literal::Uint(_v, ty) => match ty {
-                    Some(int_ty) => Ty::simple(TypeCtor::Scalar(Scalar::Uint(
-                        primitive::uint_ty_from_builtin(*int_ty),
-                    ))),
+                    Some(int_ty) => {
+                        Ty::Scalar(Scalar::Uint(primitive::uint_ty_from_builtin(*int_ty)))
+                    }
                     None => self.table.new_integer_var(),
                 },
                 Literal::Float(_v, ty) => match ty {
-                    Some(float_ty) => Ty::simple(TypeCtor::Scalar(Scalar::Float(
-                        primitive::float_ty_from_builtin(*float_ty),
-                    ))),
+                    Some(float_ty) => {
+                        Ty::Scalar(Scalar::Float(primitive::float_ty_from_builtin(*float_ty)))
+                    }
                     None => self.table.new_float_var(),
                 },
             },
@@ -857,7 +813,7 @@ impl<'a> InferenceContext<'a> {
         // Apply autoref so the below unification works correctly
         // FIXME: return correct autorefs from lookup_method
         let actual_receiver_ty = match expected_receiver_ty.as_reference() {
-            Some((_, mutability)) => Ty::apply_one(TypeCtor::Ref(mutability), derefed_receiver_ty),
+            Some((_, mutability)) => Ty::Ref(mutability, Substs::single(derefed_receiver_ty)),
             _ => derefed_receiver_ty,
         };
         self.unify(&expected_receiver_ty, &actual_receiver_ty);
@@ -934,30 +890,26 @@ impl<'a> InferenceContext<'a> {
     }
 
     fn register_obligations_for_call(&mut self, callable_ty: &Ty) {
-        if let Ty::Apply(a_ty) = callable_ty {
-            if let TypeCtor::FnDef(def) = a_ty.ctor {
-                let generic_predicates = self.db.generic_predicates(def.into());
-                for predicate in generic_predicates.iter() {
-                    let predicate = predicate.clone().subst(&a_ty.parameters);
-                    if let Some(obligation) = Obligation::from_predicate(predicate) {
-                        self.obligations.push(obligation);
+        if let &Ty::FnDef(def, ref parameters) = callable_ty {
+            let generic_predicates = self.db.generic_predicates(def.into());
+            for predicate in generic_predicates.iter() {
+                let predicate = predicate.clone().subst(parameters);
+                if let Some(obligation) = Obligation::from_predicate(predicate) {
+                    self.obligations.push(obligation);
+                }
+            }
+            // add obligation for trait implementation, if this is a trait method
+            match def {
+                CallableDefId::FunctionId(f) => {
+                    if let AssocContainerId::TraitId(trait_) = f.lookup(self.db.upcast()).container
+                    {
+                        // construct a TraitDef
+                        let substs =
+                            parameters.prefix(generics(self.db.upcast(), trait_.into()).len());
+                        self.obligations.push(Obligation::Trait(TraitRef { trait_, substs }));
                     }
                 }
-                // add obligation for trait implementation, if this is a trait method
-                match def {
-                    CallableDefId::FunctionId(f) => {
-                        if let AssocContainerId::TraitId(trait_) =
-                            f.lookup(self.db.upcast()).container
-                        {
-                            // construct a TraitDef
-                            let substs = a_ty
-                                .parameters
-                                .prefix(generics(self.db.upcast(), trait_.into()).len());
-                            self.obligations.push(Obligation::Trait(TraitRef { trait_, substs }));
-                        }
-                    }
-                    CallableDefId::StructId(_) | CallableDefId::EnumVariantId(_) => {}
-                }
+                CallableDefId::StructId(_) | CallableDefId::EnumVariantId(_) => {}
             }
         }
     }
diff --git a/crates/hir_ty/src/infer/pat.rs b/crates/hir_ty/src/infer/pat.rs
index d974f805b5b..e96e08c3cf8 100644
--- a/crates/hir_ty/src/infer/pat.rs
+++ b/crates/hir_ty/src/infer/pat.rs
@@ -13,7 +13,7 @@ use hir_expand::name::Name;
 use test_utils::mark;
 
 use super::{BindingMode, Expectation, InferenceContext};
-use crate::{utils::variant_data, Substs, Ty, TypeCtor};
+use crate::{utils::variant_data, Substs, Ty};
 
 impl<'a> InferenceContext<'a> {
     fn infer_tuple_struct_pat(
@@ -32,7 +32,7 @@ impl<'a> InferenceContext<'a> {
         }
         self.unify(&ty, expected);
 
-        let substs = ty.substs().unwrap_or_else(Substs::empty);
+        let substs = ty.substs().cloned().unwrap_or_else(Substs::empty);
 
         let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default();
         let (pre, post) = match ellipsis {
@@ -71,7 +71,7 @@ impl<'a> InferenceContext<'a> {
 
         self.unify(&ty, expected);
 
-        let substs = ty.substs().unwrap_or_else(Substs::empty);
+        let substs = ty.substs().cloned().unwrap_or_else(Substs::empty);
 
         let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default();
         for subpat in subpats {
@@ -138,10 +138,7 @@ impl<'a> InferenceContext<'a> {
                 inner_tys.extend(expectations_iter.by_ref().take(n_uncovered_patterns).cloned());
                 inner_tys.extend(post.iter().zip(expectations_iter).map(infer_pat));
 
-                Ty::apply(
-                    TypeCtor::Tuple { cardinality: inner_tys.len() as u16 },
-                    Substs(inner_tys.into()),
-                )
+                Ty::Tuple { cardinality: inner_tys.len() as u16, substs: Substs(inner_tys.into()) }
             }
             Pat::Or(ref pats) => {
                 if let Some((first_pat, rest)) = pats.split_first() {
@@ -165,7 +162,7 @@ impl<'a> InferenceContext<'a> {
                     _ => &Ty::Unknown,
                 };
                 let subty = self.infer_pat(*pat, expectation, default_bm);
-                Ty::apply_one(TypeCtor::Ref(*mutability), subty)
+                Ty::Ref(*mutability, Substs::single(subty))
             }
             Pat::TupleStruct { path: p, args: subpats, ellipsis } => self.infer_tuple_struct_pat(
                 p.as_ref(),
@@ -198,7 +195,7 @@ impl<'a> InferenceContext<'a> {
 
                 let bound_ty = match mode {
                     BindingMode::Ref(mutability) => {
-                        Ty::apply_one(TypeCtor::Ref(mutability), inner_ty.clone())
+                        Ty::Ref(mutability, Substs::single(inner_ty.clone()))
                     }
                     BindingMode::Move => inner_ty.clone(),
                 };
@@ -207,17 +204,17 @@ impl<'a> InferenceContext<'a> {
                 return inner_ty;
             }
             Pat::Slice { prefix, slice, suffix } => {
-                let (container_ty, elem_ty) = match &expected {
-                    ty_app!(TypeCtor::Array, st) => (TypeCtor::Array, st.as_single().clone()),
-                    ty_app!(TypeCtor::Slice, st) => (TypeCtor::Slice, st.as_single().clone()),
-                    _ => (TypeCtor::Slice, Ty::Unknown),
+                let (container_ty, elem_ty): (fn(_) -> _, _) = match &expected {
+                    Ty::Array(st) => (Ty::Array, st.as_single().clone()),
+                    Ty::Slice(st) => (Ty::Slice, st.as_single().clone()),
+                    _ => (Ty::Slice, Ty::Unknown),
                 };
 
                 for pat_id in prefix.iter().chain(suffix) {
                     self.infer_pat(*pat_id, &elem_ty, default_bm);
                 }
 
-                let pat_ty = Ty::apply_one(container_ty, elem_ty);
+                let pat_ty = container_ty(Substs::single(elem_ty));
                 if let Some(slice_pat_id) = slice {
                     self.infer_pat(*slice_pat_id, &pat_ty, default_bm);
                 }
@@ -239,7 +236,7 @@ impl<'a> InferenceContext<'a> {
                     };
 
                     let inner_ty = self.infer_pat(*inner, inner_expected, default_bm);
-                    Ty::apply_one(TypeCtor::Adt(box_adt), inner_ty)
+                    Ty::Adt(box_adt, Substs::single(inner_ty))
                 }
                 None => Ty::Unknown,
             },
diff --git a/crates/hir_ty/src/infer/unify.rs b/crates/hir_ty/src/infer/unify.rs
index 57eb8cede77..2852ad5bf72 100644
--- a/crates/hir_ty/src/infer/unify.rs
+++ b/crates/hir_ty/src/infer/unify.rs
@@ -9,7 +9,7 @@ use test_utils::mark;
 use super::{InferenceContext, Obligation};
 use crate::{
     BoundVar, Canonical, DebruijnIndex, GenericPredicate, InEnvironment, InferTy, Scalar, Substs,
-    Ty, TyKind, TypeCtor, TypeWalk,
+    Ty, TyKind, TypeWalk,
 };
 
 impl<'a> InferenceContext<'a> {
@@ -257,12 +257,14 @@ impl InferenceTable {
         // try to resolve type vars first
         let ty1 = self.resolve_ty_shallow(ty1);
         let ty2 = self.resolve_ty_shallow(ty2);
-        match (&*ty1, &*ty2) {
-            (Ty::Apply(a_ty1), Ty::Apply(a_ty2)) if a_ty1.ctor == a_ty2.ctor => {
-                self.unify_substs(&a_ty1.parameters, &a_ty2.parameters, depth + 1)
+        if ty1.equals_ctor(&ty2) {
+            match (ty1.substs(), ty2.substs()) {
+                (Some(st1), Some(st2)) => self.unify_substs(st1, st2, depth + 1),
+                (None, None) => true,
+                _ => false,
             }
-
-            _ => self.unify_inner_trivial(&ty1, &ty2, depth),
+        } else {
+            self.unify_inner_trivial(&ty1, &ty2, depth)
         }
     }
 
@@ -300,24 +302,12 @@ impl InferenceTable {
             | (other, Ty::Infer(InferTy::TypeVar(tv)))
             | (Ty::Infer(InferTy::MaybeNeverTypeVar(tv)), other)
             | (other, Ty::Infer(InferTy::MaybeNeverTypeVar(tv)))
-            | (Ty::Infer(InferTy::IntVar(tv)), other @ ty_app!(TypeCtor::Scalar(Scalar::Int(_))))
-            | (other @ ty_app!(TypeCtor::Scalar(Scalar::Int(_))), Ty::Infer(InferTy::IntVar(tv)))
-            | (
-                Ty::Infer(InferTy::IntVar(tv)),
-                other @ ty_app!(TypeCtor::Scalar(Scalar::Uint(_))),
-            )
-            | (
-                other @ ty_app!(TypeCtor::Scalar(Scalar::Uint(_))),
-                Ty::Infer(InferTy::IntVar(tv)),
-            )
-            | (
-                Ty::Infer(InferTy::FloatVar(tv)),
-                other @ ty_app!(TypeCtor::Scalar(Scalar::Float(_))),
-            )
-            | (
-                other @ ty_app!(TypeCtor::Scalar(Scalar::Float(_))),
-                Ty::Infer(InferTy::FloatVar(tv)),
-            ) => {
+            | (Ty::Infer(InferTy::IntVar(tv)), other @ Ty::Scalar(Scalar::Int(_)))
+            | (other @ Ty::Scalar(Scalar::Int(_)), Ty::Infer(InferTy::IntVar(tv)))
+            | (Ty::Infer(InferTy::IntVar(tv)), other @ Ty::Scalar(Scalar::Uint(_)))
+            | (other @ Ty::Scalar(Scalar::Uint(_)), Ty::Infer(InferTy::IntVar(tv)))
+            | (Ty::Infer(InferTy::FloatVar(tv)), other @ Ty::Scalar(Scalar::Float(_)))
+            | (other @ Ty::Scalar(Scalar::Float(_)), Ty::Infer(InferTy::FloatVar(tv))) => {
                 // the type var is unknown since we tried to resolve it
                 self.var_unification_table.union_value(*tv, TypeVarValue::Known(other.clone()));
                 true
diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs
index 676519594b4..5cbb9a3cc35 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -25,7 +25,7 @@ mod test_db;
 
 use std::{iter, mem, ops::Deref, sync::Arc};
 
-use base_db::{salsa, CrateId};
+use base_db::salsa;
 use hir_def::{
     builtin_type::BuiltinType,
     expr::ExprId,
@@ -57,192 +57,6 @@ pub enum Lifetime {
     Static,
 }
 
-/// A type constructor or type name: this might be something like the primitive
-/// type `bool`, a struct like `Vec`, or things like function pointers or
-/// tuples.
-#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
-pub enum TypeCtor {
-    /// a scalar type like `bool` or `u32`
-    Scalar(Scalar),
-
-    /// Structures, enumerations and unions.
-    Adt(AdtId),
-
-    /// The pointee of a string slice. Written as `str`.
-    Str,
-
-    /// The pointee of an array slice.  Written as `[T]`.
-    Slice,
-
-    /// An array with the given length. Written as `[T; n]`.
-    Array,
-
-    /// A raw pointer. Written as `*mut T` or `*const T`
-    RawPtr(Mutability),
-
-    /// A reference; a pointer with an associated lifetime. Written as
-    /// `&'a mut T` or `&'a T`.
-    Ref(Mutability),
-
-    /// The anonymous type of a function declaration/definition. Each
-    /// function has a unique type, which is output (for a function
-    /// named `foo` returning an `i32`) as `fn() -> i32 {foo}`.
-    ///
-    /// This includes tuple struct / enum variant constructors as well.
-    ///
-    /// For example the type of `bar` here:
-    ///
-    /// ```
-    /// fn foo() -> i32 { 1 }
-    /// let bar = foo; // bar: fn() -> i32 {foo}
-    /// ```
-    FnDef(CallableDefId),
-
-    /// A pointer to a function.  Written as `fn() -> i32`.
-    ///
-    /// For example the type of `bar` here:
-    ///
-    /// ```
-    /// fn foo() -> i32 { 1 }
-    /// let bar: fn() -> i32 = foo;
-    /// ```
-    // FIXME make this a Ty variant like in Chalk
-    FnPtr { num_args: u16, is_varargs: bool },
-
-    /// The never type `!`.
-    Never,
-
-    /// A tuple type.  For example, `(i32, bool)`.
-    Tuple { cardinality: u16 },
-
-    /// Represents an associated item like `Iterator::Item`.  This is used
-    /// when we have tried to normalize a projection like `T::Item` but
-    /// couldn't find a better representation.  In that case, we generate
-    /// an **application type** like `(Iterator::Item)<T>`.
-    AssociatedType(TypeAliasId),
-
-    /// This represents a placeholder for an opaque type in situations where we
-    /// don't know the hidden type (i.e. currently almost always). This is
-    /// analogous to the `AssociatedType` type constructor.
-    /// It is also used as the type of async block, with one type parameter
-    /// representing the Future::Output type.
-    OpaqueType(OpaqueTyId),
-
-    /// Represents a foreign type declared in external blocks.
-    ForeignType(TypeAliasId),
-
-    /// The type of a specific closure.
-    ///
-    /// The closure signature is stored in a `FnPtr` type in the first type
-    /// parameter.
-    Closure { def: DefWithBodyId, expr: ExprId },
-}
-
-impl TypeCtor {
-    pub fn num_ty_params(self, db: &dyn HirDatabase) -> usize {
-        match self {
-            TypeCtor::Scalar(_)
-            | TypeCtor::Str
-            | TypeCtor::Never => 0,
-            TypeCtor::Slice
-            | TypeCtor::Array
-            | TypeCtor::RawPtr(_)
-            | TypeCtor::Ref(_)
-            | TypeCtor::Closure { .. } // 1 param representing the signature of the closure
-            => 1,
-            TypeCtor::Adt(adt) => {
-                let generic_params = generics(db.upcast(), adt.into());
-                generic_params.len()
-            }
-            TypeCtor::FnDef(callable) => {
-                let generic_params = generics(db.upcast(), callable.into());
-                generic_params.len()
-            }
-            TypeCtor::AssociatedType(type_alias) => {
-                let generic_params = generics(db.upcast(), type_alias.into());
-                generic_params.len()
-            }
-            TypeCtor::ForeignType(type_alias) => {
-                let generic_params = generics(db.upcast(), type_alias.into());
-                generic_params.len()
-            }
-            TypeCtor::OpaqueType(opaque_ty_id) => {
-                match opaque_ty_id {
-                    OpaqueTyId::ReturnTypeImplTrait(func, _) => {
-                        let generic_params = generics(db.upcast(), func.into());
-                        generic_params.len()
-                    }
-                    // 1 param representing Future::Output type.
-                    OpaqueTyId::AsyncBlockTypeImplTrait(..) => 1,
-                }
-            }
-            TypeCtor::FnPtr { num_args, is_varargs: _ } => num_args as usize + 1,
-            TypeCtor::Tuple { cardinality } => cardinality as usize,
-        }
-    }
-
-    pub fn krate(self, db: &dyn HirDatabase) -> Option<CrateId> {
-        match self {
-            TypeCtor::Scalar(_)
-            | TypeCtor::Str
-            | TypeCtor::Never
-            | TypeCtor::Slice
-            | TypeCtor::Array
-            | TypeCtor::RawPtr(_)
-            | TypeCtor::Ref(_)
-            | TypeCtor::FnPtr { .. }
-            | TypeCtor::Tuple { .. } => None,
-            // Closure's krate is irrelevant for coherence I would think?
-            TypeCtor::Closure { .. } => None,
-            TypeCtor::Adt(adt) => Some(adt.module(db.upcast()).krate()),
-            TypeCtor::FnDef(callable) => Some(callable.krate(db)),
-            TypeCtor::AssociatedType(type_alias) => {
-                Some(type_alias.lookup(db.upcast()).module(db.upcast()).krate())
-            }
-            TypeCtor::ForeignType(type_alias) => {
-                Some(type_alias.lookup(db.upcast()).module(db.upcast()).krate())
-            }
-            TypeCtor::OpaqueType(opaque_ty_id) => match opaque_ty_id {
-                OpaqueTyId::ReturnTypeImplTrait(func, _) => {
-                    Some(func.lookup(db.upcast()).module(db.upcast()).krate())
-                }
-                OpaqueTyId::AsyncBlockTypeImplTrait(def, _) => {
-                    Some(def.module(db.upcast()).krate())
-                }
-            },
-        }
-    }
-
-    pub fn as_generic_def(self) -> Option<GenericDefId> {
-        match self {
-            TypeCtor::Scalar(_)
-            | TypeCtor::Str
-            | TypeCtor::Never
-            | TypeCtor::Slice
-            | TypeCtor::Array
-            | TypeCtor::RawPtr(_)
-            | TypeCtor::Ref(_)
-            | TypeCtor::FnPtr { .. }
-            | TypeCtor::Tuple { .. }
-            | TypeCtor::Closure { .. } => None,
-            TypeCtor::Adt(adt) => Some(adt.into()),
-            TypeCtor::FnDef(callable) => Some(callable.into()),
-            TypeCtor::AssociatedType(type_alias) => Some(type_alias.into()),
-            TypeCtor::ForeignType(type_alias) => Some(type_alias.into()),
-            TypeCtor::OpaqueType(_impl_trait_id) => None,
-        }
-    }
-}
-
-/// A nominal type with (maybe 0) type parameters. This might be a primitive
-/// type like `bool`, a struct, tuple, function pointer, reference or
-/// several other things.
-#[derive(Clone, PartialEq, Eq, Debug, Hash)]
-pub struct ApplicationTy {
-    pub ctor: TypeCtor,
-    pub parameters: Substs,
-}
-
 #[derive(Clone, PartialEq, Eq, Debug, Hash)]
 pub struct OpaqueTy {
     pub opaque_ty_id: OpaqueTyId,
@@ -293,10 +107,80 @@ impl TypeWalk for ProjectionTy {
 /// This should be cheap to clone.
 #[derive(Clone, PartialEq, Eq, Debug, Hash)]
 pub enum Ty {
-    /// A nominal type with (maybe 0) type parameters. This might be a primitive
-    /// type like `bool`, a struct, tuple, function pointer, reference or
-    /// several other things.
-    Apply(ApplicationTy),
+    /// Structures, enumerations and unions.
+    Adt(AdtId, Substs),
+
+    /// Represents an associated item like `Iterator::Item`.  This is used
+    /// when we have tried to normalize a projection like `T::Item` but
+    /// couldn't find a better representation.  In that case, we generate
+    /// an **application type** like `(Iterator::Item)<T>`.
+    AssociatedType(TypeAliasId, Substs),
+
+    /// a scalar type like `bool` or `u32`
+    Scalar(Scalar),
+
+    /// A tuple type.  For example, `(i32, bool)`.
+    Tuple { cardinality: u16, substs: Substs },
+
+    /// An array with the given length. Written as `[T; n]`.
+    Array(Substs),
+
+    /// The pointee of an array slice.  Written as `[T]`.
+    Slice(Substs),
+
+    /// A raw pointer. Written as `*mut T` or `*const T`
+    RawPtr(Mutability, Substs),
+
+    /// A reference; a pointer with an associated lifetime. Written as
+    /// `&'a mut T` or `&'a T`.
+    Ref(Mutability, Substs),
+
+    /// This represents a placeholder for an opaque type in situations where we
+    /// don't know the hidden type (i.e. currently almost always). This is
+    /// analogous to the `AssociatedType` type constructor.
+    /// It is also used as the type of async block, with one type parameter
+    /// representing the Future::Output type.
+    OpaqueType(OpaqueTyId, Substs),
+
+    /// The anonymous type of a function declaration/definition. Each
+    /// function has a unique type, which is output (for a function
+    /// named `foo` returning an `i32`) as `fn() -> i32 {foo}`.
+    ///
+    /// This includes tuple struct / enum variant constructors as well.
+    ///
+    /// For example the type of `bar` here:
+    ///
+    /// ```
+    /// fn foo() -> i32 { 1 }
+    /// let bar = foo; // bar: fn() -> i32 {foo}
+    /// ```
+    FnDef(CallableDefId, Substs),
+
+    /// The pointee of a string slice. Written as `str`.
+    Str,
+
+    /// The never type `!`.
+    Never,
+
+    /// The type of a specific closure.
+    ///
+    /// The closure signature is stored in a `FnPtr` type in the first type
+    /// parameter.
+    Closure { def: DefWithBodyId, expr: ExprId, substs: Substs },
+
+    /// Represents a foreign type declared in external blocks.
+    ForeignType(TypeAliasId, Substs),
+
+    /// A pointer to a function.  Written as `fn() -> i32`.
+    ///
+    /// For example the type of `bar` here:
+    ///
+    /// ```
+    /// fn foo() -> i32 { 1 }
+    /// let bar: fn() -> i32 = foo;
+    /// ```
+    // FIXME make this a Ty variant like in Chalk
+    FnPtr { num_args: u16, is_varargs: bool, substs: Substs },
 
     /// A "projection" type corresponds to an (unnormalized)
     /// projection like `<P0 as Trait<P1..Pn>>::Foo`. Note that the
@@ -420,10 +304,6 @@ impl Substs {
         Substs::builder(generic_params.len())
     }
 
-    pub fn build_for_type_ctor(db: &dyn HirDatabase, type_ctor: TypeCtor) -> SubstsBuilder {
-        Substs::builder(type_ctor.num_ty_params(db))
-    }
-
     fn builder(param_count: usize) -> SubstsBuilder {
         SubstsBuilder { vec: Vec::with_capacity(param_count), param_count }
     }
@@ -701,54 +581,42 @@ impl TypeWalk for FnSig {
 }
 
 impl Ty {
-    pub fn simple(ctor: TypeCtor) -> Ty {
-        Ty::Apply(ApplicationTy { ctor, parameters: Substs::empty() })
-    }
-    pub fn apply_one(ctor: TypeCtor, param: Ty) -> Ty {
-        Ty::Apply(ApplicationTy { ctor, parameters: Substs::single(param) })
-    }
-    pub fn apply(ctor: TypeCtor, parameters: Substs) -> Ty {
-        Ty::Apply(ApplicationTy { ctor, parameters })
-    }
     pub fn unit() -> Self {
-        Ty::apply(TypeCtor::Tuple { cardinality: 0 }, Substs::empty())
+        Ty::Tuple { cardinality: 0, substs: Substs::empty() }
     }
+
     pub fn fn_ptr(sig: FnSig) -> Self {
-        Ty::apply(
-            TypeCtor::FnPtr { num_args: sig.params().len() as u16, is_varargs: sig.is_varargs },
-            Substs(sig.params_and_return),
-        )
+        Ty::FnPtr {
+            num_args: sig.params().len() as u16,
+            is_varargs: sig.is_varargs,
+            substs: Substs(sig.params_and_return),
+        }
     }
+
     pub fn builtin(builtin: BuiltinType) -> Self {
-        Ty::simple(match builtin {
-            BuiltinType::Char => TypeCtor::Scalar(Scalar::Char),
-            BuiltinType::Bool => TypeCtor::Scalar(Scalar::Bool),
-            BuiltinType::Str => TypeCtor::Str,
-            BuiltinType::Int(t) => TypeCtor::Scalar(Scalar::Int(primitive::int_ty_from_builtin(t))),
-            BuiltinType::Uint(t) => {
-                TypeCtor::Scalar(Scalar::Uint(primitive::uint_ty_from_builtin(t)))
-            }
-            BuiltinType::Float(t) => {
-                TypeCtor::Scalar(Scalar::Float(primitive::float_ty_from_builtin(t)))
-            }
-        })
+        match builtin {
+            BuiltinType::Char => Ty::Scalar(Scalar::Char),
+            BuiltinType::Bool => Ty::Scalar(Scalar::Bool),
+            BuiltinType::Str => Ty::Str,
+            BuiltinType::Int(t) => Ty::Scalar(Scalar::Int(primitive::int_ty_from_builtin(t))),
+            BuiltinType::Uint(t) => Ty::Scalar(Scalar::Uint(primitive::uint_ty_from_builtin(t))),
+            BuiltinType::Float(t) => Ty::Scalar(Scalar::Float(primitive::float_ty_from_builtin(t))),
+        }
     }
 
     pub fn as_reference(&self) -> Option<(&Ty, Mutability)> {
         match self {
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::Ref(mutability), parameters }) => {
-                Some((parameters.as_single(), *mutability))
-            }
+            Ty::Ref(mutability, parameters) => Some((parameters.as_single(), *mutability)),
             _ => None,
         }
     }
 
     pub fn as_reference_or_ptr(&self) -> Option<(&Ty, Rawness, Mutability)> {
         match self {
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::Ref(mutability), parameters }) => {
+            Ty::Ref(mutability, parameters) => {
                 Some((parameters.as_single(), Rawness::Ref, *mutability))
             }
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(mutability), parameters }) => {
+            Ty::RawPtr(mutability, parameters) => {
                 Some((parameters.as_single(), Rawness::RawPtr, *mutability))
             }
             _ => None,
@@ -758,7 +626,7 @@ impl Ty {
     pub fn strip_references(&self) -> &Ty {
         let mut t: &Ty = self;
 
-        while let Ty::Apply(ApplicationTy { ctor: TypeCtor::Ref(_mutability), parameters }) = t {
+        while let Ty::Ref(_mutability, parameters) = t {
             t = parameters.as_single();
         }
 
@@ -767,30 +635,64 @@ impl Ty {
 
     pub fn as_adt(&self) -> Option<(AdtId, &Substs)> {
         match self {
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(adt_def), parameters }) => {
-                Some((*adt_def, parameters))
-            }
+            Ty::Adt(adt_def, parameters) => Some((*adt_def, parameters)),
             _ => None,
         }
     }
 
     pub fn as_tuple(&self) -> Option<&Substs> {
         match self {
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::Tuple { .. }, parameters }) => {
-                Some(parameters)
-            }
+            Ty::Tuple { substs: parameters, .. } => Some(parameters),
+            _ => None,
+        }
+    }
+
+    pub fn as_generic_def(&self) -> Option<GenericDefId> {
+        match *self {
+            Ty::Adt(adt, ..) => Some(adt.into()),
+            Ty::FnDef(callable, ..) => Some(callable.into()),
+            Ty::AssociatedType(type_alias, ..) => Some(type_alias.into()),
+            Ty::ForeignType(type_alias, ..) => Some(type_alias.into()),
             _ => None,
         }
     }
 
     pub fn is_never(&self) -> bool {
-        matches!(self, Ty::Apply(ApplicationTy { ctor: TypeCtor::Never, .. }))
+        matches!(self, Ty::Never)
     }
 
     pub fn is_unknown(&self) -> bool {
         matches!(self, Ty::Unknown)
     }
 
+    pub fn equals_ctor(&self, other: &Ty) -> bool {
+        match (self, other) {
+            (Ty::Adt(adt, ..), Ty::Adt(adt2, ..)) => adt == adt2,
+            (Ty::Slice(_), Ty::Slice(_)) | (Ty::Array(_), Ty::Array(_)) => true,
+            (Ty::FnDef(def_id, ..), Ty::FnDef(def_id2, ..)) => def_id == def_id2,
+            (Ty::OpaqueType(ty_id, ..), Ty::OpaqueType(ty_id2, ..)) => ty_id == ty_id2,
+            (Ty::AssociatedType(ty_id, ..), Ty::AssociatedType(ty_id2, ..))
+            | (Ty::ForeignType(ty_id, ..), Ty::ForeignType(ty_id2, ..)) => ty_id == ty_id2,
+            (Ty::Closure { def, expr, .. }, Ty::Closure { def: def2, expr: expr2, .. }) => {
+                expr == expr2 && def == def2
+            }
+            (Ty::Ref(mutability, ..), Ty::Ref(mutability2, ..))
+            | (Ty::RawPtr(mutability, ..), Ty::RawPtr(mutability2, ..)) => {
+                mutability == mutability2
+            }
+            (
+                Ty::FnPtr { num_args, is_varargs, .. },
+                Ty::FnPtr { num_args: num_args2, is_varargs: is_varargs2, .. },
+            ) => num_args == num_args2 && is_varargs == is_varargs2,
+            (Ty::Tuple { cardinality, .. }, Ty::Tuple { cardinality: cardinality2, .. }) => {
+                cardinality == cardinality2
+            }
+            (Ty::Str, Ty::Str) | (Ty::Never, Ty::Never) => true,
+            (Ty::Scalar(scalar), Ty::Scalar(scalar2)) => scalar == scalar2,
+            _ => false,
+        }
+    }
+
     /// If this is a `dyn Trait` type, this returns the `Trait` part.
     pub fn dyn_trait_ref(&self) -> Option<&TraitRef> {
         match self {
@@ -809,41 +711,32 @@ impl Ty {
 
     fn builtin_deref(&self) -> Option<Ty> {
         match self {
-            Ty::Apply(a_ty) => match a_ty.ctor {
-                TypeCtor::Ref(..) => Some(Ty::clone(a_ty.parameters.as_single())),
-                TypeCtor::RawPtr(..) => Some(Ty::clone(a_ty.parameters.as_single())),
-                _ => None,
-            },
+            Ty::Ref(.., parameters) => Some(Ty::clone(parameters.as_single())),
+            Ty::RawPtr(.., parameters) => Some(Ty::clone(parameters.as_single())),
             _ => None,
         }
     }
 
     pub fn as_fn_def(&self) -> Option<FunctionId> {
         match self {
-            &Ty::Apply(ApplicationTy {
-                ctor: TypeCtor::FnDef(CallableDefId::FunctionId(func)),
-                ..
-            }) => Some(func),
+            &Ty::FnDef(CallableDefId::FunctionId(func), ..) => Some(func),
             _ => None,
         }
     }
 
     pub fn callable_sig(&self, db: &dyn HirDatabase) -> Option<FnSig> {
         match self {
-            Ty::Apply(a_ty) => match a_ty.ctor {
-                TypeCtor::FnPtr { is_varargs, .. } => {
-                    Some(FnSig::from_fn_ptr_substs(&a_ty.parameters, is_varargs))
-                }
-                TypeCtor::FnDef(def) => {
-                    let sig = db.callable_item_signature(def);
-                    Some(sig.subst(&a_ty.parameters))
-                }
-                TypeCtor::Closure { .. } => {
-                    let sig_param = &a_ty.parameters[0];
-                    sig_param.callable_sig(db)
-                }
-                _ => None,
-            },
+            &Ty::FnPtr { is_varargs, substs: ref parameters, .. } => {
+                Some(FnSig::from_fn_ptr_substs(&parameters, is_varargs))
+            }
+            &Ty::FnDef(def, ref parameters) => {
+                let sig = db.callable_item_signature(def);
+                Some(sig.subst(&parameters))
+            }
+            Ty::Closure { substs: parameters, .. } => {
+                let sig_param = &parameters[0];
+                sig_param.callable_sig(db)
+            }
             _ => None,
         }
     }
@@ -852,28 +745,71 @@ impl Ty {
     /// the `Substs` for these type parameters with the given ones. (So e.g. if
     /// `self` is `Option<_>` and the substs contain `u32`, we'll have
     /// `Option<u32>` afterwards.)
-    pub fn apply_substs(self, substs: Substs) -> Ty {
-        match self {
-            Ty::Apply(ApplicationTy { ctor, parameters: previous_substs }) => {
-                assert_eq!(previous_substs.len(), substs.len());
-                Ty::Apply(ApplicationTy { ctor, parameters: substs })
+    pub fn apply_substs(mut self, new_substs: Substs) -> Ty {
+        match &mut self {
+            Ty::Adt(_, substs)
+            | Ty::Slice(substs)
+            | Ty::Array(substs)
+            | Ty::RawPtr(_, substs)
+            | Ty::Ref(_, substs)
+            | Ty::FnDef(_, substs)
+            | Ty::FnPtr { substs, .. }
+            | Ty::Tuple { substs, .. }
+            | Ty::OpaqueType(_, substs)
+            | Ty::AssociatedType(_, substs)
+            | Ty::ForeignType(_, substs)
+            | Ty::Closure { substs, .. } => {
+                assert_eq!(substs.len(), new_substs.len());
+                *substs = new_substs;
             }
-            _ => self,
+            _ => (),
         }
+        self
     }
 
     /// Returns the type parameters of this type if it has some (i.e. is an ADT
     /// or function); so if `self` is `Option<u32>`, this returns the `u32`.
-    pub fn substs(&self) -> Option<Substs> {
+    pub fn substs(&self) -> Option<&Substs> {
         match self {
-            Ty::Apply(ApplicationTy { parameters, .. }) => Some(parameters.clone()),
+            Ty::Adt(_, substs)
+            | Ty::Slice(substs)
+            | Ty::Array(substs)
+            | Ty::RawPtr(_, substs)
+            | Ty::Ref(_, substs)
+            | Ty::FnDef(_, substs)
+            | Ty::FnPtr { substs, .. }
+            | Ty::Tuple { substs, .. }
+            | Ty::OpaqueType(_, substs)
+            | Ty::AssociatedType(_, substs)
+            | Ty::ForeignType(_, substs)
+            | Ty::Closure { substs, .. } => Some(substs),
+
+            _ => None,
+        }
+    }
+
+    pub fn substs_mut(&mut self) -> Option<&mut Substs> {
+        match self {
+            Ty::Adt(_, substs)
+            | Ty::Slice(substs)
+            | Ty::Array(substs)
+            | Ty::RawPtr(_, substs)
+            | Ty::Ref(_, substs)
+            | Ty::FnDef(_, substs)
+            | Ty::FnPtr { substs, .. }
+            | Ty::Tuple { substs, .. }
+            | Ty::OpaqueType(_, substs)
+            | Ty::AssociatedType(_, substs)
+            | Ty::ForeignType(_, substs)
+            | Ty::Closure { substs, .. } => Some(substs),
+
             _ => None,
         }
     }
 
     pub fn impl_trait_bounds(&self, db: &dyn HirDatabase) -> Option<Vec<GenericPredicate>> {
         match self {
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::OpaqueType(opaque_ty_id), .. }) => {
+            Ty::OpaqueType(opaque_ty_id, ..) => {
                 match opaque_ty_id {
                     OpaqueTyId::AsyncBlockTypeImplTrait(def, _expr) => {
                         let krate = def.module(db.upcast()).krate();
@@ -934,7 +870,7 @@ impl Ty {
 
     pub fn associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option<TraitId> {
         match self {
-            Ty::Apply(ApplicationTy { ctor: TypeCtor::AssociatedType(type_alias_id), .. }) => {
+            Ty::AssociatedType(type_alias_id, ..) => {
                 match type_alias_id.lookup(db.upcast()).container {
                     AssocContainerId::TraitId(trait_id) => Some(trait_id),
                     _ => None,
@@ -1049,11 +985,6 @@ pub trait TypeWalk {
 impl TypeWalk for Ty {
     fn walk(&self, f: &mut impl FnMut(&Ty)) {
         match self {
-            Ty::Apply(a_ty) => {
-                for t in a_ty.parameters.iter() {
-                    t.walk(f);
-                }
-            }
             Ty::Projection(p_ty) => {
                 for t in p_ty.parameters.iter() {
                     t.walk(f);
@@ -1069,7 +1000,13 @@ impl TypeWalk for Ty {
                     t.walk(f);
                 }
             }
-            Ty::Placeholder { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {}
+            _ => {
+                if let Some(substs) = self.substs() {
+                    for t in substs.iter() {
+                        t.walk(f);
+                    }
+                }
+            }
         }
         f(self);
     }
@@ -1080,9 +1017,6 @@ impl TypeWalk for Ty {
         binders: DebruijnIndex,
     ) {
         match self {
-            Ty::Apply(a_ty) => {
-                a_ty.parameters.walk_mut_binders(f, binders);
-            }
             Ty::Projection(p_ty) => {
                 p_ty.parameters.walk_mut_binders(f, binders);
             }
@@ -1094,7 +1028,11 @@ impl TypeWalk for Ty {
             Ty::Opaque(o_ty) => {
                 o_ty.parameters.walk_mut_binders(f, binders);
             }
-            Ty::Placeholder { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {}
+            _ => {
+                if let Some(substs) = self.substs_mut() {
+                    substs.walk_mut_binders(f, binders);
+                }
+            }
         }
         f(self, binders);
     }
diff --git a/crates/hir_ty/src/lower.rs b/crates/hir_ty/src/lower.rs
index 99b0ecf3b55..6b919a09ede 100644
--- a/crates/hir_ty/src/lower.rs
+++ b/crates/hir_ty/src/lower.rs
@@ -33,7 +33,7 @@ use crate::{
     },
     Binders, BoundVar, DebruijnIndex, FnSig, GenericPredicate, OpaqueTy, OpaqueTyId, PolyFnSig,
     ProjectionPredicate, ProjectionTy, ReturnTypeImplTrait, ReturnTypeImplTraits, Substs,
-    TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk,
+    TraitEnvironment, TraitRef, Ty, TypeWalk,
 };
 
 #[derive(Debug)]
@@ -145,13 +145,10 @@ impl Ty {
     pub fn from_hir_ext(ctx: &TyLoweringContext<'_>, type_ref: &TypeRef) -> (Self, Option<TypeNs>) {
         let mut res = None;
         let ty = match type_ref {
-            TypeRef::Never => Ty::simple(TypeCtor::Never),
+            TypeRef::Never => Ty::Never,
             TypeRef::Tuple(inner) => {
                 let inner_tys: Arc<[Ty]> = inner.iter().map(|tr| Ty::from_hir(ctx, tr)).collect();
-                Ty::apply(
-                    TypeCtor::Tuple { cardinality: inner_tys.len() as u16 },
-                    Substs(inner_tys),
-                )
+                Ty::Tuple { cardinality: inner_tys.len() as u16, substs: Substs(inner_tys) }
             }
             TypeRef::Path(path) => {
                 let (ty, res_) = Ty::from_hir_path(ctx, path);
@@ -160,27 +157,24 @@ impl Ty {
             }
             TypeRef::RawPtr(inner, mutability) => {
                 let inner_ty = Ty::from_hir(ctx, inner);
-                Ty::apply_one(TypeCtor::RawPtr(*mutability), inner_ty)
+                Ty::RawPtr(*mutability, Substs::single(inner_ty))
             }
             TypeRef::Array(inner) => {
                 let inner_ty = Ty::from_hir(ctx, inner);
-                Ty::apply_one(TypeCtor::Array, inner_ty)
+                Ty::Array(Substs::single(inner_ty))
             }
             TypeRef::Slice(inner) => {
                 let inner_ty = Ty::from_hir(ctx, inner);
-                Ty::apply_one(TypeCtor::Slice, inner_ty)
+                Ty::Slice(Substs::single(inner_ty))
             }
             TypeRef::Reference(inner, _, mutability) => {
                 let inner_ty = Ty::from_hir(ctx, inner);
-                Ty::apply_one(TypeCtor::Ref(*mutability), inner_ty)
+                Ty::Ref(*mutability, Substs::single(inner_ty))
             }
             TypeRef::Placeholder => Ty::Unknown,
             TypeRef::Fn(params, is_varargs) => {
                 let sig = Substs(params.iter().map(|tr| Ty::from_hir(ctx, tr)).collect());
-                Ty::apply(
-                    TypeCtor::FnPtr { num_args: sig.len() as u16 - 1, is_varargs: *is_varargs },
-                    sig,
-                )
+                Ty::FnPtr { num_args: sig.len() as u16 - 1, is_varargs: *is_varargs, substs: sig }
             }
             TypeRef::DynTrait(bounds) => {
                 let self_ty = Ty::Bound(BoundVar::new(DebruijnIndex::INNERMOST, 0));
@@ -414,7 +408,6 @@ impl Ty {
             // FIXME: report error
             TypeNs::EnumVariantId(_) => return (Ty::Unknown, None),
         };
-
         Ty::from_type_relative_path(ctx, ty, Some(resolution), remaining_segments)
     }
 
@@ -1025,7 +1018,7 @@ fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig {
 fn type_for_fn(db: &dyn HirDatabase, def: FunctionId) -> Binders<Ty> {
     let generics = generics(db.upcast(), def.into());
     let substs = Substs::bound_vars(&generics, DebruijnIndex::INNERMOST);
-    Binders::new(substs.len(), Ty::apply(TypeCtor::FnDef(def.into()), substs))
+    Binders::new(substs.len(), Ty::FnDef(def.into(), substs))
 }
 
 /// Build the declared type of a const.
@@ -1068,7 +1061,7 @@ fn type_for_struct_constructor(db: &dyn HirDatabase, def: StructId) -> Binders<T
     }
     let generics = generics(db.upcast(), def.into());
     let substs = Substs::bound_vars(&generics, DebruijnIndex::INNERMOST);
-    Binders::new(substs.len(), Ty::apply(TypeCtor::FnDef(def.into()), substs))
+    Binders::new(substs.len(), Ty::FnDef(def.into(), substs))
 }
 
 fn fn_sig_for_enum_variant_constructor(db: &dyn HirDatabase, def: EnumVariantId) -> PolyFnSig {
@@ -1093,13 +1086,13 @@ fn type_for_enum_variant_constructor(db: &dyn HirDatabase, def: EnumVariantId) -
     }
     let generics = generics(db.upcast(), def.parent.into());
     let substs = Substs::bound_vars(&generics, DebruijnIndex::INNERMOST);
-    Binders::new(substs.len(), Ty::apply(TypeCtor::FnDef(def.into()), substs))
+    Binders::new(substs.len(), Ty::FnDef(def.into(), substs))
 }
 
 fn type_for_adt(db: &dyn HirDatabase, adt: AdtId) -> Binders<Ty> {
     let generics = generics(db.upcast(), adt.into());
     let substs = Substs::bound_vars(&generics, DebruijnIndex::INNERMOST);
-    Binders::new(substs.len(), Ty::apply(TypeCtor::Adt(adt), substs))
+    Binders::new(substs.len(), Ty::Adt(adt, substs))
 }
 
 fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders<Ty> {
@@ -1109,7 +1102,7 @@ fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders<Ty> {
         TyLoweringContext::new(db, &resolver).with_type_param_mode(TypeParamLoweringMode::Variable);
     let substs = Substs::bound_vars(&generics, DebruijnIndex::INNERMOST);
     if db.type_alias_data(t).is_extern {
-        Binders::new(substs.len(), Ty::apply(TypeCtor::ForeignType(t), substs))
+        Binders::new(substs.len(), Ty::ForeignType(t, substs))
     } else {
         let type_ref = &db.type_alias_data(t).type_ref;
         let inner = Ty::from_hir(&ctx, type_ref.as_ref().unwrap_or(&TypeRef::Error));
diff --git a/crates/hir_ty/src/method_resolution.rs b/crates/hir_ty/src/method_resolution.rs
index 66d8de95917..087b67935f4 100644
--- a/crates/hir_ty/src/method_resolution.rs
+++ b/crates/hir_ty/src/method_resolution.rs
@@ -7,8 +7,8 @@ use std::{iter, sync::Arc};
 use arrayvec::ArrayVec;
 use base_db::CrateId;
 use hir_def::{
-    lang_item::LangItemTarget, type_ref::Mutability, AssocContainerId, AssocItemId, FunctionId,
-    GenericDefId, HasModule, ImplId, Lookup, ModuleId, TraitId,
+    lang_item::LangItemTarget, type_ref::Mutability, AdtId, AssocContainerId, AssocItemId,
+    FunctionId, GenericDefId, HasModule, ImplId, Lookup, ModuleId, TraitId, TypeAliasId,
 };
 use hir_expand::name::Name;
 use rustc_hash::{FxHashMap, FxHashSet};
@@ -18,15 +18,24 @@ use crate::{
     db::HirDatabase,
     primitive::{self, FloatTy, IntTy, UintTy},
     utils::all_super_traits,
-    ApplicationTy, Canonical, DebruijnIndex, InEnvironment, Scalar, Substs, TraitEnvironment,
-    TraitRef, Ty, TyKind, TypeCtor, TypeWalk,
+    Canonical, DebruijnIndex, InEnvironment, Scalar, Substs, TraitEnvironment, TraitRef, Ty,
+    TyKind, TypeWalk,
 };
 
 /// This is used as a key for indexing impls.
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub enum TyFingerprint {
-    Apply(TypeCtor),
+    Str,
+    Slice,
+    Array,
+    Never,
+    RawPtr(Mutability),
+    Scalar(Scalar),
+    Adt(AdtId),
     Dyn(TraitId),
+    Tuple { cardinality: u16 },
+    ForeignType(TypeAliasId),
+    FnPtr { num_args: u16, is_varargs: bool },
 }
 
 impl TyFingerprint {
@@ -34,32 +43,44 @@ impl TyFingerprint {
     /// have impls: if we have some `struct S`, we can have an `impl S`, but not
     /// `impl &S`. Hence, this will return `None` for reference types and such.
     pub(crate) fn for_impl(ty: &Ty) -> Option<TyFingerprint> {
-        match ty {
-            Ty::Apply(a_ty) => Some(TyFingerprint::Apply(a_ty.ctor)),
-            Ty::Dyn(_) => ty.dyn_trait().map(|trait_| TyFingerprint::Dyn(trait_)),
-            _ => None,
-        }
+        let fp = match ty {
+            &Ty::Str => TyFingerprint::Str,
+            &Ty::Never => TyFingerprint::Never,
+            &Ty::Slice(..) => TyFingerprint::Slice,
+            &Ty::Array(..) => TyFingerprint::Array,
+            &Ty::Scalar(scalar) => TyFingerprint::Scalar(scalar),
+            &Ty::Adt(adt, _) => TyFingerprint::Adt(adt),
+            &Ty::Tuple { cardinality: u16, .. } => TyFingerprint::Tuple { cardinality: u16 },
+            &Ty::RawPtr(mutability, ..) => TyFingerprint::RawPtr(mutability),
+            &Ty::ForeignType(alias_id, ..) => TyFingerprint::ForeignType(alias_id),
+            &Ty::FnPtr { num_args, is_varargs, .. } => {
+                TyFingerprint::FnPtr { num_args, is_varargs }
+            }
+            Ty::Dyn(_) => ty.dyn_trait().map(|trait_| TyFingerprint::Dyn(trait_))?,
+            _ => return None,
+        };
+        Some(fp)
     }
 }
 
 pub(crate) const ALL_INT_FPS: [TyFingerprint; 12] = [
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Int(IntTy::I8))),
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Int(IntTy::I16))),
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Int(IntTy::I32))),
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Int(IntTy::I64))),
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Int(IntTy::I128))),
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Int(IntTy::Isize))),
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Uint(UintTy::U8))),
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Uint(UintTy::U16))),
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Uint(UintTy::U32))),
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Uint(UintTy::U64))),
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Uint(UintTy::U128))),
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Uint(UintTy::Usize))),
+    TyFingerprint::Scalar(Scalar::Int(IntTy::I8)),
+    TyFingerprint::Scalar(Scalar::Int(IntTy::I16)),
+    TyFingerprint::Scalar(Scalar::Int(IntTy::I32)),
+    TyFingerprint::Scalar(Scalar::Int(IntTy::I64)),
+    TyFingerprint::Scalar(Scalar::Int(IntTy::I128)),
+    TyFingerprint::Scalar(Scalar::Int(IntTy::Isize)),
+    TyFingerprint::Scalar(Scalar::Uint(UintTy::U8)),
+    TyFingerprint::Scalar(Scalar::Uint(UintTy::U16)),
+    TyFingerprint::Scalar(Scalar::Uint(UintTy::U32)),
+    TyFingerprint::Scalar(Scalar::Uint(UintTy::U64)),
+    TyFingerprint::Scalar(Scalar::Uint(UintTy::U128)),
+    TyFingerprint::Scalar(Scalar::Uint(UintTy::Usize)),
 ];
 
 pub(crate) const ALL_FLOAT_FPS: [TyFingerprint; 2] = [
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Float(FloatTy::F32))),
-    TyFingerprint::Apply(TypeCtor::Scalar(Scalar::Float(FloatTy::F64))),
+    TyFingerprint::Scalar(Scalar::Float(FloatTy::F32)),
+    TyFingerprint::Scalar(Scalar::Float(FloatTy::F64)),
 ];
 
 /// Trait impls defined or available in some crate.
@@ -211,32 +232,29 @@ impl Ty {
         let mod_to_crate_ids = |module: ModuleId| Some(std::iter::once(module.krate()).collect());
 
         let lang_item_targets = match self {
-            Ty::Apply(a_ty) => match a_ty.ctor {
-                TypeCtor::Adt(def_id) => {
-                    return mod_to_crate_ids(def_id.module(db.upcast()));
-                }
-                TypeCtor::ForeignType(type_alias_id) => {
-                    return mod_to_crate_ids(type_alias_id.lookup(db.upcast()).module(db.upcast()));
-                }
-                TypeCtor::Scalar(Scalar::Bool) => lang_item_crate!("bool"),
-                TypeCtor::Scalar(Scalar::Char) => lang_item_crate!("char"),
-                TypeCtor::Scalar(Scalar::Float(f)) => match f {
-                    // There are two lang items: one in libcore (fXX) and one in libstd (fXX_runtime)
-                    FloatTy::F32 => lang_item_crate!("f32", "f32_runtime"),
-                    FloatTy::F64 => lang_item_crate!("f64", "f64_runtime"),
-                },
-                TypeCtor::Scalar(Scalar::Int(t)) => {
-                    lang_item_crate!(primitive::int_ty_to_string(t))
-                }
-                TypeCtor::Scalar(Scalar::Uint(t)) => {
-                    lang_item_crate!(primitive::uint_ty_to_string(t))
-                }
-                TypeCtor::Str => lang_item_crate!("str_alloc", "str"),
-                TypeCtor::Slice => lang_item_crate!("slice_alloc", "slice"),
-                TypeCtor::RawPtr(Mutability::Shared) => lang_item_crate!("const_ptr"),
-                TypeCtor::RawPtr(Mutability::Mut) => lang_item_crate!("mut_ptr"),
-                _ => return None,
+            Ty::Adt(def_id, _) => {
+                return mod_to_crate_ids(def_id.module(db.upcast()));
+            }
+            Ty::ForeignType(type_alias_id, _) => {
+                return mod_to_crate_ids(type_alias_id.lookup(db.upcast()).module(db.upcast()));
+            }
+            Ty::Scalar(Scalar::Bool) => lang_item_crate!("bool"),
+            Ty::Scalar(Scalar::Char) => lang_item_crate!("char"),
+            Ty::Scalar(Scalar::Float(f)) => match f {
+                // There are two lang items: one in libcore (fXX) and one in libstd (fXX_runtime)
+                FloatTy::F32 => lang_item_crate!("f32", "f32_runtime"),
+                FloatTy::F64 => lang_item_crate!("f64", "f64_runtime"),
             },
+            &Ty::Scalar(Scalar::Int(t)) => {
+                lang_item_crate!(primitive::int_ty_to_string(t))
+            }
+            &Ty::Scalar(Scalar::Uint(t)) => {
+                lang_item_crate!(primitive::uint_ty_to_string(t))
+            }
+            Ty::Str => lang_item_crate!("str_alloc", "str"),
+            Ty::Slice(_) => lang_item_crate!("slice_alloc", "slice"),
+            Ty::RawPtr(Mutability::Shared, _) => lang_item_crate!("const_ptr"),
+            Ty::RawPtr(Mutability::Mut, _) => lang_item_crate!("mut_ptr"),
             Ty::Dyn(_) => {
                 return self.dyn_trait().and_then(|trait_| {
                     mod_to_crate_ids(GenericDefId::TraitId(trait_).module(db.upcast()))
@@ -413,7 +431,7 @@ fn iterate_method_candidates_with_autoref(
     }
     let refed = Canonical {
         kinds: deref_chain[0].kinds.clone(),
-        value: Ty::apply_one(TypeCtor::Ref(Mutability::Shared), deref_chain[0].value.clone()),
+        value: Ty::Ref(Mutability::Shared, Substs::single(deref_chain[0].value.clone())),
     };
     if iterate_method_candidates_by_receiver(
         &refed,
@@ -429,7 +447,7 @@ fn iterate_method_candidates_with_autoref(
     }
     let ref_muted = Canonical {
         kinds: deref_chain[0].kinds.clone(),
-        value: Ty::apply_one(TypeCtor::Ref(Mutability::Mut), deref_chain[0].value.clone()),
+        value: Ty::Ref(Mutability::Mut, Substs::single(deref_chain[0].value.clone())),
     };
     if iterate_method_candidates_by_receiver(
         &ref_muted,
@@ -756,11 +774,9 @@ fn autoderef_method_receiver(
 ) -> Vec<Canonical<Ty>> {
     let mut deref_chain: Vec<_> = autoderef::autoderef(db, Some(krate), ty).collect();
     // As a last step, we can do array unsizing (that's the only unsizing that rustc does for method receivers!)
-    if let Some(Ty::Apply(ApplicationTy { ctor: TypeCtor::Array, parameters })) =
-        deref_chain.last().map(|ty| &ty.value)
-    {
+    if let Some(Ty::Array(parameters)) = deref_chain.last().map(|ty| &ty.value) {
         let kinds = deref_chain.last().unwrap().kinds.clone();
-        let unsized_ty = Ty::apply(TypeCtor::Slice, parameters.clone());
+        let unsized_ty = Ty::Slice(parameters.clone());
         deref_chain.push(Canonical { value: unsized_ty, kinds })
     }
     deref_chain
diff --git a/crates/hir_ty/src/op.rs b/crates/hir_ty/src/op.rs
index a4999c51d4d..1c01a67ade1 100644
--- a/crates/hir_ty/src/op.rs
+++ b/crates/hir_ty/src/op.rs
@@ -1,30 +1,23 @@
 //! Helper functions for binary operator type inference.
 use hir_def::expr::{ArithOp, BinaryOp, CmpOp};
 
-use super::{InferTy, Ty, TypeCtor};
-use crate::{ApplicationTy, Scalar};
+use crate::{InferTy, Scalar, Ty};
 
 pub(super) fn binary_op_return_ty(op: BinaryOp, lhs_ty: Ty, rhs_ty: Ty) -> Ty {
     match op {
-        BinaryOp::LogicOp(_) | BinaryOp::CmpOp(_) => Ty::simple(TypeCtor::Scalar(Scalar::Bool)),
+        BinaryOp::LogicOp(_) | BinaryOp::CmpOp(_) => Ty::Scalar(Scalar::Bool),
         BinaryOp::Assignment { .. } => Ty::unit(),
         BinaryOp::ArithOp(ArithOp::Shl) | BinaryOp::ArithOp(ArithOp::Shr) => match lhs_ty {
-            Ty::Apply(ApplicationTy { ctor, .. }) => match ctor {
-                TypeCtor::Scalar(Scalar::Int(_))
-                | TypeCtor::Scalar(Scalar::Uint(_))
-                | TypeCtor::Scalar(Scalar::Float(_)) => lhs_ty,
-                _ => Ty::Unknown,
-            },
+            Ty::Scalar(Scalar::Int(_))
+            | Ty::Scalar(Scalar::Uint(_))
+            | Ty::Scalar(Scalar::Float(_)) => lhs_ty,
             Ty::Infer(InferTy::IntVar(..)) | Ty::Infer(InferTy::FloatVar(..)) => lhs_ty,
             _ => Ty::Unknown,
         },
         BinaryOp::ArithOp(_) => match rhs_ty {
-            Ty::Apply(ApplicationTy { ctor, .. }) => match ctor {
-                TypeCtor::Scalar(Scalar::Int(_))
-                | TypeCtor::Scalar(Scalar::Uint(_))
-                | TypeCtor::Scalar(Scalar::Float(_)) => rhs_ty,
-                _ => Ty::Unknown,
-            },
+            Ty::Scalar(Scalar::Int(_))
+            | Ty::Scalar(Scalar::Uint(_))
+            | Ty::Scalar(Scalar::Float(_)) => rhs_ty,
             Ty::Infer(InferTy::IntVar(..)) | Ty::Infer(InferTy::FloatVar(..)) => rhs_ty,
             _ => Ty::Unknown,
         },
@@ -33,13 +26,10 @@ pub(super) fn binary_op_return_ty(op: BinaryOp, lhs_ty: Ty, rhs_ty: Ty) -> Ty {
 
 pub(super) fn binary_op_rhs_expectation(op: BinaryOp, lhs_ty: Ty) -> Ty {
     match op {
-        BinaryOp::LogicOp(..) => Ty::simple(TypeCtor::Scalar(Scalar::Bool)),
+        BinaryOp::LogicOp(..) => Ty::Scalar(Scalar::Bool),
         BinaryOp::Assignment { op: None } => lhs_ty,
         BinaryOp::CmpOp(CmpOp::Eq { .. }) => match lhs_ty {
-            Ty::Apply(ApplicationTy { ctor, .. }) => match ctor {
-                TypeCtor::Scalar(_) | TypeCtor::Str => lhs_ty,
-                _ => Ty::Unknown,
-            },
+            Ty::Scalar(_) | Ty::Str => lhs_ty,
             Ty::Infer(InferTy::IntVar(..)) | Ty::Infer(InferTy::FloatVar(..)) => lhs_ty,
             _ => Ty::Unknown,
         },
@@ -47,12 +37,9 @@ pub(super) fn binary_op_rhs_expectation(op: BinaryOp, lhs_ty: Ty) -> Ty {
         BinaryOp::CmpOp(CmpOp::Ord { .. })
         | BinaryOp::Assignment { op: Some(_) }
         | BinaryOp::ArithOp(_) => match lhs_ty {
-            Ty::Apply(ApplicationTy { ctor, .. }) => match ctor {
-                TypeCtor::Scalar(Scalar::Int(_))
-                | TypeCtor::Scalar(Scalar::Uint(_))
-                | TypeCtor::Scalar(Scalar::Float(_)) => lhs_ty,
-                _ => Ty::Unknown,
-            },
+            Ty::Scalar(Scalar::Int(_))
+            | Ty::Scalar(Scalar::Uint(_))
+            | Ty::Scalar(Scalar::Float(_)) => lhs_ty,
             Ty::Infer(InferTy::IntVar(..)) | Ty::Infer(InferTy::FloatVar(..)) => lhs_ty,
             _ => Ty::Unknown,
         },
diff --git a/crates/hir_ty/src/traits/chalk.rs b/crates/hir_ty/src/traits/chalk.rs
index d74c837370a..c53e327daf7 100644
--- a/crates/hir_ty/src/traits/chalk.rs
+++ b/crates/hir_ty/src/traits/chalk.rs
@@ -20,7 +20,7 @@ use crate::{
     method_resolution::{TyFingerprint, ALL_FLOAT_FPS, ALL_INT_FPS},
     utils::generics,
     BoundVar, CallableDefId, DebruijnIndex, FnSig, GenericPredicate, ProjectionPredicate,
-    ProjectionTy, Substs, TraitRef, Ty, TypeCtor,
+    ProjectionTy, Substs, TraitRef, Ty,
 };
 use mapping::{
     convert_where_clauses, generic_predicate_to_inline_bound, make_binders, TypeAliasAsAssocType,
@@ -489,10 +489,11 @@ pub(crate) fn struct_datum_query(
     struct_id: AdtId,
 ) -> Arc<StructDatum> {
     debug!("struct_datum {:?}", struct_id);
-    let type_ctor = TypeCtor::Adt(from_chalk(db, struct_id));
+    let adt_id = from_chalk(db, struct_id);
+    let type_ctor = Ty::Adt(adt_id, Substs::empty());
     debug!("struct {:?} = {:?}", struct_id, type_ctor);
-    let num_params = type_ctor.num_ty_params(db);
-    let upstream = type_ctor.krate(db) != Some(krate);
+    let num_params = generics(db.upcast(), adt_id.into()).len();
+    let upstream = adt_id.module(db.upcast()).krate() != krate;
     let where_clauses = type_ctor
         .as_generic_def()
         .map(|generic_def| {
diff --git a/crates/hir_ty/src/traits/chalk/mapping.rs b/crates/hir_ty/src/traits/chalk/mapping.rs
index 5a3cb790600..297ddeabdb4 100644
--- a/crates/hir_ty/src/traits/chalk/mapping.rs
+++ b/crates/hir_ty/src/traits/chalk/mapping.rs
@@ -16,9 +16,8 @@ use crate::{
     db::HirDatabase,
     primitive::UintTy,
     traits::{Canonical, Obligation},
-    ApplicationTy, CallableDefId, GenericPredicate, InEnvironment, OpaqueTy, OpaqueTyId,
-    ProjectionPredicate, ProjectionTy, Scalar, Substs, TraitEnvironment, TraitRef, Ty, TyKind,
-    TypeCtor,
+    CallableDefId, GenericPredicate, InEnvironment, OpaqueTy, OpaqueTyId, ProjectionPredicate,
+    ProjectionTy, Scalar, Substs, TraitEnvironment, TraitRef, Ty, TyKind,
 };
 
 use super::interner::*;
@@ -28,75 +27,71 @@ impl ToChalk for Ty {
     type Chalk = chalk_ir::Ty<Interner>;
     fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::Ty<Interner> {
         match self {
-            Ty::Apply(apply_ty) => match apply_ty.ctor {
-                TypeCtor::Ref(m) => ref_to_chalk(db, m, apply_ty.parameters),
-                TypeCtor::Array => array_to_chalk(db, apply_ty.parameters),
-                TypeCtor::FnPtr { num_args: _, is_varargs } => {
-                    let substitution =
-                        chalk_ir::FnSubst(apply_ty.parameters.to_chalk(db).shifted_in(&Interner));
-                    chalk_ir::TyKind::Function(chalk_ir::FnPointer {
-                        num_binders: 0,
-                        sig: chalk_ir::FnSig {
-                            abi: (),
-                            safety: chalk_ir::Safety::Safe,
-                            variadic: is_varargs,
-                        },
-                        substitution,
-                    })
-                    .intern(&Interner)
-                }
-                TypeCtor::AssociatedType(type_alias) => {
-                    let assoc_type = TypeAliasAsAssocType(type_alias);
-                    let assoc_type_id = assoc_type.to_chalk(db);
-                    let substitution = apply_ty.parameters.to_chalk(db);
-                    chalk_ir::TyKind::AssociatedType(assoc_type_id, substitution).intern(&Interner)
-                }
+            Ty::Ref(m, parameters) => ref_to_chalk(db, m, parameters),
+            Ty::Array(parameters) => array_to_chalk(db, parameters),
+            Ty::FnPtr { num_args: _, is_varargs, substs } => {
+                let substitution = chalk_ir::FnSubst(substs.to_chalk(db).shifted_in(&Interner));
+                chalk_ir::TyKind::Function(chalk_ir::FnPointer {
+                    num_binders: 0,
+                    sig: chalk_ir::FnSig {
+                        abi: (),
+                        safety: chalk_ir::Safety::Safe,
+                        variadic: is_varargs,
+                    },
+                    substitution,
+                })
+                .intern(&Interner)
+            }
+            Ty::AssociatedType(type_alias, substs) => {
+                let assoc_type = TypeAliasAsAssocType(type_alias);
+                let assoc_type_id = assoc_type.to_chalk(db);
+                let substitution = substs.to_chalk(db);
+                chalk_ir::TyKind::AssociatedType(assoc_type_id, substitution).intern(&Interner)
+            }
 
-                TypeCtor::OpaqueType(impl_trait_id) => {
-                    let id = impl_trait_id.to_chalk(db);
-                    let substitution = apply_ty.parameters.to_chalk(db);
-                    chalk_ir::TyKind::OpaqueType(id, substitution).intern(&Interner)
-                }
+            Ty::OpaqueType(impl_trait_id, substs) => {
+                let id = impl_trait_id.to_chalk(db);
+                let substitution = substs.to_chalk(db);
+                chalk_ir::TyKind::OpaqueType(id, substitution).intern(&Interner)
+            }
 
-                TypeCtor::ForeignType(type_alias) => {
-                    let foreign_type = TypeAliasAsForeignType(type_alias);
-                    let foreign_type_id = foreign_type.to_chalk(db);
-                    chalk_ir::TyKind::Foreign(foreign_type_id).intern(&Interner)
-                }
+            Ty::ForeignType(type_alias, _) => {
+                let foreign_type = TypeAliasAsForeignType(type_alias);
+                let foreign_type_id = foreign_type.to_chalk(db);
+                chalk_ir::TyKind::Foreign(foreign_type_id).intern(&Interner)
+            }
 
-                TypeCtor::Scalar(scalar) => chalk_ir::TyKind::Scalar(scalar).intern(&Interner),
+            Ty::Scalar(scalar) => chalk_ir::TyKind::Scalar(scalar).intern(&Interner),
 
-                TypeCtor::Tuple { cardinality } => {
-                    let substitution = apply_ty.parameters.to_chalk(db);
-                    chalk_ir::TyKind::Tuple(cardinality.into(), substitution).intern(&Interner)
-                }
-                TypeCtor::RawPtr(mutability) => {
-                    let ty = apply_ty.parameters[0].clone().to_chalk(db);
-                    chalk_ir::TyKind::Raw(mutability.to_chalk(db), ty).intern(&Interner)
-                }
-                TypeCtor::Slice => {
-                    chalk_ir::TyKind::Slice(apply_ty.parameters[0].clone().to_chalk(db))
-                        .intern(&Interner)
-                }
-                TypeCtor::Str => chalk_ir::TyKind::Str.intern(&Interner),
-                TypeCtor::FnDef(callable_def) => {
-                    let id = callable_def.to_chalk(db);
-                    let substitution = apply_ty.parameters.to_chalk(db);
-                    chalk_ir::TyKind::FnDef(id, substitution).intern(&Interner)
-                }
-                TypeCtor::Never => chalk_ir::TyKind::Never.intern(&Interner),
+            Ty::Tuple { cardinality, substs } => {
+                let substitution = substs.to_chalk(db);
+                chalk_ir::TyKind::Tuple(cardinality.into(), substitution).intern(&Interner)
+            }
+            Ty::RawPtr(mutability, substs) => {
+                let ty = substs[0].clone().to_chalk(db);
+                chalk_ir::TyKind::Raw(mutability.to_chalk(db), ty).intern(&Interner)
+            }
+            Ty::Slice(substs) => {
+                chalk_ir::TyKind::Slice(substs[0].clone().to_chalk(db)).intern(&Interner)
+            }
+            Ty::Str => chalk_ir::TyKind::Str.intern(&Interner),
+            Ty::FnDef(callable_def, substs) => {
+                let id = callable_def.to_chalk(db);
+                let substitution = substs.to_chalk(db);
+                chalk_ir::TyKind::FnDef(id, substitution).intern(&Interner)
+            }
+            Ty::Never => chalk_ir::TyKind::Never.intern(&Interner),
 
-                TypeCtor::Closure { def, expr } => {
-                    let closure_id = db.intern_closure((def, expr));
-                    let substitution = apply_ty.parameters.to_chalk(db);
-                    chalk_ir::TyKind::Closure(closure_id.into(), substitution).intern(&Interner)
-                }
+            Ty::Closure { def, expr, substs } => {
+                let closure_id = db.intern_closure((def, expr));
+                let substitution = substs.to_chalk(db);
+                chalk_ir::TyKind::Closure(closure_id.into(), substitution).intern(&Interner)
+            }
 
-                TypeCtor::Adt(adt_id) => {
-                    let substitution = apply_ty.parameters.to_chalk(db);
-                    chalk_ir::TyKind::Adt(chalk_ir::AdtId(adt_id), substitution).intern(&Interner)
-                }
-            },
+            Ty::Adt(adt_id, substs) => {
+                let substitution = substs.to_chalk(db);
+                chalk_ir::TyKind::Adt(chalk_ir::AdtId(adt_id), substitution).intern(&Interner)
+            }
             Ty::Projection(proj_ty) => {
                 let associated_ty_id = TypeAliasAsAssocType(proj_ty.associated_ty).to_chalk(db);
                 let substitution = proj_ty.parameters.to_chalk(db);
@@ -143,9 +138,7 @@ impl ToChalk for Ty {
     fn from_chalk(db: &dyn HirDatabase, chalk: chalk_ir::Ty<Interner>) -> Self {
         match chalk.data(&Interner).kind.clone() {
             chalk_ir::TyKind::Error => Ty::Unknown,
-            chalk_ir::TyKind::Array(ty, _size) => {
-                Ty::apply(TypeCtor::Array, Substs::single(from_chalk(db, ty)))
-            }
+            chalk_ir::TyKind::Array(ty, _size) => Ty::Array(Substs::single(from_chalk(db, ty))),
             chalk_ir::TyKind::Placeholder(idx) => {
                 assert_eq!(idx.ui, UniverseIndex::ROOT);
                 let interned_id = crate::db::GlobalTypeParamId::from_intern_id(
@@ -175,13 +168,11 @@ impl ToChalk for Ty {
                     db,
                     substitution.0.shifted_out(&Interner).expect("fn ptr should have no binders"),
                 );
-                Ty::Apply(ApplicationTy {
-                    ctor: TypeCtor::FnPtr {
-                        num_args: (parameters.len() - 1) as u16,
-                        is_varargs: variadic,
-                    },
-                    parameters,
-                })
+                Ty::FnPtr {
+                    num_args: (parameters.len() - 1) as u16,
+                    is_varargs: variadic,
+                    substs: parameters,
+                }
             }
             chalk_ir::TyKind::BoundVar(idx) => Ty::Bound(idx),
             chalk_ir::TyKind::InferenceVar(_iv, _kind) => Ty::Unknown,
@@ -196,60 +187,50 @@ impl ToChalk for Ty {
                 Ty::Dyn(predicates)
             }
 
-            chalk_ir::TyKind::Adt(struct_id, subst) => {
-                apply_ty_from_chalk(db, TypeCtor::Adt(struct_id.0), subst)
-            }
-            chalk_ir::TyKind::AssociatedType(type_id, subst) => apply_ty_from_chalk(
-                db,
-                TypeCtor::AssociatedType(from_chalk::<TypeAliasAsAssocType, _>(db, type_id).0),
-                subst,
+            chalk_ir::TyKind::Adt(struct_id, subst) => Ty::Adt(struct_id.0, from_chalk(db, subst)),
+            chalk_ir::TyKind::AssociatedType(type_id, subst) => Ty::AssociatedType(
+                from_chalk::<TypeAliasAsAssocType, _>(db, type_id).0,
+                from_chalk(db, subst),
             ),
+
             chalk_ir::TyKind::OpaqueType(opaque_type_id, subst) => {
-                apply_ty_from_chalk(db, TypeCtor::OpaqueType(from_chalk(db, opaque_type_id)), subst)
+                Ty::OpaqueType(from_chalk(db, opaque_type_id), from_chalk(db, subst))
             }
 
-            chalk_ir::TyKind::Scalar(scalar) => Ty::simple(TypeCtor::Scalar(scalar)),
+            chalk_ir::TyKind::Scalar(scalar) => Ty::Scalar(scalar),
             chalk_ir::TyKind::Tuple(cardinality, subst) => {
-                apply_ty_from_chalk(db, TypeCtor::Tuple { cardinality: cardinality as u16 }, subst)
+                Ty::Tuple { cardinality: cardinality as u16, substs: from_chalk(db, subst) }
             }
             chalk_ir::TyKind::Raw(mutability, ty) => {
-                Ty::apply_one(TypeCtor::RawPtr(from_chalk(db, mutability)), from_chalk(db, ty))
+                Ty::RawPtr(from_chalk(db, mutability), Substs::single(from_chalk(db, ty)))
             }
-            chalk_ir::TyKind::Slice(ty) => Ty::apply_one(TypeCtor::Slice, from_chalk(db, ty)),
+            chalk_ir::TyKind::Slice(ty) => Ty::Slice(Substs::single(from_chalk(db, ty))),
             chalk_ir::TyKind::Ref(mutability, _lifetime, ty) => {
-                Ty::apply_one(TypeCtor::Ref(from_chalk(db, mutability)), from_chalk(db, ty))
+                Ty::Ref(from_chalk(db, mutability), Substs::single(from_chalk(db, ty)))
             }
-            chalk_ir::TyKind::Str => Ty::simple(TypeCtor::Str),
-            chalk_ir::TyKind::Never => Ty::simple(TypeCtor::Never),
+            chalk_ir::TyKind::Str => Ty::Str,
+            chalk_ir::TyKind::Never => Ty::Never,
 
             chalk_ir::TyKind::FnDef(fn_def_id, subst) => {
-                let callable_def = from_chalk(db, fn_def_id);
-                apply_ty_from_chalk(db, TypeCtor::FnDef(callable_def), subst)
+                Ty::FnDef(from_chalk(db, fn_def_id), from_chalk(db, subst))
             }
 
             chalk_ir::TyKind::Closure(id, subst) => {
                 let id: crate::db::ClosureId = id.into();
                 let (def, expr) = db.lookup_intern_closure(id);
-                apply_ty_from_chalk(db, TypeCtor::Closure { def, expr }, subst)
+                Ty::Closure { def, expr, substs: from_chalk(db, subst) }
             }
 
-            chalk_ir::TyKind::Foreign(foreign_def_id) => Ty::simple(TypeCtor::ForeignType(
+            chalk_ir::TyKind::Foreign(foreign_def_id) => Ty::ForeignType(
                 from_chalk::<TypeAliasAsForeignType, _>(db, foreign_def_id).0,
-            )),
+                Substs::empty(),
+            ),
             chalk_ir::TyKind::Generator(_, _) => unimplemented!(), // FIXME
             chalk_ir::TyKind::GeneratorWitness(_, _) => unimplemented!(), // FIXME
         }
     }
 }
 
-fn apply_ty_from_chalk(
-    db: &dyn HirDatabase,
-    ctor: TypeCtor,
-    subst: chalk_ir::Substitution<Interner>,
-) -> Ty {
-    Ty::Apply(ApplicationTy { ctor, parameters: from_chalk(db, subst) })
-}
-
 /// We currently don't model lifetimes, but Chalk does. So, we have to insert a
 /// fake lifetime here, because Chalks built-in logic may expect it to be there.
 fn ref_to_chalk(