Repository: intellij-rust/intellij-rust (language: rust)

Issue:
Detect E0308 for closure and function types
<!--
Hello and thank you for the issue!
If you would like to report a bug, we have added some points below that you can fill out.
Feel free to remove all the irrelevant text to request a new feature.
-->

## Environment

* **IntelliJ Rust plugin version:** 0.3.136.3472-202-nightly
* **Rust toolchain version:** 1.47.0 (18bf6b4f0 2020-10-07) x86_64-apple-darwin
* **IDE name and version:** IntelliJ IDEA 2020.2.3 Ultimate Edition (IU-202.7660.26)
* **Operating system:** macOS 10.15.6

## Problem description
Currently, the plugin type system doesn't distinguish types of closures, function and function pointers. All of them are represented by `org.rust.lang.core.types.ty.TyFunction` class. As a result, the plugin doesn't show [E0308](https://doc.rust-lang.org/error-index.html#E0308) error in some cases

![image](https://user-images.githubusercontent.com/2539310/99095030-a7547280-25e5-11eb-804f-0996ce6e2708.png)


## Steps to reproduce
```rust
fn main() {
    let mut a = |x: i32| x;
    a = |x: i32| x;
}
```

```rust
fn main() {
    let mut a = foo;
    a = bar;
}

fn foo(x: i32) {}
fn bar(x: i32) {}
```

<!--
Please include as much of your codebase as needed to reproduce the error.
If the relevant files are large, please provide a link to a public repository or a [Gist](https://gist.github.com/).
-->


Discussion / hints:

Fix patch:
diff --git a/src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.kt b/src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.kt
index 3d372d2a8bd..93b7aced7d1 100644
--- a/src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.kt
+++ b/src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.kt
@@ -1766,7 +1766,7 @@ fun RsCallExpr.getFunctionCallContext(): FunctionCallContext? {
             val type = el.type.stripReferences()
             // TODO: replace with more generic solution
             // when https://github.com/intellij-rust/intellij-rust/issues/6391 will be implemented
-            if (type is TyFunction) {
+            if (type is TyFunctionBase) {
                 val letDecl = el.parent?.parent as? RsLetDecl
                 if (letDecl?.expr is RsLambdaExpr) {
                     FunctionCallContext(type.paramTypes.size, FunctionType.CLOSURE)
diff --git a/src/main/kotlin/org/rust/ide/fixes/FillFunctionArgumentsFix.kt b/src/main/kotlin/org/rust/ide/fixes/FillFunctionArgumentsFix.kt
index ed7721b3a6e..cbc12c79c85 100644
--- a/src/main/kotlin/org/rust/ide/fixes/FillFunctionArgumentsFix.kt
+++ b/src/main/kotlin/org/rust/ide/fixes/FillFunctionArgumentsFix.kt
@@ -24,7 +24,7 @@ import org.rust.lang.core.psi.ext.getLocalVariableVisibleBindings
 import org.rust.lang.core.resolve.knownItems
 import org.rust.lang.core.types.inference
 import org.rust.lang.core.types.ty.Ty
-import org.rust.lang.core.types.ty.TyFunction
+import org.rust.lang.core.types.ty.TyFunctionBase
 import org.rust.lang.core.types.type
 
 class FillFunctionArgumentsFix(element: PsiElement) : LocalQuickFixAndIntentionActionOnPsiElement(element) {
@@ -93,7 +93,7 @@ class FillFunctionArgumentsFix(element: PsiElement) : LocalQuickFixAndIntentionA
 
 private fun getParameterTypes(element: PsiElement): List<Ty>? {
     return when (element) {
-        is RsCallExpr -> (element.expr.type as? TyFunction)?.paramTypes
+        is RsCallExpr -> (element.expr.type as? TyFunctionBase)?.paramTypes
         is RsMethodCall -> element.inference?.getResolvedMethodType(element)?.paramTypes?.drop(1)
         else -> null
     }
diff --git a/src/main/kotlin/org/rust/ide/hints/type/RsTypeHintsPresentationFactory.kt b/src/main/kotlin/org/rust/ide/hints/type/RsTypeHintsPresentationFactory.kt
index c57aa743926..28cac7c1fb5 100644
--- a/src/main/kotlin/org/rust/ide/hints/type/RsTypeHintsPresentationFactory.kt
+++ b/src/main/kotlin/org/rust/ide/hints/type/RsTypeHintsPresentationFactory.kt
@@ -45,7 +45,7 @@ class RsTypeHintsPresentationFactory(
         return when (kind) {
             is TyTuple -> tupleTypeHint(kind, level)
             is TyAdt -> adtTypeHint(kind, level)
-            is TyFunction -> functionTypeHint(kind, level)
+            is TyFunctionBase -> functionTypeHint(kind, level)
             is TyReference -> referenceTypeHint(kind, level)
             is TyPointer -> pointerTypeHint(kind, level)
             is TyProjection -> projectionTypeHint(kind, level)
@@ -61,7 +61,7 @@ class RsTypeHintsPresentationFactory(
         }
     }
 
-    private fun functionTypeHint(type: TyFunction, level: Int): InlayPresentation {
+    private fun functionTypeHint(type: TyFunctionBase, level: Int): InlayPresentation {
         val parameters = type.paramTypes
         val returnType = type.retType
 
diff --git a/src/main/kotlin/org/rust/ide/inspections/RsUnnecessaryCastInspection.kt b/src/main/kotlin/org/rust/ide/inspections/RsUnnecessaryCastInspection.kt
index 1b0411d8c0c..3e8a4097fca 100644
--- a/src/main/kotlin/org/rust/ide/inspections/RsUnnecessaryCastInspection.kt
+++ b/src/main/kotlin/org/rust/ide/inspections/RsUnnecessaryCastInspection.kt
@@ -16,7 +16,7 @@ import org.rust.lang.core.types.infer.TypeVisitor
 import org.rust.lang.core.types.infer.containsTyOfClass
 import org.rust.lang.core.types.rawType
 import org.rust.lang.core.types.ty.Ty
-import org.rust.lang.core.types.ty.TyFunction
+import org.rust.lang.core.types.ty.TyFunctionBase
 import org.rust.lang.core.types.ty.TyUnknown
 import org.rust.lang.core.types.type
 
@@ -114,7 +114,7 @@ class RsUnnecessaryCastInspection : RsLintInspection() {
     }
 
     private fun isFunction(ty: Ty): Boolean {
-        return ty is TyFunction
+        return ty is TyFunctionBase
     }
 
     private fun isAlias(ty: Ty): Boolean {
diff --git a/src/main/kotlin/org/rust/ide/intentions/ConvertClosureToFunctionIntention.kt b/src/main/kotlin/org/rust/ide/intentions/ConvertClosureToFunctionIntention.kt
index b8d6f57fb5c..61af00b0cc6 100644
--- a/src/main/kotlin/org/rust/ide/intentions/ConvertClosureToFunctionIntention.kt
+++ b/src/main/kotlin/org/rust/ide/intentions/ConvertClosureToFunctionIntention.kt
@@ -15,7 +15,7 @@ import org.rust.ide.utils.PsiModificationUtil
 import org.rust.ide.utils.template.buildAndRunTemplate
 import org.rust.lang.core.psi.*
 import org.rust.lang.core.psi.ext.*
-import org.rust.lang.core.types.ty.TyFunction
+import org.rust.lang.core.types.ty.TyFunctionBase
 import org.rust.lang.core.types.ty.TyUnit
 import org.rust.lang.core.types.ty.TyUnknown
 import org.rust.lang.core.types.type
@@ -56,7 +56,7 @@ class ConvertClosureToFunctionIntention : RsElementBaseIntentionAction<ConvertCl
         val useDefaultName = letBidingName == null
         val targetFunctionName = letBidingName ?: "func"
 
-        val fnType = ctx.lambda.type as? TyFunction ?: return
+        val fnType = ctx.lambda.type as? TyFunctionBase ?: return
         val parametersText = ctx.lambda.valueParameters.zip(fnType.paramTypes).joinToString(", ") { (pat, paramType) ->
             val patText = pat.patText ?: "_"
             val type = paramType.renderInsertionSafe()
diff --git a/src/main/kotlin/org/rust/ide/presentation/TypeRendering.kt b/src/main/kotlin/org/rust/ide/presentation/TypeRendering.kt
index 48fd9ac3080..f444306b6dc 100644
--- a/src/main/kotlin/org/rust/ide/presentation/TypeRendering.kt
+++ b/src/main/kotlin/org/rust/ide/presentation/TypeRendering.kt
@@ -132,7 +132,8 @@ private data class TypeRenderer(
         }
 
         return when (ty) {
-            is TyFunction -> formatFnLike("fn", ty.paramTypes, ty.retType, render)
+            is TyFunctionDef -> formatFunctionDef(ty.def.name, ty.unsafety, ty.paramTypes, ty.retType, render)
+            is TyFunctionBase -> formatFnLike("fn", ty.unsafety, ty.paramTypes, ty.retType, render)
             is TySlice -> "[${render(ty.elementType)}]"
 
             is TyTuple -> if (ty.types.size == 1) {
@@ -199,15 +200,29 @@ private data class TypeRenderer(
             else -> unknownConst
         }
 
-    private fun formatFnLike(fnType: String, paramTypes: List<Ty>, retType: Ty, render: (Ty) -> String): String =
+    private fun formatFnLike(fnType: String, unsafety: Unsafety, paramTypes: List<Ty>, retType: Ty, render: (Ty) -> String): String =
         buildString {
-            paramTypes.joinTo(this, ", ", "$fnType(", ")", transform = render)
-            if (retType !is TyUnit) {
-                append(" -> ")
-                append(render(retType))
+            buildFnLikeString(fnType, unsafety, paramTypes, retType, render)
+        }
+
+
+    private fun formatFunctionDef(name: String?, unsafety: Unsafety, paramTypes: List<Ty>, retType: Ty, render: (Ty) -> String): String =
+        buildString {
+            buildFnLikeString("fn", unsafety, paramTypes, retType, render)
+            if (name != null) {
+                append(" {$name}")
             }
         }
 
+    private fun StringBuilder.buildFnLikeString(fnType: String, unsafety: Unsafety, paramTypes: List<Ty>, retType: Ty, render: (Ty) -> String) {
+        val unsafetyPrefix = if (unsafety == Unsafety.Unsafe) "unsafe " else ""
+        paramTypes.joinTo(this, ", ", "$unsafetyPrefix$fnType(", ")", transform = render)
+        if (retType !is TyUnit) {
+            append(" -> ")
+            append(render(retType))
+        }
+    }
+
     private fun formatTrait(trait: BoundElement<RsTraitItem>, render: (Ty) -> String): String = buildString {
         val name = trait.element.name ?: return anonymous
         if (trait.element.langAttribute in listOf("fn", "fn_once", "fn_mut")) {
@@ -220,7 +235,7 @@ private data class TypeRenderer(
                 .find { it.key.name == "Output" }
                 ?.value
                 ?: TyUnit.INSTANCE
-            append(formatFnLike(name, paramTypes, retType, render))
+            append(formatFnLike(name, Unsafety.fromBoolean(trait.element.isUnsafe), paramTypes, retType, render))
         } else {
             append(name)
             if (includeTypeArguments) append(formatTraitGenerics(trait, render))
diff --git a/src/main/kotlin/org/rust/ide/utils/CallInfo.kt b/src/main/kotlin/org/rust/ide/utils/CallInfo.kt
index fe2d5c6a2ab..dd9e59bf31f 100644
--- a/src/main/kotlin/org/rust/ide/utils/CallInfo.kt
+++ b/src/main/kotlin/org/rust/ide/utils/CallInfo.kt
@@ -11,7 +11,7 @@ import org.rust.lang.core.psi.ext.*
 import org.rust.lang.core.types.emptySubstitution
 import org.rust.lang.core.types.inference
 import org.rust.lang.core.types.ty.Ty
-import org.rust.lang.core.types.ty.TyFunction
+import org.rust.lang.core.types.ty.TyFunctionBase
 import org.rust.lang.core.types.ty.TyUnknown
 import org.rust.lang.core.types.type
 
@@ -33,7 +33,7 @@ class CallInfo private constructor(
     companion object {
         fun resolve(call: RsCallExpr): CallInfo? {
             val fn = (call.expr as? RsPathExpr)?.path?.reference?.resolve() ?: return null
-            val ty = call.expr.type as? TyFunction ?: return null
+            val ty = call.expr.type as? TyFunctionBase ?: return null
 
             return when (fn) {
                 is RsFunction -> buildFunctionParameters(fn, ty)
@@ -47,7 +47,7 @@ class CallInfo private constructor(
             return buildFunctionParameters(function, type)
         }
 
-        private fun buildFunctionLike(fn: RsElement, ty: TyFunction): CallInfo? {
+        private fun buildFunctionLike(fn: RsElement, ty: TyFunctionBase): CallInfo? {
             val parameters = getFunctionLikeParameters(fn) ?: return null
             return CallInfo(buildParameters(ty.paramTypes, parameters))
         }
@@ -66,7 +66,7 @@ class CallInfo private constructor(
             }
         }
 
-        private fun buildFunctionParameters(function: RsFunction, ty: TyFunction): CallInfo {
+        private fun buildFunctionParameters(function: RsFunction, ty: TyFunctionBase): CallInfo {
             val types = run {
                 val types = ty.paramTypes
                 if (function.isMethod) {
diff --git a/src/main/kotlin/org/rust/lang/core/dfa/ExprUseWalker.kt b/src/main/kotlin/org/rust/lang/core/dfa/ExprUseWalker.kt
index af34b97785b..6eaa03319bd 100644
--- a/src/main/kotlin/org/rust/lang/core/dfa/ExprUseWalker.kt
+++ b/src/main/kotlin/org/rust/lang/core/dfa/ExprUseWalker.kt
@@ -23,7 +23,7 @@ import org.rust.lang.core.types.normType
 import org.rust.lang.core.types.regions.ReScope
 import org.rust.lang.core.types.regions.Scope
 import org.rust.lang.core.types.ty.TyAdt
-import org.rust.lang.core.types.ty.TyFunction
+import org.rust.lang.core.types.ty.TyFunctionBase
 import org.rust.lang.core.types.ty.TyUnknown
 import org.rust.lang.core.types.ty.isMovesByDefault
 import org.rust.lang.core.types.type
@@ -304,7 +304,7 @@ class ExprUseWalker(private val delegate: Delegate, private val mc: MemoryCatego
 
     private fun walkCallee(callee: RsExpr) {
         when (callee.type) {
-            is TyFunction -> consumeExpr(callee)
+            is TyFunctionBase -> consumeExpr(callee)
             else -> useAllPaths(callee)
         }
     }
diff --git a/src/main/kotlin/org/rust/lang/core/psi/ext/RsForeignModItem.kt b/src/main/kotlin/org/rust/lang/core/psi/ext/RsForeignModItem.kt
index 113cb6a5ad0..3dc5c9d25da 100644
--- a/src/main/kotlin/org/rust/lang/core/psi/ext/RsForeignModItem.kt
+++ b/src/main/kotlin/org/rust/lang/core/psi/ext/RsForeignModItem.kt
@@ -12,6 +12,19 @@ import org.rust.lang.core.macros.RsExpandedElement
 import org.rust.lang.core.psi.RsForeignModItem
 import org.rust.lang.core.stubs.RsForeignModStub
 
+val RsForeignModItem.effectiveAbi: String
+    get() = abi ?: "C"
+
+val RsForeignModItem.abi: String?
+    get() {
+        val stub = greenStub
+        return if (stub != null) {
+            stub.abi
+        } else {
+            externAbi.litExpr?.stringValue
+        }
+    }
+
 abstract class RsForeignModItemImplMixin : RsStubbedElementImpl<RsForeignModStub>,
                                            RsForeignModItem {
 
@@ -21,7 +34,5 @@ abstract class RsForeignModItemImplMixin : RsStubbedElementImpl<RsForeignModStub
 
     override val visibility: RsVisibility get() = RsVisibility.Private // visibility does not affect foreign mods
 
-    val abi: String = greenStub?.abi ?: "C"
-
     override fun getContext(): PsiElement? = RsExpandedElement.getContextImpl(this)
 }
diff --git a/src/main/kotlin/org/rust/lang/core/psi/ext/RsFunction.kt b/src/main/kotlin/org/rust/lang/core/psi/ext/RsFunction.kt
index 115f7df8f8a..4faf2e61cdf 100644
--- a/src/main/kotlin/org/rust/lang/core/psi/ext/RsFunction.kt
+++ b/src/main/kotlin/org/rust/lang/core/psi/ext/RsFunction.kt
@@ -169,7 +169,7 @@ val RsFunction.isActuallyUnsafe: Boolean
                 // See https://github.com/rustwasm/wasm-bindgen
                 context.queryAttributes.hasAttribute("wasm_bindgen") -> false
                 // Some Rust intrinsics are safe. This info is hardcoded in compiler
-                context.externAbi.litExpr?.stringValue == "rust-intrinsic" -> name !in SAFE_INTRINSICS
+                isIntrinsic -> name !in SAFE_INTRINSICS
                 else -> true
             }
         } else {
@@ -177,6 +177,13 @@ val RsFunction.isActuallyUnsafe: Boolean
         }
     }
 
+val RsFunction.isIntrinsic: Boolean
+    get() {
+        val context = context
+        return context is RsForeignModItem && context.effectiveAbi == "rust-intrinsic"
+    }
+
+
 // Taken from https://github.com/rust-lang/rust/blob/6b4563bf93f4b103ed22507ed825008b89e4f5d9/compiler/rustc_typeck/src/check/intrinsic.rs#L65-L108
 private val SAFE_INTRINSICS: Set<String> = hashSetOf(
     "abort",
diff --git a/src/main/kotlin/org/rust/lang/core/psi/ext/RsLambdaExpr.kt b/src/main/kotlin/org/rust/lang/core/psi/ext/RsLambdaExpr.kt
index 1b3c41491fa..4f52df31ed9 100644
--- a/src/main/kotlin/org/rust/lang/core/psi/ext/RsLambdaExpr.kt
+++ b/src/main/kotlin/org/rust/lang/core/psi/ext/RsLambdaExpr.kt
@@ -10,7 +10,7 @@ import org.rust.lang.core.psi.RsElementTypes
 import org.rust.lang.core.psi.RsLambdaExpr
 import org.rust.lang.core.psi.RsValueParameter
 import org.rust.lang.core.types.ty.Ty
-import org.rust.lang.core.types.ty.TyFunction
+import org.rust.lang.core.types.ty.TyFunctionBase
 import org.rust.lang.core.types.type
 
 val RsLambdaExpr.async: PsiElement?
@@ -26,4 +26,4 @@ val RsLambdaExpr.valueParameters: List<RsValueParameter>
     get() = valueParameterList.valueParameterList
 
 val RsLambdaExpr.returnType: Ty?
-    get() = (type as? TyFunction)?.retType
+    get() = (type as? TyFunctionBase)?.retType
diff --git a/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt b/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt
index a447d47174b..2e5b3cf5826 100644
--- a/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt
+++ b/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt
@@ -173,9 +173,11 @@ interface ParamEnv {
                             add(TraitRef(TyTypeParameter.self(), owner.trait.withDefaultSubst()))
                             addAll(owner.trait.bounds)
                         }
+
                         is RsAbstractableOwner.Impl -> {
                             addAll(owner.impl.bounds)
                         }
+
                         else -> Unit
                     }
                 }
@@ -217,6 +219,7 @@ class LazyParamEnv(private val parentItem: RsGenericDeclaration) : ParamEnv {
                 } else {
                     emptySequence()
                 }
+
                 TyTypeParameter.Self -> if (parentItem !is RsTraitOrImpl) {
                     parentItem.whereClause?.wherePredList.orEmpty().asSequence()
                         .filter { (it.typeReference?.skipParens() as? RsPathType)?.path?.kind == PathKind.CSELF }
@@ -302,11 +305,13 @@ class ImplLookup(
                 ty.getTraitBoundsTransitively().mapTo(implsAndTraits) { TraitImplSource.Object(it.element) }
                 findExplicitImpls(ty) { implsAndTraits += it.explicitImpl; false }
             }
-            is TyFunction -> {
+
+            is TyFunctionBase -> {
                 findExplicitImpls(ty) { implsAndTraits += it.explicitImpl; false }
                 implsAndTraits += fnTraits.map { TraitImplSource.Object(it) }
                 listOfNotNull(items.Clone, items.Copy).mapTo(implsAndTraits) { TraitImplSource.Builtin(it) }
             }
+
             is TyAnon -> {
                 ty.getTraitBoundsTransitively()
                     .distinctBy { it.element }
@@ -317,6 +322,7 @@ class ImplLookup(
                     }
                 }
             }
+
             is TyProjection -> {
                 val subst = ty.trait.subst + ty.target.subst + mapOf(TyTypeParameter.self() to ty.type).toTypeSubst()
                 implsAndTraits += ty.trait.element.bounds.asSequence()
@@ -326,6 +332,7 @@ class ImplLookup(
                     .distinct()
 
             }
+
             is TyUnknown -> Unit
             else -> {
                 implsAndTraits += findDerivedTraits(ty).map { TraitImplSource.Derived(it) }
@@ -586,6 +593,7 @@ class ImplLookup(
         return when {
             other is BuiltinCandidate && !other.hasNested
                 || other == ConstDestructCandidate -> true
+
             victim is BuiltinCandidate && !victim.hasNested
                 || victim == ConstDestructCandidate -> false
 
@@ -661,6 +669,7 @@ class ImplLookup(
                 assembleCandidatesFromImpls(ref, candidates)
                 assembleBuiltinBoundCandidates(copyCloneConditions(ref.selfTy), candidates)
             }
+
             items.Sized -> assembleBuiltinBoundCandidates(sizedConditions(ref), candidates)
             items.Unsize -> assembleCandidatesForUnsizing(ref, candidates)
             items.Destruct -> candidates.list.add(ConstDestructCandidate)
@@ -693,6 +702,7 @@ class ImplLookup(
             is BuiltinImplConditions.Where -> {
                 candidates.list += BuiltinCandidate(hasNested = conditions.nested.isNotEmpty())
             }
+
             BuiltinImplConditions.None -> Unit
             BuiltinImplConditions.Ambiguous -> candidates.ambiguous = true
         }
@@ -701,13 +711,15 @@ class ImplLookup(
     // https://github.com/rust-lang/rust/blob/3a90bedb332d/compiler/rustc_trait_selection/src/traits/select/mod.rs#L1820
     private fun copyCloneConditions(selfTy: Ty): BuiltinImplConditions {
         return when (selfTy) {
-            is TyInfer.IntVar, is TyInfer.FloatVar, is TyFunction, TyUnknown, is TyUnit -> {
+            is TyInfer.IntVar, is TyInfer.FloatVar, is TyFunctionBase, TyUnknown, is TyUnit -> {
                 BuiltinImplConditions.Where(emptyList())
             }
+
             is TyInteger, is TyFloat, is TyBool, is TyChar, is TyPointer, is TyNever, is TyReference, is TyArray -> {
                 // Implementations provided in libcore
                 BuiltinImplConditions.None
             }
+
             is TyTuple -> BuiltinImplConditions.Where(selfTy.types)
             else -> BuiltinImplConditions.None
         }
@@ -720,7 +732,7 @@ class ImplLookup(
             is TyInfer.FloatVar,
             is TyNumeric,
             is TyBool,
-            is TyFunction,
+            is TyFunctionBase,
             is TyPointer,
             is TyReference,
             is TyChar,
@@ -755,7 +767,7 @@ class ImplLookup(
 
     // TODO simplify
     private fun getTyFunctionImpls(ty: Ty): Collection<BoundElement<RsTraitItem>> {
-        if (ty is TyFunction) {
+        if (ty is TyFunctionBase) {
             val fnOnceOutput = fnOnceOutput
             val args = if (ty.paramTypes.isEmpty()) TyUnit.INSTANCE else TyTuple(ty.paramTypes)
             val assoc = if (fnOnceOutput != null) mapOf(fnOnceOutput to ty.retType) else emptyMap()
@@ -887,24 +899,31 @@ class ImplLookup(
             is BuiltinCandidate -> {
                 SelectionResult.Ok(confirmBuiltinCandidate(ref, recursionDepth, candidate.hasNested))
             }
+
             is ParamCandidate -> {
                 SelectionResult.Ok(confirmParamCandidate(ref, candidate))
             }
+
             is ImplCandidate -> {
                 SelectionResult.Ok(confirmImplCandidate(ref, candidate, recursionDepth))
             }
+
             is ProjectionCandidate -> {
                 SelectionResult.Ok(confirmProjectionCandidate(ref, candidate))
             }
+
             ObjectCandidate -> {
                 SelectionResult.Ok(confirmObjectCandidate(ref))
             }
+
             BuiltinUnsizeCandidate -> {
                 confirmBuiltinUnsizeCandidate(ref, recursionDepth)
             }
+
             is FnPointerCandidate -> {
                 SelectionResult.Ok(confirmFnPointerCandidate(ref))
             }
+
             ConstDestructCandidate -> {
                 SelectionResult.Ok(Selection(ref.trait.element, emptyList()))
             }
@@ -957,6 +976,7 @@ class ImplLookup(
         when (candidate) {
             is ImplCandidate.DerivedTrait ->
                 return confirmDerivedCandidate(ref, candidate, recursionDepth)
+
             is ImplCandidate.ExplicitImpl -> Unit
         }
         testAssert { !candidate.formalSelfTy.containsTyOfClass(TyInfer::class.java) }
@@ -1232,7 +1252,8 @@ class ImplLookup(
     }
 
     private fun lookupAssocTypeInSelection(selection: Selection, assocDef: BoundElement<RsTypeAlias>): Ty? {
-        val assocImpl = selection.impl.associatedTypesTransitively.find { it.name == assocDef.element.name } ?: return null
+        val assocImpl = selection.impl.associatedTypesTransitively.find { it.name == assocDef.element.name }
+            ?: return null
         val subst = substFromTraitToImpl(assocDef, assocImpl)
         return assocImpl.typeReference?.rawType?.substitute(selection.subst + subst)
     }
@@ -1273,8 +1294,9 @@ class ImplLookup(
     fun findOverloadedOpImpl(lhsType: Ty, rhsType: Ty, op: OverloadableBinaryOperator): RsTraitOrImpl? =
         selectOverloadedOp(lhsType, rhsType, op).ok()?.impl
 
-    fun asTyFunction(ty: Ty): TyWithObligations<TyFunction>? {
-        return (ty as? TyFunction)?.withObligations() ?: run {
+    // TODO: return FnSig instead
+    fun asTyFunction(ty: Ty): TyWithObligations<TyFunctionBase>? {
+        return (ty as? TyFunctionBase)?.withObligations() ?: run {
             val output = fnOnceOutput ?: return@run null
 
             val inputArgVar = TyInfer.TyVar()
@@ -1282,13 +1304,20 @@ class ImplLookup(
                 .mapNotNull { ctx.commitIfNotNull { selectProjection(it to output, ty, inputArgVar).ok() } }
                 .firstOrNull() ?: return@run null
             TyWithObligations(
-                TyFunction((ctx.shallowResolve(inputArgVar) as? TyTuple)?.types.orEmpty(), ok.value),
+                TyFunctionPointer(
+                    FnSig(
+                        (ctx.shallowResolve(inputArgVar) as? TyTuple)?.types.orEmpty(),
+                        ok.value,
+                        Unsafety.Normal,
+                        false
+                    )
+                ),
                 ok.obligations
             )
         }
     }
 
-    fun asTyFunction(ref: BoundElement<RsTraitItem>): TyFunction? {
+    fun asTyFunction(ref: BoundElement<RsTraitItem>): TyFunctionBase? {
         return ref.asFunctionType
     }
 
@@ -1334,13 +1363,13 @@ class ImplLookup(
         return canSelect(TraitRef(this, trait.withSubst(*subst)))
     }
 
-    private val BoundElement<RsTraitItem>.asFunctionType: TyFunction?
+    private val BoundElement<RsTraitItem>.asFunctionType: TyFunctionBase?
         get() {
             val outputParam = fnOnceOutput ?: return null
             val param = element.typeParamSingle ?: return null
             val argumentTypes = ((subst[param] ?: TyUnknown) as? TyTuple)?.types.orEmpty()
             val outputType = (assoc[outputParam] ?: TyUnit.INSTANCE)
-            return TyFunction(argumentTypes, outputType)
+            return TyFunctionPointer(FnSig(argumentTypes, outputType))
         }
 
     companion object {
diff --git a/src/main/kotlin/org/rust/lang/core/stubs/StubImplementations.kt b/src/main/kotlin/org/rust/lang/core/stubs/StubImplementations.kt
index 6f05c912146..3358cdec08f 100644
--- a/src/main/kotlin/org/rust/lang/core/stubs/StubImplementations.kt
+++ b/src/main/kotlin/org/rust/lang/core/stubs/StubImplementations.kt
@@ -1102,7 +1102,7 @@ class RsForeignModStub(
             return RsForeignModStub(
                 parentStub, this,
                 flags, procMacroInfo,
-                abi = psi.externAbi.litExpr?.stringValue,
+                abi = psi.abi,
             )
         }
     }
diff --git a/src/main/kotlin/org/rust/lang/core/thir/Mirror.kt b/src/main/kotlin/org/rust/lang/core/thir/Mirror.kt
index 85eebffff90..bd2733d4a9e 100644
--- a/src/main/kotlin/org/rust/lang/core/thir/Mirror.kt
+++ b/src/main/kotlin/org/rust/lang/core/thir/Mirror.kt
@@ -229,5 +229,8 @@ private fun applyAdjustment(psiExpr: RsElement, thirExpr: ThirExpr, adjustment:
         is Adjustment.Deref -> TODO()
         is Adjustment.MutToConstPointer -> TODO()
         is Adjustment.Unsize -> TODO()
+        is Adjustment.ClosureFnPointer -> TODO()
+        is Adjustment.ReifyFnPointer -> TODO()
+        is Adjustment.UnsafeFnPointer -> TODO()
     }
 }
diff --git a/src/main/kotlin/org/rust/lang/core/types/TyFingerprint.kt b/src/main/kotlin/org/rust/lang/core/types/TyFingerprint.kt
index c10a763bd45..cea82ef0d2d 100644
--- a/src/main/kotlin/org/rust/lang/core/types/TyFingerprint.kt
+++ b/src/main/kotlin/org/rust/lang/core/types/TyFingerprint.kt
@@ -75,7 +75,7 @@ data class TyFingerprint constructor(
             is TyReference -> create(type.referenced)
             is TyTuple -> TyFingerprint("(tuple)")
             is TyPrimitive -> TyFingerprint(type.toString())
-            is TyFunction -> TyFingerprint("fn()")
+            is TyFunctionBase -> TyFingerprint("fn()")
             is TyTraitObject -> TyFingerprint("dyn T")
             is TyInfer.IntVar -> ANY_INTEGER_FINGERPRINT
             is TyInfer.FloatVar -> ANY_FLOAT_FINGERPRINT
diff --git a/src/main/kotlin/org/rust/lang/core/types/infer/TyLowering.kt b/src/main/kotlin/org/rust/lang/core/types/infer/TyLowering.kt
index 70556e18e89..1ea567c6239 100644
--- a/src/main/kotlin/org/rust/lang/core/types/infer/TyLowering.kt
+++ b/src/main/kotlin/org/rust/lang/core/types/infer/TyLowering.kt
@@ -133,8 +133,18 @@ class TyLowering private constructor(
             }
 
             is RsFnPointerType -> {
-                val paramTypes = type.valueParameters.map { p -> p.typeReference?.let { lowerTy(it, null) } ?: TyUnknown }
-                TyFunction(paramTypes, type.retType?.let { it -> it.typeReference?.let { lowerTy(it, null) } ?: TyUnknown } ?: TyUnit.INSTANCE)
+                val paramTypes = type.valueParameters.map { p ->
+                    p.typeReference?.let { lowerTy(it, null) } ?: TyUnknown
+                }
+                TyFunctionPointer(
+                    FnSig(
+                        paramTypes,
+                        type.retType?.let { it -> it.typeReference?.let { lowerTy(it, null) } ?: TyUnknown }
+                            ?: TyUnit.INSTANCE,
+                        Unsafety.fromBoolean(type.isUnsafe),
+                        false
+                    )
+                )
             }
 
             is RsTraitType -> {
@@ -252,6 +262,7 @@ class TyLowering private constructor(
                     val expectedTy = psiParam.typeReference?.normType ?: TyUnknown
                     psiValue.value.toConst(expectedTy, resolver)
                 }
+
                 is RsPsiSubstitution.Value.DefaultValue -> {
                     val expectedTy = psiParam.typeReference?.normType ?: TyUnknown
                     psiValue.value.toConst(expectedTy, resolver).substitute(constSubst.toConstSubst())
diff --git a/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt b/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt
index b2c8af31aaa..6c83c832a2d 100644
--- a/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt
+++ b/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt
@@ -8,6 +8,7 @@ package org.rust.lang.core.types.infer
 import com.intellij.openapi.project.Project
 import com.intellij.psi.util.CachedValueProvider
 import com.intellij.psi.util.CachedValuesManager
+import kotlinx.html.Unsafe
 import org.jetbrains.annotations.TestOnly
 import org.rust.lang.core.psi.*
 import org.rust.lang.core.psi.ext.*
@@ -73,6 +74,30 @@ sealed class Adjustment: TypeFoldable<Adjustment> {
         override fun superFoldWith(folder: TypeFolder): Adjustment = BorrowPointer(target.foldWith(folder) as TyPointer)
     }
 
+    class ClosureFnPointer(
+        override val target: TyFunctionPointer
+    ): Adjustment() {
+        override fun superFoldWith(folder: TypeFolder): Adjustment {
+            return ClosureFnPointer(target.foldWith(folder) as TyFunctionPointer)
+        }
+    }
+
+    class ReifyFnPointer(
+        override val target: TyFunctionPointer
+    ): Adjustment() {
+        override fun superFoldWith(folder: TypeFolder): Adjustment {
+            return ReifyFnPointer(target.foldWith(folder) as TyFunctionPointer)
+        }
+    }
+
+    class UnsafeFnPointer(
+        override val target: TyFunctionPointer
+    ): Adjustment() {
+        override fun superFoldWith(folder: TypeFolder): Adjustment {
+            return UnsafeFnPointer(target.foldWith(folder) as TyFunctionPointer)
+        }
+    }
+
     class MutToConstPointer(override val target: TyPointer) : Adjustment() {
         override fun superFoldWith(folder: TypeFolder): Adjustment = MutToConstPointer(target.foldWith(folder) as TyPointer)
     }
@@ -140,7 +165,7 @@ class RsInferenceResult(
     fun getResolvedMethod(call: RsMethodCall): List<MethodResolveVariant> =
         resolvedMethods[call]?.resolveVariants ?: emptyList()
 
-    fun getResolvedMethodType(call: RsMethodCall): TyFunction? =
+    fun getResolvedMethodType(call: RsMethodCall): TyFunctionBase? =
         resolvedMethods[call]?.type
 
     fun getResolvedMethodSubst(call: RsMethodCall): Substitution =
@@ -429,7 +454,7 @@ class RsInferenceContext(
         resolvedMethods[call] = InferredMethodCallInfo(resolvedTo)
     }
 
-    fun writeResolvedMethodSubst(call: RsMethodCall, subst: Substitution, ty: TyFunction) {
+    fun writeResolvedMethodSubst(call: RsMethodCall, subst: Substitution, ty: TyFunctionBase) {
         resolvedMethods[call]?.let {
             it.subst = subst
             it.type = ty
@@ -582,9 +607,15 @@ class RsInferenceContext(
             ty1 is TyTuple && ty2 is TyTuple && ty1.types.size == ty2.types.size -> {
                 combineTypePairs(ty1.types.zip(ty2.types))
             }
-            ty1 is TyFunction && ty2 is TyFunction && ty1.paramTypes.size == ty2.paramTypes.size -> {
+            ty1 is TyFunctionBase && ty2 is TyFunctionBase && ty1.paramTypes.size == ty2.paramTypes.size
+                && (
+                (ty1 is TyFunctionPointer && ty2 is TyFunctionPointer) ||
+                    (ty1 is TyFunctionDef && ty2 is TyFunctionDef && ty1.def == ty2.def) ||
+                    (ty1 is TyClosure && ty2 is TyClosure && ty1.def == ty2.def)
+                    ) -> {
                 combineTypePairs(ty1.paramTypes.zip(ty2.paramTypes))
                     .and { combineTypes(ty1.retType, ty2.retType) }
+                    .and { combineUnsafety(ty1, ty2) }
             }
             ty1 is TyAdt && ty2 is TyAdt && ty1.item == ty2.item -> {
                 combineTypePairs(ty1.typeArguments.zip(ty2.typeArguments))
@@ -598,6 +629,7 @@ class RsInferenceContext(
             else -> Err(TypeMismatch(ty1, ty2))
         }
 
+
     fun combineConsts(const1: Const, const2: Const): RelateResult {
         return combineConstsResolved(shallowResolve(const1), shallowResolve(const2))
     }
@@ -628,6 +660,9 @@ class RsInferenceContext(
             else -> Err(ConstMismatch(const1, const2))
         }
 
+    private fun combineUnsafety(ty1: TyFunctionBase, ty2: TyFunctionBase): RelateResult =
+        if (ty1.fnSig.unsafety == ty2.fnSig.unsafety) Ok(Unit) else Err(TypeMismatch(ty1, ty2))
+
     fun combineTypePairs(pairs: List<Pair<Ty, Ty>>): RelateResult = combinePairs(pairs, ::combineTypes)
 
     fun combineConstPairs(pairs: List<Pair<Const, Const>>): RelateResult = combinePairs(pairs, ::combineConsts)
@@ -695,10 +730,50 @@ class RsInferenceContext(
                 coerceMutability(inferred.mutability, expected.mutability) -> {
                 coerceReference(inferred, expected)
             }
+
+            inferred is TyClosure && expected is TyFunctionPointer -> {
+                inferred.def.move
+                // TODO: check for captured variables
+                combineTypes(TyFunctionPointer(inferred.fnSig.copy(unsafety = expected.unsafety)), expected).map {
+                    CoerceOk(adjustments = listOf(Adjustment.ClosureFnPointer(expected)))
+                }
+            }
+
+            inferred is TyFunctionDef && expected is TyFunctionPointer -> {
+                // FIXME: Provide better error messages. Current message will only mention function signature, which is not the reason for the error
+                if (inferred.def.isIntrinsic) {
+                    // Intrinsics are not coercible to function pointers
+                    Err(TypeMismatch(inferred, expected))
+                } else if (expected.unsafety == Unsafety.Normal && inferred.def.queryAttributes.hasAttribute("target_feature")) {
+                    // Safe `#[target_feature]` functions are not assignable to safe fn pointers (RFC 2396).
+                    Err(TypeMismatch(inferred, expected))
+                } else {
+                    val adjustments = listOf<Adjustment>(Adjustment.ReifyFnPointer(TyFunctionPointer(inferred.fnSig)))
+                    coerceFromSafeFn(inferred, expected, adjustments)
+                }
+            }
+
+            inferred is TyFunctionPointer && expected is TyFunctionPointer -> {
+                coerceFromSafeFn(inferred, expected)
+            }
+
             else -> combineTypes(inferred, expected).into()
         }
     }
 
+    private fun coerceFromSafeFn(inferred: TyFunctionBase, expected: TyFunctionPointer, adjustments: List<Adjustment> = emptyList()): RsResult<CoerceOk, TypeError> {
+        val finalAdjustments = adjustments.toMutableList()
+        val inf = if (inferred.unsafety == Unsafety.Normal && expected.unsafety == Unsafety.Unsafe) {
+            finalAdjustments.add(Adjustment.UnsafeFnPointer(expected))
+            TyFunctionPointer(inferred.fnSig.copy(unsafety = Unsafety.Unsafe))
+        } else {
+            TyFunctionPointer(inferred.fnSig)
+        }
+        return combineTypes(inf, expected).map {
+            CoerceOk(adjustments = finalAdjustments)
+        }
+    }
+
     // &[T; n] or &mut [T; n] -> &[T]
     // or &mut [T; n] -> &mut [T]
     // or &Concrete -> &Trait, etc.
@@ -1375,12 +1450,12 @@ sealed class ResolvedPath {
 data class InferredMethodCallInfo(
     val resolveVariants: List<MethodResolveVariant>,
     var subst: Substitution = emptySubstitution,
-    var type: TyFunction? = null,
+    var type: TyFunctionBase? = null,
 ) : TypeFoldable<InferredMethodCallInfo> {
     override fun superFoldWith(folder: TypeFolder): InferredMethodCallInfo = InferredMethodCallInfo(
         resolveVariants,
         subst.foldValues(folder),
-        type?.foldWith(folder) as? TyFunction
+        type?.foldWith(folder) as? TyFunctionBase
     )
 
     override fun superVisitWith(visitor: TypeVisitor): Boolean {
diff --git a/src/main/kotlin/org/rust/lang/core/types/infer/TypeInferenceWalker.kt b/src/main/kotlin/org/rust/lang/core/types/infer/TypeInferenceWalker.kt
index 6d081cff83f..b7275298bbb 100644
--- a/src/main/kotlin/org/rust/lang/core/types/infer/TypeInferenceWalker.kt
+++ b/src/main/kotlin/org/rust/lang/core/types/infer/TypeInferenceWalker.kt
@@ -310,7 +310,7 @@ class RsTypeInferenceWalker(
                 ty ?: when (val ety = expected.tyAsNullable(ctx)) {
                     is TyInteger -> ety
                     is TyChar -> TyInteger.U8.INSTANCE
-                    is TyPointer, is TyFunction -> TyInteger.USize.INSTANCE
+                    is TyPointer, is TyFunctionBase -> TyInteger.USize.INSTANCE
                     else -> TyInfer.IntVar()
                 }
             }
@@ -420,7 +420,8 @@ class RsTypeInferenceWalker(
             val tupleFields = ((implForTy as? TyAdt)?.item as? RsFieldsOwner)?.tupleFields
             return if (tupleFields != null) {
                 // Treat tuple constructor as a function
-                TyFunction(tupleFields.tupleFieldDeclList.map { it.typeReference.rawType }, implForTy)
+                // TODO: This is actually TyFunctionBase, but can't find RsFunction
+                TyFunctionPointer(FnSig(tupleFields.tupleFieldDeclList.map { it.typeReference.rawType }, implForTy))
                     .substitute(implForTy.typeParameterValues)
             } else {
                 implForTy
@@ -502,7 +503,8 @@ class RsTypeInferenceWalker(
         val tupleFields = (element as? RsFieldsOwner)?.tupleFields
         return if (tupleFields != null) {
             // Treat tuple constructor as a function
-            TyFunction(tupleFields.tupleFieldDeclList.map { it.typeReference.rawType }, type)
+            // TODO: This is actually TyFunctionBase, but can't find RsFunction
+            TyFunctionPointer(FnSig(tupleFields.tupleFieldDeclList.map { it.typeReference.rawType }, type))
         } else {
             type
         }.substitute(typeParameters).foldWith(associatedTypeNormalizer)
@@ -633,7 +635,7 @@ class RsTypeInferenceWalker(
         val calleeType = ctx.resolveTypeVarsIfPossible(
             (derefTy?.register() ?: unknownTyFunction(argExprs.size))
                 .foldWith(associatedTypeNormalizer)
-        ) as TyFunction
+        ) as TyFunctionBase
         val expectedInputTys = expectedInputsForExpectedOutput(expected, calleeType.retType, calleeType.paramTypes)
         inferArgumentTypes(calleeType.paramTypes, expectedInputTys, argExprs)
         return calleeType.retType
@@ -723,7 +725,7 @@ class RsTypeInferenceWalker(
 
         val methodType = (callee.element.type)
             .substitute(newSubst)
-            .foldWith(associatedTypeNormalizer) as TyFunction
+            .foldWith(associatedTypeNormalizer) as TyFunctionBase
         // drop first element of paramTypes because it's `self` param
         // and it doesn't have value in `methodCall.valueArgumentList.exprList`
         val formalInputTys = methodType.paramTypes.drop(1)
@@ -837,8 +839,9 @@ class RsTypeInferenceWalker(
         }
     }
 
-    private fun unknownTyFunction(arity: Int): TyFunction =
-        TyFunction(generateSequence { TyUnknown }.take(arity).toList(), TyUnknown)
+    // TODO: return FnSig
+    private fun unknownTyFunction(arity: Int): TyFunctionPointer =
+        TyFunctionPointer(FnSig(generateSequence { TyUnknown }.take(arity).toList(), TyUnknown))
 
     private fun inferArgumentTypes(
         formalInputTys: List<Ty>,
@@ -1339,7 +1342,7 @@ class RsTypeInferenceWalker(
         val expectedFnTy = expected
             .tyAsNullable(ctx)
             ?.let(this::deduceLambdaType)
-            ?: TyFunction(generateSequence { TyInfer.TyVar() }.take(params.size).toList(), TyUnknown)
+            ?: TyClosure(expr, FnSig(generateSequence { TyInfer.TyVar() }.take(params.size).toList(), TyUnknown))
         val extendedArgs = expectedFnTy.paramTypes.asSequence().infiniteWithTyUnknown()
         val paramTypes = extendedArgs.zip(params.asSequence()).map { (expectedArg, actualArg) ->
             val paramTy = actualArg.typeReference?.rawType?.let { normalizeAssociatedTypesIn(it) } ?: expectedArg
@@ -1359,13 +1362,13 @@ class RsTypeInferenceWalker(
 
         val yieldTy = lambdaBodyContext.yieldTy
         return if (yieldTy == null) {
-            TyFunction(paramTypes, if (expr.isAsync) items.makeFuture(actualRetTy) else actualRetTy)
+            TyClosure(expr, FnSig(paramTypes, if (expr.isAsync) items.makeFuture(actualRetTy) else actualRetTy))
         } else {
             items.makeGenerator(yieldTy, actualRetTy)
         }
     }
 
-    private fun deduceLambdaType(expected: Ty): TyFunction? {
+    private fun deduceLambdaType(expected: Ty): TyFunctionBase? {
         return when (expected) {
             is TyInfer.TyVar -> {
                 fulfill.pendingObligations
@@ -1374,7 +1377,7 @@ class RsTypeInferenceWalker(
                     ?.let { lookup.asTyFunction(it.trait.trait) }
             }
             is TyTraitObject -> lookup.asTyFunction(expected.traits.first()) // TODO: Use all trait bounds
-            is TyFunction -> expected
+            is TyFunctionBase -> expected
             is TyAnon -> {
                 val trait = expected.traits.find { it.element in listOf(items.Fn, items.FnMut, items.FnOnce) }
                 trait?.let { lookup.asTyFunction(it) }
@@ -1580,19 +1583,8 @@ private fun RsSelfParameter.typeOfValue(selfType: Ty): Ty {
 
 }
 
-val RsFunction.type: TyFunction
-    get() {
-        val paramTypes = mutableListOf<Ty>()
-
-        val self = selfParameter
-        if (self != null) {
-            paramTypes += self.typeOfValue
-        }
-
-        paramTypes += valueParameters.map { it.typeReference?.rawType ?: TyUnknown }
-
-        return TyFunction(paramTypes, if (isAsync) knownItems.makeFuture(rawReturnType) else rawReturnType)
-    }
+val RsFunction.type: TyFunctionDef
+    get() = TyFunctionDef(this)
 
 private fun Sequence<Ty>.infiniteWithTyUnknown(): Sequence<Ty> =
     this + generateSequence { TyUnknown }
@@ -1646,7 +1638,7 @@ private fun KnownItems.makeGenerator(yieldTy: Ty, returnTy: Ty): Ty {
     return TyAnon(null, listOfNotNull(boundGenerator, Sized?.withSubst()))
 }
 
-private fun KnownItems.makeFuture(outputTy: Ty): Ty {
+fun KnownItems.makeFuture(outputTy: Ty): Ty {
     val futureTrait = Future ?: return TyUnknown
     val boundFuture = futureTrait.substAssocType("Output", outputTy)
     return TyAnon(null, listOfNotNull(boundFuture, Sized?.withSubst()))
diff --git a/src/main/kotlin/org/rust/lang/core/types/ty/FnSig.kt b/src/main/kotlin/org/rust/lang/core/types/ty/FnSig.kt
new file mode 100644
index 00000000000..bd7c7fea641
--- /dev/null
+++ b/src/main/kotlin/org/rust/lang/core/types/ty/FnSig.kt
@@ -0,0 +1,69 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.lang.core.types.ty
+
+import org.rust.lang.core.psi.RsFunction
+import org.rust.lang.core.psi.RsTypeAlias
+import org.rust.lang.core.psi.ext.*
+import org.rust.lang.core.resolve.knownItems
+import org.rust.lang.core.types.BoundElement
+import org.rust.lang.core.types.infer.*
+import org.rust.lang.core.types.mergeFlags
+import org.rust.lang.core.types.rawType
+
+data class FnSig(
+    val paramTypes: List<Ty>,
+    val retType: Ty,
+    val unsafety: Unsafety = Unsafety.Normal,
+    val cVariadic: Boolean = false
+) : TypeFoldable<FnSig> {
+    override fun superFoldWith(folder: TypeFolder): FnSig {
+        return FnSig(
+            paramTypes.map { it.foldWith(folder) },
+            retType.foldWith(folder),
+            unsafety,
+            cVariadic
+        )
+    }
+
+    override fun superVisitWith(visitor: TypeVisitor): Boolean {
+        return paramTypes.any { it.visitWith(visitor) } || retType.visitWith(visitor)
+    }
+
+    fun isEquivalentToInner(other: FnSig): Boolean {
+        if (this === other) return true
+
+        if (paramTypes.size != other.paramTypes.size) return false
+        for (i in paramTypes.indices) {
+            if (!paramTypes[i].isEquivalentTo(other.paramTypes[i])) return false
+        }
+        if (!retType.isEquivalentTo(other.retType)) return false
+
+        if (unsafety != other.unsafety) return false
+        return cVariadic == other.cVariadic
+    }
+
+    companion object {
+        fun of(function: RsFunction): FnSig {
+            val paramTypes = mutableListOf<Ty>()
+
+            val self = function.selfParameter
+            if (self != null) {
+                paramTypes += self.typeOfValue
+            }
+
+            paramTypes += function.valueParameters.map { it.typeReference?.rawType ?: TyUnknown }
+
+            val rawReturnType = function.rawReturnType
+            return FnSig(
+                paramTypes,
+                if (function.isAsync) function.knownItems.makeFuture(rawReturnType) else rawReturnType,
+                Unsafety.fromBoolean(function.isActuallyUnsafe),
+                function.isVariadic
+            )
+        }
+    }
+}
diff --git a/src/main/kotlin/org/rust/lang/core/types/ty/Ty.kt b/src/main/kotlin/org/rust/lang/core/types/ty/Ty.kt
index 1c252934282..1f2dd9d3d50 100644
--- a/src/main/kotlin/org/rust/lang/core/types/ty/Ty.kt
+++ b/src/main/kotlin/org/rust/lang/core/types/ty/Ty.kt
@@ -136,7 +136,7 @@ private fun pushSubTypes(stack: Deque<Ty>, parentTy: Ty) {
             stack.push(parentTy.elementType)
         is TyTuple ->
             parentTy.types.asReversed().forEach(stack::push)
-        is TyFunction -> {
+        is TyFunctionBase -> {
             stack.push(parentTy.retType)
             parentTy.paramTypes.asReversed().forEach(stack::push)
         }
@@ -209,5 +209,5 @@ val Ty.isScalar: Boolean
         this is TyBool ||
         this is TyChar ||
         this is TyUnit ||
-        this is TyFunction || // really TyFnDef & TyFnPtr
+        this is TyFunctionBase || // really TyFnDef & TyFnPtr
         this is TyPointer
diff --git a/src/main/kotlin/org/rust/lang/core/types/ty/TyClosure.kt b/src/main/kotlin/org/rust/lang/core/types/ty/TyClosure.kt
new file mode 100644
index 00000000000..c0ea0289bcd
--- /dev/null
+++ b/src/main/kotlin/org/rust/lang/core/types/ty/TyClosure.kt
@@ -0,0 +1,34 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.lang.core.types.ty
+
+import org.rust.lang.core.psi.RsLambdaExpr
+import org.rust.lang.core.psi.RsTypeAlias
+import org.rust.lang.core.types.BoundElement
+import org.rust.lang.core.types.infer.TypeFolder
+import org.rust.lang.core.types.infer.TypeVisitor
+import org.rust.lang.core.types.mergeFlags
+
+class TyClosure(
+    val def: RsLambdaExpr,
+    fnSig: FnSig,
+    override val aliasedBy: BoundElement<RsTypeAlias>? = null
+) : TyFunctionBase(fnSig) {
+    override fun superFoldWith(folder: TypeFolder): TyClosure =
+        TyClosure(def, fnSig.superFoldWith(folder), aliasedBy)
+
+    override fun superVisitWith(visitor: TypeVisitor): Boolean =
+        fnSig.superVisitWith(visitor)
+
+    override fun withAlias(aliasedBy: BoundElement<RsTypeAlias>): Ty = TyClosure(def, fnSig, aliasedBy)
+
+    override fun isEquivalentToInner(other: Ty): Boolean {
+        if (this === other) return true
+        if (other !is TyClosure) return false
+        if (def != other.def) return false
+        return fnSig.isEquivalentToInner(other.fnSig)
+    }
+}
diff --git a/src/main/kotlin/org/rust/lang/core/types/ty/TyFunctionBase.kt b/src/main/kotlin/org/rust/lang/core/types/ty/TyFunctionBase.kt
new file mode 100644
index 00000000000..f7c5859e57b
--- /dev/null
+++ b/src/main/kotlin/org/rust/lang/core/types/ty/TyFunctionBase.kt
@@ -0,0 +1,23 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.lang.core.types.ty
+
+import org.rust.lang.core.psi.RsTypeAlias
+import org.rust.lang.core.types.BoundElement
+import org.rust.lang.core.types.mergeFlags
+
+abstract class TyFunctionBase(
+    val fnSig: FnSig
+) : Ty(mergeFlags(fnSig.paramTypes) or fnSig.retType.flags) {
+    val paramTypes: List<Ty>
+        get() = fnSig.paramTypes
+
+    val retType: Ty
+        get() = fnSig.retType
+
+    val unsafety: Unsafety
+        get() = fnSig.unsafety
+}
diff --git a/src/main/kotlin/org/rust/lang/core/types/ty/TyFunctionDef.kt b/src/main/kotlin/org/rust/lang/core/types/ty/TyFunctionDef.kt
new file mode 100644
index 00000000000..0cbd463a7ce
--- /dev/null
+++ b/src/main/kotlin/org/rust/lang/core/types/ty/TyFunctionDef.kt
@@ -0,0 +1,34 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.lang.core.types.ty
+
+import org.rust.lang.core.psi.RsFunction
+import org.rust.lang.core.psi.RsTypeAlias
+import org.rust.lang.core.types.BoundElement
+import org.rust.lang.core.types.infer.TypeFolder
+import org.rust.lang.core.types.infer.TypeVisitor
+
+class TyFunctionDef(
+    val def: RsFunction,
+    fnSig: FnSig = FnSig.of(def),
+    override val aliasedBy: BoundElement<RsTypeAlias>? = null
+) : TyFunctionBase(fnSig) {
+    override fun superFoldWith(folder: TypeFolder): Ty =
+        TyFunctionDef(def, fnSig.superFoldWith(folder), aliasedBy)
+
+    override fun superVisitWith(visitor: TypeVisitor): Boolean =
+        fnSig.superVisitWith(visitor)
+
+    override fun withAlias(aliasedBy: BoundElement<RsTypeAlias>): TyFunctionDef = TyFunctionDef(def, fnSig, aliasedBy)
+
+    override fun isEquivalentToInner(other: Ty): Boolean {
+        if (this === other) return true
+        if (other !is TyFunctionDef) return false
+
+        if (def != other.def) return false
+        return fnSig.isEquivalentToInner(other.fnSig)
+    }
+}
diff --git a/src/main/kotlin/org/rust/lang/core/types/ty/TyFunction.kt b/src/main/kotlin/org/rust/lang/core/types/ty/TyFunctionPointer.kt
similarity index 51%
rename from src/main/kotlin/org/rust/lang/core/types/ty/TyFunction.kt
rename to src/main/kotlin/org/rust/lang/core/types/ty/TyFunctionPointer.kt
index f77cd1d9d77..291e7d989c7 100644
--- a/src/main/kotlin/org/rust/lang/core/types/ty/TyFunction.kt
+++ b/src/main/kotlin/org/rust/lang/core/types/ty/TyFunctionPointer.kt
@@ -11,30 +11,22 @@ import org.rust.lang.core.types.infer.TypeFolder
 import org.rust.lang.core.types.infer.TypeVisitor
 import org.rust.lang.core.types.mergeFlags
 
-data class TyFunction(
-    val paramTypes: List<Ty>,
-    val retType: Ty,
+class TyFunctionPointer(
+    fnSig: FnSig,
     override val aliasedBy: BoundElement<RsTypeAlias>? = null
-) : Ty(mergeFlags(paramTypes) or retType.flags) {
-
+) : TyFunctionBase(fnSig) {
     override fun superFoldWith(folder: TypeFolder): Ty =
-        TyFunction(paramTypes.map { it.foldWith(folder) }, retType.foldWith(folder), aliasedBy?.foldWith(folder))
+        TyFunctionPointer(fnSig.superFoldWith(folder), aliasedBy)
 
     override fun superVisitWith(visitor: TypeVisitor): Boolean =
-        paramTypes.any { it.visitWith(visitor) } || retType.visitWith(visitor)
+        fnSig.superVisitWith(visitor)
 
-    override fun withAlias(aliasedBy: BoundElement<RsTypeAlias>): Ty = copy(aliasedBy = aliasedBy)
+    override fun withAlias(aliasedBy: BoundElement<RsTypeAlias>): TyFunctionPointer = TyFunctionPointer(fnSig, aliasedBy)
 
     override fun isEquivalentToInner(other: Ty): Boolean {
         if (this === other) return true
-        if (other !is TyFunction) return false
-
-        if (paramTypes.size != other.paramTypes.size) return false
-        for (i in paramTypes.indices) {
-            if (!paramTypes[i].isEquivalentTo(other.paramTypes[i])) return false
-        }
-        if (!retType.isEquivalentTo(other.retType)) return false
+        if (other !is TyFunctionPointer) return false
 
-        return true
+        return fnSig.isEquivalentToInner(other.fnSig)
     }
 }
diff --git a/src/main/kotlin/org/rust/lang/core/types/ty/Unsafety.kt b/src/main/kotlin/org/rust/lang/core/types/ty/Unsafety.kt
new file mode 100644
index 00000000000..330d1743ed2
--- /dev/null
+++ b/src/main/kotlin/org/rust/lang/core/types/ty/Unsafety.kt
@@ -0,0 +1,17 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.lang.core.types.ty
+
+enum class Unsafety {
+    Unsafe,
+    Normal;
+
+    companion object {
+        fun fromBoolean(unsafe: Boolean): Unsafety {
+            return if (unsafe) Unsafe else Normal
+        }
+    }
+}


Test patch:
diff --git a/src/test/kotlin/org/rust/ide/hints/type/RsExpressionTypeProviderTest.kt b/src/test/kotlin/org/rust/ide/hints/type/RsExpressionTypeProviderTest.kt
index 87909be9b4c..2adbf4de955 100644
--- a/src/test/kotlin/org/rust/ide/hints/type/RsExpressionTypeProviderTest.kt
+++ b/src/test/kotlin/org/rust/ide/hints/type/RsExpressionTypeProviderTest.kt
@@ -102,7 +102,7 @@ class RsExpressionTypeProviderTest : RsTestBase() {
         fn main() {
             let _ = /*caret*/foo(0);
         }
-    """, "fn(i32) -> i32, i32")
+    """, "fn(i32) -> i32 {foo}, i32")
 
     fun `test inside macro call`() = doTest("""
         macro_rules! as_is { ($($ t:tt)*) => { $($ t)* } }
@@ -114,7 +114,7 @@ class RsExpressionTypeProviderTest : RsTestBase() {
                 /*caret*/foo();
             }
         }
-    """, "fn() -> i32, i32")
+    """, "fn() -> i32 {foo}, i32")
 
     private fun doTest(@Language("Rust") code: String, type: String) {
         InlineFile(code).withCaret()
diff --git a/src/test/kotlin/org/rust/ide/inspections/typecheck/RsTypeCheckInspectionTest.kt b/src/test/kotlin/org/rust/ide/inspections/typecheck/RsTypeCheckInspectionTest.kt
index 1236af5fc45..452bbd9e91a 100644
--- a/src/test/kotlin/org/rust/ide/inspections/typecheck/RsTypeCheckInspectionTest.kt
+++ b/src/test/kotlin/org/rust/ide/inspections/typecheck/RsTypeCheckInspectionTest.kt
@@ -5,6 +5,8 @@
 
 package org.rust.ide.inspections.typecheck
 
+import org.junit.ComparisonFailure
+import org.junit.Test
 import org.rust.MockRustcVersion
 import org.rust.ProjectDescriptor
 import org.rust.WithStdlibRustProjectDescriptor
@@ -441,4 +443,135 @@ class RsTypeCheckInspectionTest : RsInspectionsTestBase(RsTypeCheckInspection::c
         fn foo(_: &[u8]) {}
         fn bar<T>(t: T) -> T { t }
     """)
+
+    fun `test different closure`() = checkErrors("""
+        fn main() {
+            let mut a = |x: i32| x;
+            a = /*error*/|x: i32| x/*error**/;
+        }
+    """)
+
+    fun `test closure to function pointer`() = checkErrors("""
+        fn main() {
+            let a: fn(i32) -> i32 = |x: i32| x;
+        }
+    """)
+
+    fun `test closure to unsafe function pointer`() = checkErrors("""
+        fn main() {
+            let a: unsafe fn(i32) -> i32 = |x: i32| x;
+        }
+    """)
+
+
+    fun `test closure to function def`() = checkErrors("""
+        fn foo() {}
+        fn main() {
+            let mut a = foo;
+            a = /*error*/|| {}/*error**/;
+        }
+    """)
+
+    fun `test function def to function pointer`() = checkErrors("""
+        fn main() {
+            let a: fn() = foo;
+        }
+        fn foo() {}
+    """)
+
+    fun `test unsafe function def to normal function pointer`() = checkErrors("""
+        fn main() {
+            let a: fn() = /*error descr="mismatched types [E0308]expected `fn()`, found `unsafe fn() {foo}`"*/foo/*error**/;
+        }
+        unsafe fn foo() {}
+    """)
+
+
+    fun `test function def to unsafe function pointer`() = checkErrors("""
+        fn main() {
+            let a: unsafe fn() = foo;
+        }
+        fn foo() {}
+    """)
+
+    fun `test different function`() = checkErrors("""
+        fn main() {
+            let mut a = foo;
+            a = /*error*/bar/*error**/;
+        }
+        fn foo() {}
+        fn bar() {}
+    """)
+
+    fun `test function to closure`() = checkErrors("""
+        fn main() {
+            let a = foo;
+            let mut b = || {};
+            b = /*error*/a/*error**/;
+        }
+        fn foo() {}
+    """)
+
+    fun `test normal function pointer to unsafe function pointer`() = checkErrors("""
+        fn foo() {}
+        fn main() {
+            let a: fn() = foo;
+            let b: unsafe fn() = a;
+        }
+    """)
+
+    fun `test function pointer mismatch`() = checkErrors(
+        """
+        fn main() {
+            let a: fn() = /*error descr="mismatched types [E0308]expected `fn()`, found `fn(i32) {f_a}`"*/f_a/*error**/;
+            let b: fn() = /*error descr="mismatched types [E0308]expected `fn()`, found `fn() -> i64 {f_b}`"*/f_b/*error**/;
+            let c: fn(i8) = /*error descr="mismatched types [E0308]expected `fn(i8)`, found `fn() {f_c}`"*/f_c/*error**/;
+            let d: fn(i8) = /*error descr="mismatched types [E0308]expected `fn(i8)`, found `fn(i32) {f_a}`"*/f_a/*error**/;
+        }
+        fn f_a(b: i32) {}
+        fn f_b() -> i64 { 1i64 }
+        fn f_c() {}
+    """
+    )
+
+    // FIXME: make this test pass by checking for captures in the closure
+    @Test(expected = ComparisonFailure::class)
+    fun `test capturing closure`() = checkErrors("""
+        fn main() {
+            let x = 1;
+            let a = || x;
+            let b: fn() -> i32 = /*error*/a/*error**/;
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test intrinsics don't coerce to function pointer`() = checkErrors("""
+        #![feature(core_intrinsics)]
+
+        use std::intrinsics::breakpoint;
+
+        fn main() {
+            let a: unsafe fn() = /*error*/breakpoint/*error**/;
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test unsafe target_feature function coerces to unsafe function pointer`() = checkErrors("""
+        #[target_feature(enable = "avx2")]
+        unsafe fn foo_avx2() {}
+
+        fn main() {
+            let a: unsafe fn() = foo_avx2;
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test normal target_feature function doesn't coerce to normal function pointer`() = checkErrors("""
+        #[target_feature(enable = "avx2")]
+        fn foo_avx2() {}
+
+        fn main() {
+            let a: fn() = /*error*/foo_avx2/*error**/;
+        }
+    """)
 }
diff --git a/src/test/kotlin/org/rust/lang/core/type/RsAdjustmentTest.kt b/src/test/kotlin/org/rust/lang/core/type/RsAdjustmentTest.kt
index 4bcbbd0624c..ddc92c64f71 100644
--- a/src/test/kotlin/org/rust/lang/core/type/RsAdjustmentTest.kt
+++ b/src/test/kotlin/org/rust/lang/core/type/RsAdjustmentTest.kt
@@ -556,6 +556,149 @@ class RsAdjustmentTest : RsTestBase() {
         } //^ borrow(&mut [i32; 3]), unsize(&mut [i32])
     """)
 
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test closure to function pointer`() = testExpr("""
+        fn main() {
+            let a: fn() = || {};
+                        //^ closureFnPointer(fn())
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test closure to function pointer with param`() = testExpr("""
+        fn main() {
+            let a: fn(i32) = |b: i32| {};
+                           //^ closureFnPointer(fn(i32))
+        }
+    """)
+
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test closure to function pointer with return value`() = testExpr("""
+        fn main() {
+            let a: fn() -> i32 = || { 0i32 };
+                               //^ closureFnPointer(fn() -> i32)
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test closure to function pointer with param and return value`() = testExpr("""
+        fn main() {
+            let a: fn(i32) -> i64 = |b: i32| { 0i64 };
+                                  //^ closureFnPointer(fn(i32) -> i64)
+        }
+
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test closure to unsafe function pointer`() = testExpr("""
+        fn main() {
+            let a: unsafe fn() = || {};
+                               //^ closureFnPointer(unsafe fn())
+        }
+    """)
+
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test dont coerce closure to another closure`() = testExpr("""
+        fn main() {
+            let mut a = || {};
+            a = || {};
+              //^
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test dont coerce closure to function def`() = testExpr("""
+        fn foo() {}
+        fn main() {
+            let mut a = foo;
+            a = || {};
+              //^
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test function def to function pointer`() = testExpr("""
+        fn foo() {}
+        fn main() {
+            let a: fn() = foo;
+                        //^ reifyFnPointer(fn())
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test function def to unsafe function pointer`() = testExpr("""
+        fn foo() {}
+        fn main() {
+            let a: unsafe fn() = foo;
+                               //^ reifyFnPointer(fn()), unsafeFnPointer(unsafe fn())
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test dont coerce function to another function`() = testExpr("""
+        fn foo() {}
+        fn bar() {}
+        fn main() {
+            let mut a = foo;
+            a = bar;
+              //^
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test dont coerce function to closure`() = testExpr("""
+        fn foo() {}
+        fn main() {
+            let mut a = || {};
+            a = foo;
+              //^
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test function pointer to unsafe function pointer`() = testExpr("""
+        fn foo() {}
+        fn main() {
+            let a: fn() = foo;
+            let b: unsafe fn() = a;
+                               //^ unsafeFnPointer(unsafe fn())
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test no coercion needed for function pointer to function pointer`() = testExpr("""
+        fn foo() {}
+        fn main() {
+            let a: fn() = foo;
+            let b: fn() = a;
+                        //^
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test don't coerce function pointer to function def`() = testExpr("""
+        fn foo() {}
+        fn main() {
+            let a: fn() = foo;
+            let mut b = foo;
+            b = a;
+              //^
+        }
+    """)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test don't coerce function pointer to closure`() = testExpr("""
+        fn foo() {}
+        fn main() {
+            let a: fn() = foo;
+            let mut b = || {};
+            b = a;
+              //^
+        }
+    """)
+
     private fun testExpr(@Language("Rust") code: String) {
         InlineFile(code)
         val (expr, expectedAdjustments) = findElementAndDataInEditor<RsExpr>()
@@ -589,9 +732,12 @@ class RsAdjustmentTest : RsTestBase() {
                     }
                 }
                 is Adjustment.BorrowReference -> "borrow(${it.target})"
-                is Adjustment.BorrowPointer -> "borrow(${it.target})"
+                is Adjustment.BorrowPointer -> "borrow(${it.target})" // FIXME: should be different from BorrowReference
                 is Adjustment.MutToConstPointer -> "mutToConstPtr(${it.target})"
                 is Adjustment.Unsize -> "unsize(${it.target})"
+                is Adjustment.ClosureFnPointer -> "closureFnPointer(${it.target})"
+                is Adjustment.ReifyFnPointer -> "reifyFnPointer(${it.target})"
+                is Adjustment.UnsafeFnPointer -> "unsafeFnPointer(${it.target})"
             }
         }
         assertEquals(expectedAdjustments.replace("), ", ")\n"), adjustmentsStr)
diff --git a/src/test/kotlin/org/rust/lang/core/type/RsFunctionTypeInferenceTest.kt b/src/test/kotlin/org/rust/lang/core/type/RsFunctionTypeInferenceTest.kt
new file mode 100644
index 00000000000..52ad45f5e10
--- /dev/null
+++ b/src/test/kotlin/org/rust/lang/core/type/RsFunctionTypeInferenceTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.lang.core.type
+
+class RsFunctionTypeInferenceTest : RsTypificationTestBase() {
+    fun `test function unification`() = testExpr("""
+        fn foo<T>(t: T) {}
+
+        fn main() {
+            let a = foo;
+            let b = 0;
+            a(b); //^ u8
+            a(1u8);
+        }
+    """)
+
+    fun `test closure unification`() = testExpr("""
+        fn main() {
+            let a = |x| x;
+            let b = 0;
+                  //^ i64
+            a(1i64);
+            a(b);
+        }
+    """)
+
+    fun `test closure to function pointer unification`() = testExpr("""
+        fn main() {
+            let a = |x| {};
+            let b: fn(i64) = a;
+            let c = 1;
+                  //^ i64
+            a(c);
+        }
+    """)
+
+
+    fun `test function def to function pointer unification`() = testExpr("""
+        fn foo<T>(i: T) {}
+        fn main() {
+            let a = foo;
+            let b: fn(i64) = a;
+            let c = 1;
+                  //^ i64
+            a(c);
+        }
+    """)
+}
diff --git a/src/test/kotlin/org/rust/lang/core/type/RsShortTypeRenderingTest.kt b/src/test/kotlin/org/rust/lang/core/type/RsShortTypeRenderingTest.kt
index 1802902eea7..c76059f849e 100644
--- a/src/test/kotlin/org/rust/lang/core/type/RsShortTypeRenderingTest.kt
+++ b/src/test/kotlin/org/rust/lang/core/type/RsShortTypeRenderingTest.kt
@@ -70,6 +70,20 @@ class RsShortTypeRenderingTest : RsTestBase() {
         } //^ Foo<i32>
     """)
 
+    fun `test fn`() = testShortTypeExpr("""
+        fn func(a: i32) -> u64 { 1u64 }
+        fn main() {
+            func;
+        } //^ fn(i32) -> u64 {func}
+    """)
+
+    fun `test unsafe fn`() = testShortTypeExpr("""
+        unsafe fn func(a: i32) -> u64 { 1u64 }
+        fn main() {
+            func;
+        } //^ unsafe fn(i32) -> u64 {func}
+    """)
+
     private fun testShortTypeExpr(@Language("Rust") code: String) {
         InlineFile(code)
         val (expr, expectedType) = findElementAndDataInEditor<RsExpr>()