Repository: JuliaDebug/Cthulhu.jl
Pull request #481 (2023-08-08 04:03:41)

enable dependabot for GitHub actions
This allows to get updates for GitHub actions automatically. I have used this for my own packages, the [Trixi.jl framework](https://github.com/trixi-framework), and the [SciML organization](https://github.com/SciML). After merging this, you could also enable other Dependabot actions in 'Settings -> Code security and analysis -> Dependabot alerts' and '... -> Dependabot security updates'.

See https://github.com/SciML/MuladdMacro.jl/pull/37


Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..d60f070
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,7 @@
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+version: 2
+updates:
+  - package-ecosystem: "github-actions"
+    directory: "/" # Location of package manifests
+    schedule:
+      interval: "monthly"
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index b3ba031..e2e6776 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -14,15 +14,19 @@ jobs:
       matrix:
         version:
           - '1'
-          - '1.9.0'
+          - '1.10.0'
+          - '1.11-nightly'
           - 'nightly'
-          - '1.8.5'
         os:
           - ubuntu-latest
           - macOS-latest
           - windows-latest
         arch:
           - x64
+          - x86
+        exclude:
+          - os: macOS-latest
+            arch: x86
     steps:
       - uses: actions/checkout@v2
       - uses: julia-actions/setup-julia@v1
@@ -39,17 +43,14 @@ jobs:
             ${{ runner.os }}-test-${{ env.cache-name }}-
             ${{ runner.os }}-test-
             ${{ runner.os }}-
-      - name: dev TypedSyntax
-        shell: julia --color=yes --project=. {0}    # this is necessary for the next command to work on Windows
+      - name: dev TypedSyntax # dev TypedSyntax (a subdir package) since the latest version may not be registered yet
+        shell: julia --color=yes --project=. {0} # this is necessary for the next command to work on Windows
         run: 'using Pkg; Pkg.develop(path=joinpath(pwd(), "TypedSyntax"))'
       - uses: julia-actions/julia-buildpkg@v1
       - uses: julia-actions/julia-runtest@v1
         with:
           check_bounds: 'auto'
           coverage: 'false'
-      - name: TypedSyntax     # run the tests of TypedSyntax (a subdir package)
-        if: ${{ matrix.os == 'ubuntu-latest' }}
-        run: julia --project=TypedSyntax -e 'using Pkg; Pkg.test(coverage=true)'
       # - name: Coverage off  # `empty_func` test doesn't work as intended with `coverage=true`
       #   if: ${{ matrix.os == 'ubuntu-latest' }}
       #   run: julia --project -e 'using Pkg; Pkg.test("Cthulhu"; coverage=false)'
diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml
index f49313b..54d19dd 100644
--- a/.github/workflows/TagBot.yml
+++ b/.github/workflows/TagBot.yml
@@ -4,6 +4,22 @@ on:
     types:
       - created
   workflow_dispatch:
+    inputs:
+      lookback:
+        default: 100
+permissions:
+  actions: read
+  checks: read
+  contents: write
+  deployments: read
+  issues: read
+  discussions: read
+  packages: read
+  pages: read
+  pull-requests: read
+  repository-projects: read
+  security-events: read
+  statuses: read
 jobs:
   TagBot:
     if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
diff --git a/.github/workflows/TypedSyntaxCI.yml b/.github/workflows/TypedSyntaxCI.yml
index 59a351a..21f88bb 100644
--- a/.github/workflows/TypedSyntaxCI.yml
+++ b/.github/workflows/TypedSyntaxCI.yml
@@ -13,11 +13,15 @@ jobs:
       fail-fast: false
       matrix:
         version:
-          - '1.6'    # test LTS versions that are not supported by Cthulhu
+          - '1'
+          - '1.10.0'
+          - '1.11-nightly'
+          - 'nightly'
         os:
           - ubuntu-latest
         arch:
           - x64
+          - x86
     steps:
       - uses: actions/checkout@v2
       - uses: julia-actions/setup-julia@v1
diff --git a/.gitignore b/.gitignore
index 9e1c065..4479069 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 Manifest.toml
+Manifest-*.toml
 *.jl.cov
 *.jl.*.cov
 *.jl.mem
diff --git a/Project.toml b/Project.toml
index 3b1af6d..b21ad15 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,16 +1,16 @@
 name = "Cthulhu"
 uuid = "f68482b8-f384-11e8-15f7-abe071a5a75f"
 authors = ["Valentin Churavy <v.churavy@gmail.com> and contributors"]
-version = "2.9.2"
+version = "2.14.0"
 
 [deps]
 CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
 FoldingTrees = "1eca21be-9b9b-4ed8-839a-6d8ae26b1781"
 InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
 JuliaSyntax = "70703baa-626e-46a2-a12c-08ffd08c73b4"
+PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
 Preferences = "21216c6a-2e73-6563-6e65-726566657250"
 REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
-PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
 TypedSyntax = "d265eb64-f81a-44ad-a842-4247ee1503de"
 UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
 Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
@@ -19,12 +19,16 @@ WidthLimitedIO = "b8c1c048-cf81-46c6-9da0-18c1d99e41f2"
 [compat]
 CodeTracking = "0.5, 1"
 FoldingTrees = "1"
+InteractiveUtils = "1.9"
 JuliaSyntax = "0.4"
-Preferences = "1"
 PrecompileTools = "1"
-TypedSyntax = "1.1.7"
+Preferences = "1"
+REPL = "1.9"
+TypedSyntax = "1.3.0"
+UUIDs = "1.9"
+Unicode = "1.9"
 WidthLimitedIO = "1"
-julia = "1.8.5"
+julia = "1.10.0"
 
 [extras]
 DeepDiffs = "ab62b9b5-e342-54a8-a765-a90f495de1a6"
diff --git a/README.md b/README.md
index 3725b0a..1283c3f 100644
--- a/README.md
+++ b/README.md
@@ -274,8 +274,6 @@ Enter `@descend bar(x, y, z)` you can see that, for `foo(4)`, the types within `
 
 ## Viewing the internal representation of Julia code
 
-Anyone using Cthulhu to investigate the behavior of Julia's compiler will
-prefer to examine the
 While Cthulhu tries to place type-annotations on the source code, this obscures
 detail and can occassionally go awry (see details [here](TypedSyntax/README.md)).
 For anyone who needs more direct insight, it can be better to look directly at Julia's
diff --git a/TypedSyntax/Project.toml b/TypedSyntax/Project.toml
index b2fd320..d41a4b4 100644
--- a/TypedSyntax/Project.toml
+++ b/TypedSyntax/Project.toml
@@ -1,7 +1,7 @@
 name = "TypedSyntax"
 uuid = "d265eb64-f81a-44ad-a842-4247ee1503de"
 authors = ["Tim Holy <tim.holy@gmail.com> and contributors"]
-version = "1.2.2"
+version = "1.4.0"
 
 [deps]
 CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
@@ -10,7 +10,7 @@ JuliaSyntax = "70703baa-626e-46a2-a12c-08ffd08c73b4"
 [compat]
 CodeTracking = "1.3"
 JuliaSyntax = "0.4"
-julia = "1.6"
+julia = "1.10.0"
 
 [extras]
 Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
diff --git a/TypedSyntax/src/node.jl b/TypedSyntax/src/node.jl
index 0756e5a..c83f07e 100644
--- a/TypedSyntax/src/node.jl
+++ b/TypedSyntax/src/node.jl
@@ -24,22 +24,23 @@ const no_default_value = NoDefaultValue()
 # These are TypedSyntaxNode constructor helpers
 # Call these directly if you want both the TypedSyntaxNode and the `mappings` list,
 # where `mappings[i]` corresponds to the list of nodes matching `(src::CodeInfo).code[i]`.
-function tsn_and_mappings(@nospecialize(f), @nospecialize(t); kwargs...)
-    m = which(f, t)
-    src, rt = getsrc(f, t)
-    tsn_and_mappings(m, src, rt; kwargs...)
+function tsn_and_mappings(@nospecialize(f), @nospecialize(tt=Base.default_tt(f)); kwargs...)
+    inferred_result = get_inferred_result(f, tt)
+    return tsn_and_mappings(inferred_result.mi, inferred_result.src, inferred_result.rt; kwargs...)
 end
 
-function tsn_and_mappings(m::Method, src::CodeInfo, @nospecialize(rt); warn::Bool=true, strip_macros::Bool=false, kwargs...)
+function tsn_and_mappings(mi::MethodInstance, src::CodeInfo, @nospecialize(rt); warn::Bool=true, strip_macros::Bool=false, kwargs...)
+    m = mi.def::Method
     def = definition(String, m)
     if isnothing(def)
         warn && @warn "couldn't retrieve source of $m"
         return nothing, nothing
     end
-    return tsn_and_mappings(m, src, rt, def...; warn, strip_macros, kwargs...)
+    return tsn_and_mappings(mi, src, rt, def...; warn, strip_macros, kwargs...)
 end
 
-function tsn_and_mappings(m::Method, src::CodeInfo, @nospecialize(rt), sourcetext::AbstractString, lineno::Integer; warn::Bool=true, strip_macros::Bool=false, kwargs...)
+function tsn_and_mappings(mi::MethodInstance, src::CodeInfo, @nospecialize(rt), sourcetext::AbstractString, lineno::Integer; warn::Bool=true, strip_macros::Bool=false, kwargs...)
+    m = mi.def::Method
     filename = isnothing(functionloc(m)[1]) ? string(m.file) : functionloc(m)[1]
     rootnode = JuliaSyntax.parsestmt(SyntaxNode, sourcetext; filename=filename, first_line=lineno, kwargs...)
     if strip_macros
@@ -50,28 +51,33 @@ function tsn_and_mappings(m::Method, src::CodeInfo, @nospecialize(rt), sourcetex
         end
     end
     Δline = lineno - m.line   # offset from original line number (Revise)
-    mappings, symtyps = map_ssas_to_source(src, rootnode, Δline)
+    mappings, symtyps = map_ssas_to_source(src, mi, rootnode, Δline)
     node = TypedSyntaxNode(rootnode, src, mappings, symtyps)
     node.typ = rt
     return node, mappings
 end
 
-TypedSyntaxNode(@nospecialize(f), @nospecialize(t); kwargs...) = tsn_and_mappings(f, t; kwargs...)[1]
+TypedSyntaxNode(@nospecialize(f), @nospecialize(tt=Base.default_tt(f)); kwargs...) = tsn_and_mappings(f, tt; kwargs...)[1]
 
 function TypedSyntaxNode(mi::MethodInstance; kwargs...)
-    m = mi.def::Method
-    src, rt = getsrc(mi)
-    tsn_and_mappings(m, src, rt; kwargs...)[1]
+    src, rt = code_typed1_tsn(mi)
+    tsn_and_mappings(mi, src, rt; kwargs...)[1]
+end
+
+function TypedSyntaxNode(rootnode::SyntaxNode, @nospecialize(f), @nospecialize(tt=Base.default_tt(f)); kwargs...)
+    inferred_result = get_inferred_result(f, tt)
+    TypedSyntaxNode(rootnode, inferred_result.src, inferred_result.mi; kwargs...)
 end
 
-TypedSyntaxNode(rootnode::SyntaxNode, src::CodeInfo, Δline::Integer=0) =
-    TypedSyntaxNode(rootnode, src, map_ssas_to_source(src, rootnode, Δline)...)
+TypedSyntaxNode(rootnode::SyntaxNode, src::CodeInfo, mi::MethodInstance, Δline::Integer=0) =
+    TypedSyntaxNode(rootnode, src, map_ssas_to_source(src, mi, rootnode, Δline)...)
 
 function TypedSyntaxNode(rootnode::SyntaxNode, src::CodeInfo, mappings, symtyps)
     # There may be ambiguous assignments back to the source; preserve just the unambiguous ones
     node2ssa = IdDict{SyntaxNode,Int}(only(list) => i for (i, list) in pairs(mappings) if length(list) == 1)
     # Copy `rootnode`, adding type annotations
-    trootnode = TypedSyntaxNode(nothing, nothing, TypedSyntaxData(rootnode.data::SyntaxData, src, gettyp(node2ssa, rootnode, src)))
+    typ = gettyp(node2ssa, rootnode, src)
+    trootnode = TypedSyntaxNode(nothing, nothing, TypedSyntaxData(rootnode.data::SyntaxData, src, typ))
     addchildren!(trootnode, rootnode, src, node2ssa, symtyps, mappings)
     # Add argtyps to signature
     fnode = get_function_def(trootnode)
@@ -185,12 +191,12 @@ function map_signature!(sig::TypedSyntaxNode, slotnames::Vector{Symbol}, slottyp
         kwdivider = 1
         if havekws && slotnames[1] !== Symbol("#self#")
             kwdivider = findfirst(1:length(slotnames)) do i
-                slotnames[i] == Symbol("") && unwrapinternal(slottypes[i]) <: Function  # this should be the parent function as an argument
+                slotnames[i] == Symbol("") && isa(unwrapinternal(slottypes[i]), Function)  # this should be the parent function as an argument
             end
             if kwdivider === nothing
                 kwdivider = 1
             end
-            if length(slottypes) >= 2 && slotnames[2] == Symbol("") && (nt = unwrapinternal(slottypes[2])) <: NamedTuple
+            if length(slottypes) >= 2 && slotnames[2] == Symbol("") && (nt = unwrapinternal(slottypes[2]); isa(nt, Type)) && nt <: NamedTuple
                 # Match kwargs
                 argcontainer = children(last(children(sig)))
                 offset = length(children(sig)) - 1
@@ -238,7 +244,7 @@ function map_signature!(sig::TypedSyntaxNode, slotnames::Vector{Symbol}, slottyp
         if kind(arg) == K"::" && length(children(arg)) == 2
             arg = child(arg, 1)
         end
-        arg.typ = unwrapinternal(slottypes[idx])
+        arg.typ = slottypes[idx]
     end
 
     # It's annoying to print the signature as `foo::typeof(foo)(a::Int)`
@@ -270,7 +276,7 @@ function striparg(arg)
 end
 
 function unwrapinternal(@nospecialize(T))
-    isa(T, Core.Const) && return Core.Typeof(T.val)
+    isa(T, Core.Const) && return T.val
     isa(T, Core.PartialStruct) && return T.typ
     return T
 end
@@ -281,10 +287,10 @@ function gettyp(node2ssa, node, src)
     ssavaluetypes = src.ssavaluetypes::Vector{Any}
     if isa(stmt, Core.ReturnNode)
         arg = stmt.val
-        isa(arg, SSAValue) && return unwrapinternal(ssavaluetypes[arg.id])
-        is_slot(arg) && return unwrapinternal((src.slottypes::Vector{Any})[arg.id])
+        isa(arg, SSAValue) && return ssavaluetypes[arg.id]
+        is_slot(arg) && return (src.slottypes::Vector{Any})[arg.id]
     end
-    return unwrapinternal(ssavaluetypes[i])
+    return ssavaluetypes[i]
 end
 
 Base.copy(tsd::TypedSyntaxData) = TypedSyntaxData(tsd.source, tsd.typedsource, tsd.raw, tsd.position, tsd.val, tsd.typ, tsd.runtime)
@@ -304,17 +310,57 @@ function sparam_name(mi::MethodInstance, i::Int)
     return sig.var.name
 end
 
-function getsrc(@nospecialize(f), @nospecialize(t))
-    srcrts = code_typed(f, t; debuginfo=:source, optimize=false)
-    return only(srcrts)
-end
-
-function getsrc(mi::MethodInstance)
-    cis = Base.code_typed_by_type(mi.specTypes; debuginfo=:source, optimize=false)
-    isempty(cis) && error("no applicable type-inferred code found for ", mi)
-    length(cis) == 1 || error("got $(length(cis)) possible type-inferred results for ", mi,
-                              ", you may need a more specialized signature")
-    return cis[1]::Pair{CodeInfo}
+@static if isdefined(Base, :method_instances)
+using Base: method_instances
+else
+function method_instances(@nospecialize(f), @nospecialize(t), world::UInt)
+    tt = Base.signature_type(f, t)
+    results = Core.MethodInstance[]
+    # this make a better error message than the typeassert that follows
+    world == typemax(UInt) && error("code reflection cannot be used from generated functions")
+    for match in Base._methods_by_ftype(tt, -1, world)::Vector
+        instance = Core.Compiler.specialize_method(match)
+        push!(results, instance)
+    end
+    return results
+end
+end
+
+struct InferredResult
+    mi::MethodInstance
+    src::CodeInfo
+    rt
+    InferredResult(mi::MethodInstance, src::CodeInfo, @nospecialize(rt)) = new(mi, src, rt)
+end
+function get_inferred_result(@nospecialize(f), @nospecialize(tt=Base.default_tt(f)),
+                             world::UInt=Base.get_world_counter())
+    mis = method_instances(f, tt, world)
+    if isempty(mis)
+        sig = sprint(Base.show_tuple_as_call, Symbol(""), Base.signature_type(f, tt))
+        error("no applicable type-inferred code found for ", sig)
+    elseif length(mis) ≠ 1
+        sig = sprint(Base.show_tuple_as_call, Symbol(""), Base.signature_type(f, tt))
+        error("got $(length(mis)) possible type-inferred results for ", sig,
+              ", you may need a more specialized signature")
+    end
+    mi = only(mis)
+    return InferredResult(mi, code_typed1_tsn(mi)...)
+end
+
+code_typed1_tsn(mi::MethodInstance) = code_typed1_by_method_instance(mi; optimize=false, debuginfo=:source)
+
+function code_typed1_by_method_instance(mi::MethodInstance;
+                                        optimize::Bool=true,
+                                        debuginfo::Symbol=:default,
+                                        world::UInt=Base.get_world_counter(),
+                                        interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world))
+    (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) &&
+        error("code reflection should not be used from generated functions")
+    debuginfo = Base.IRShow.debuginfo(debuginfo)
+    code, rt = Core.Compiler.typeinf_code(interp, mi.def::Method, mi.specTypes, mi.sparam_vals, optimize)
+    code isa CodeInfo || error("no code is available for ", mi)
+    debuginfo === :none && Base.remove_linenums!(code)
+    return Pair{CodeInfo,Any}(code, rt)
 end
 
 function is_function_def(node)  # this is not `Base.is_function_def`
@@ -394,11 +440,80 @@ function collect_symbol_nodes!(symlocs::AbstractDict, node)
     return symlocs
 end
 
+## utility function to extract the line number at a particular program counter (ignoring inlining).
+## return <= 0 if there is no line number change caused by this statement
+@static if VERSION ≥ v"1.12.0-DEV.173"
+function getline(lt::Core.DebugInfo, i::Int)
+    while true
+        codeloc = Base.IRShow.getdebugidx(lt, i)
+        line::Int = codeloc[1]
+        line < 0 && return 0 # broken or disabled debug info?
+        line == 0 && return 0 # no line number update (though maybe inlining changed)
+        ltnext = lt.linetable
+        if ltnext === nothing
+            return line
+        end
+        i = line
+        lt = ltnext
+    end
+end
+function getnextline(lt::Core.DebugInfo, i::Int, Δline)
+    while true
+        codeloc = Base.IRShow.getdebugidx(lt, i)
+        line::Int = codeloc[1]
+        line < 0 && return typemax(Int) # broken or disabled debug info?
+        if line == 0
+            i += 1
+            continue
+        end
+        ltnext = lt.linetable
+        if ltnext === nothing
+            break
+        end
+        i = line
+        lt = ltnext
+    end
+    # now that we have line i and a list of all lines with code on them lt
+    # find the next largest line number in this list greater than i, or return typemax(Int)
+    j = i+1
+    currline = Base.IRShow.getdebugidx(lt, i)[1]
+    while j ≤ typemax(Int)
+        codeloc = Base.IRShow.getdebugidx(lt, j)
+        line::Int = codeloc[1]
+        line < 0 && break
+        if line == 0 || currline == line
+            j += 1
+        else
+            return line + Δline
+        end
+    end
+    return typemax(Int)
+end
+
+else # VERSION < v"1.12.0-DEV.173"
+function getline(lt, j)
+    linfo = (j == 0 ? first(lt) : lt[j])::Core.LineInfoNode
+    linfo.inlined_at == 0 && return linfo.line
+    @assert linfo.method === Symbol("macro expansion")
+    linfo = lt[linfo.inlined_at]::Core.LineInfoNode
+    return linfo.line
+end
+function getnextline(lt, j, Δline)
+    j == 0 && return typemax(Int)
+    j += 1
+    while j <= length(lt)
+        linfo = lt[j]::Core.LineInfoNode
+        linfo.inlined_at == 0 && return linfo.line + Δline
+        j += 1
+    end
+    return typemax(Int)
+end
+end # @static if
+
 # Main logic for mapping `src.code[i]` to node(s) in the SyntaxNode tree
 # Success: when we map it to a unique node
 # Δline is the (Revise) offset of the line number
-function map_ssas_to_source(src::CodeInfo, rootnode::SyntaxNode, Δline::Int)
-    mi = src.parent::MethodInstance
+function map_ssas_to_source(src::CodeInfo, mi::MethodInstance, rootnode::SyntaxNode, Δline::Int)
     slottypes = src.slottypes::Union{Nothing, Vector{Any}}
     have_slottypes = slottypes !== nothing
     ssavaluetypes = src.ssavaluetypes::Vector{Any}
@@ -427,8 +542,13 @@ function map_ssas_to_source(src::CodeInfo, rootnode::SyntaxNode, Δline::Int)
     # Append (to `mapped`) all nodes in `targets` that are consistent with the line number of the `i`th stmt
     # (Essentially `copy!(mapped, filter(predicate, targets))`)
     function append_targets_for_line!(mapped#=::Vector{nodes}=#, i::Int, targets#=::Vector{nodes}=#)
-        j = src.codelocs[i]
-        lt = src.linetable::Vector{Any}
+        @static if VERSION ≥ v"1.12.0-DEV.173"
+            j = i
+            lt = src.debuginfo
+        else
+            j = src.codelocs[i]
+            lt = src.linetable::Vector
+        end
         start = getline(lt, j) + Δline
         stop = getnextline(lt, j, Δline) - 1
         linerange = start : stop
@@ -488,12 +608,15 @@ function map_ssas_to_source(src::CodeInfo, rootnode::SyntaxNode, Δline::Int)
     argmapping = typeof(rootnode)[]   # temporary storage
     for (i, mapped, stmt) in zip(eachindex(mappings), mappings, src.code)
         empty!(argmapping)
-        if is_slot(stmt) || isa(stmt, SSAValue)
+        if is_slot(stmt) || isa(stmt, SSAValue) || isa(stmt, GlobalRef)
             append_targets_for_arg!(mapped, i, stmt)
         elseif isa(stmt, Core.ReturnNode)
             append_targets_for_line!(mapped, i, append_targets_for_arg!(argmapping, i, stmt.val))
         elseif isa(stmt, Expr)
-            if stmt.head == :(=) && is_slot(stmt.args[1])
+            targets = get_targets(stmt)
+            if targets !== nothing
+                append_targets_for_line!(mapped, i, targets)
+            elseif stmt.head == :(=) && is_slot(stmt.args[1])
                 # We defer setting up `symtyps` for the LHS because processing the RHS first might eliminate ambiguities
                 # # Update `symtyps` for this assignment
                 lhs = stmt.args[1]
@@ -503,16 +626,14 @@ function map_ssas_to_source(src::CodeInfo, rootnode::SyntaxNode, Δline::Int)
                     append_targets_for_arg!(mapped, i, stmt)
                     filter_assignment_targets!(mapped, true)   # match the RHS of assignments
                     if length(mapped) == 1
-                        symtyps[only(mapped)] = unwrapinternal(
-                                                (is_slot(stmt) & have_slottypes) ? slottypes[(stmt::SlotType).id] :
+                        symtyps[only(mapped)] = (is_slot(stmt) & have_slottypes) ? slottypes[(stmt::SlotType).id] :
                                                 isa(stmt, SSAValue) ? ssavaluetypes[stmt.id] : #=literal=#typeof(stmt)
-                        )
                     end
                     # Now try to assign types to the LHS of the assignment
                     append_targets_for_arg!(argmapping, i, lhs)
                     filter_assignment_targets!(argmapping, false)  # match the LHS of assignments
                     if length(argmapping) == 1
-                        T = unwrapinternal(ssavaluetypes[i])
+                        T = ssavaluetypes[i]
                         symtyps[only(argmapping)] = T
                     end
                     empty!(argmapping)
@@ -615,7 +736,7 @@ function map_ssas_to_source(src::CodeInfo, rootnode::SyntaxNode, Δline::Int)
                         if isexpr(nextstmt, :call)
                             f = nextstmt.args[1]
                             if isa(f, GlobalRef) && f.mod == Base && f.name == :broadcasted
-                                empty!(mapped)
+                                # empty!(mapped)
                                 break
                             elseif isa(f, GlobalRef) && f.mod == Base && f.name == :materialize && nextstmt.args[2] === SSAValue(i)
                                 push!(mappings[inext], node)
@@ -670,14 +791,14 @@ function map_ssas_to_source(src::CodeInfo, rootnode::SyntaxNode, Δline::Int)
                                     haskey(symtyps, t) && continue
                                     if skipped_parent(t) == node
                                         is_prec_assignment(node) && t == child(node, 1) && continue
-                                        symtyps[t] = unwrapinternal(if j > 0
+                                        symtyps[t] = if j > 0
                                             ssavaluetypes[j]
                                         elseif have_slottypes
                                             # We failed to find it as an SSAValue, it must have type assigned at function entry
                                             slottypes[arg.id]
                                         else
                                             nothing
-                                        end)
+                                        end
                                         break
                                     end
                                 end
@@ -736,7 +857,7 @@ function map_ssas_to_source(src::CodeInfo, rootnode::SyntaxNode, Δline::Int)
     end
     return mappings, symtyps
 end
-map_ssas_to_source(src::CodeInfo, rootnode::SyntaxNode, Δline::Integer) = map_ssas_to_source(src, rootnode, Int(Δline))
+map_ssas_to_source(src::CodeInfo, mi::MethodInstance, rootnode::SyntaxNode, Δline::Integer) = map_ssas_to_source(src, mi, rootnode, Int(Δline))
 
 function follow_back(src, arg)
     # Follow SSAValue backward to see if it maps back to a slot
@@ -781,6 +902,9 @@ function skipped_parent(node::SyntaxNode)
     pnode === nothing && return node
     ppnode = pnode.parent
     if ppnode !== nothing && kind(pnode) ∈ KSet"... quote"   # might need to add more things here
+        if kind(node) == K"Identifier" && kind(pnode) == K"quote" && kind(ppnode) == K"." && sourcetext(node) == "materialize"
+            return ppnode.parent
+        end
         return ppnode
     end
     return pnode
@@ -809,25 +933,6 @@ function symloc_key(sym::Symbol)
     return sym
 end
 
-function getline(lt, j)
-    linfo = (j == 0 ? first(lt) : lt[j])::Core.LineInfoNode
-    linfo.inlined_at == 0 && return linfo.line
-    @assert linfo.method === Symbol("macro expansion")
-    linfo = lt[linfo.inlined_at]::Core.LineInfoNode
-    return linfo.line
-end
-
-function getnextline(lt, j, Δline)
-    j == 0 && return typemax(Int)
-    j += 1
-    while j <= length(lt)
-        linfo = lt[j]::Core.LineInfoNode
-        linfo.inlined_at == 0 && return linfo.line + Δline
-        j += 1
-    end
-    return typemax(Int)
-end
-
 function find_identifier_or_tuplechild(node::AbstractSyntaxNode, sym)
     kind(node) == K"Identifier" && node.val === sym && return node, true
     if kind(node) == K"tuple"   # tuple destructuring
diff --git a/TypedSyntax/src/show.jl b/TypedSyntax/src/show.jl
index 79361ca..b4197b3 100644
--- a/TypedSyntax/src/show.jl
+++ b/TypedSyntax/src/show.jl
@@ -80,30 +80,63 @@ function show_src_expr(io::IO, node::MaybeTypedSyntaxNode, position::Int, pre::S
         position = show_src_expr(io, child, position, cpre, cpre2; type_annotations, iswarn, hide_type_stable, nd)
         ctype_annotate && show_annotation(io, cT, cpost, node.source, position; iswarn)
     end
-    return catchup(io, node, position, nd, _lastidx+1)
+    return Int(catchup(io, node, position, nd, _lastidx+1))
 end
 
 # should we print a type-annotation?
 function is_show_annotation(@nospecialize(T); type_annotations::Bool, hide_type_stable::Bool)
     type_annotations || return false
     if isa(T, Core.Const)
-        T = typeof(T.val)
+        isa(T.val, Module) && return false
+        T = Core.Typeof(T.val)
     end
     isa(T, Type) || return false
     hide_type_stable || return true
     return isa(T, Type) && is_type_unstable(T)
 end
 
+# Is the type equivalent to the source-text?
+# We use `endswith` to handle module qualification
+is_type_transparent(node, @nospecialize(T)) = endswith(replace(sprint(show, T), r"\s" => ""), replace(sourcetext(node), r"\s" => ""))
+
+function is_callfunc(node::MaybeTypedSyntaxNode, @nospecialize(T))
+    thisnode = node
+    pnode = node.parent
+    while pnode !== nothing && kind(pnode) ∈ KSet"quote ." && pnode.parent !== nothing
+        thisnode = pnode
+        pnode = pnode.parent
+    end
+    if pnode !== nothing && kind(pnode) ∈ (K"call", K"curly") && ((is_infix_op_call(pnode) && is_operator(thisnode)) || thisnode === pnode.children[1])
+        if isa(T, Core.Const)
+            T = T.val
+        end
+        if isa(T, Type) || isa(T, Function)
+            T === Colon() && sourcetext(node) == ":" && return true
+            return is_type_transparent(node, T)
+        end
+    end
+    return false
+end
+
 function type_annotation_mode(node, @nospecialize(T); type_annotations::Bool, hide_type_stable::Bool)
     kind(node) == K"return" && return false, "", "", ""
+    is_callfunc(node, T) && return false, "", "", ""
     type_annotate = is_show_annotation(T; type_annotations, hide_type_stable)
     pre = pre2 = post = ""
     if type_annotate
-        if T isa Type && T <: Type
-            # Don't annotate `String::Type{String}`
-            if replace(sourcetext(node), r"\s" => "") == replace(sprint(show, T.parameters[1]), r"\s" => "")
-                return false, pre, pre2, post
+        # Try stripping Core.Const and Type{T} wrappers to check if we need to avoid `String::Type{String}`
+        # or `String::Core.Const(String)` annotations
+        S = nothing
+        if isa(T, Core.Const)
+            val = T.val
+            if isa(val, DataType)
+                S = val
             end
+        elseif isa(T, DataType) && T <: Type && isassigned(T.parameters, 1)
+            S = T.parameters[1]
+        end
+        if S !== nothing && is_type_transparent(node, S)
+            return false, pre, pre2, post
         end
         if kind(node) ∈ KSet":: where" || is_infix_op_call(node) || (is_prec_assignment(node) && kind(node) != K"=")
             pre, post = "(", ")"
@@ -119,6 +152,9 @@ function show_annotation(io, @nospecialize(T), post, node, position; iswarn::Boo
     inlay_hints = get(io, :inlay_hints, nothing)
 
     print(io, post)
+    if isa(T, Core.Const) && isa(T.val, Type)
+        T = Type{T.val}
+    end
     T_str = string(T)
     if iswarn && is_type_unstable(T)
         color = is_small_union_or_tunion(T) ? :yellow : :red
diff --git a/src/Cthulhu.jl b/src/Cthulhu.jl
index 6376b79..814ff2a 100644
--- a/src/Cthulhu.jl
+++ b/src/Cthulhu.jl
@@ -2,7 +2,7 @@ module Cthulhu
 
 Base.Experimental.@compiler_options compile=min optimize=1
 
-using CodeTracking: definition, whereis
+using CodeTracking: CodeTracking, definition, whereis, maybe_fix_path
 using InteractiveUtils
 using UUIDs
 using REPL: REPL, AbstractTerminal
@@ -20,6 +20,13 @@ const mapany = Base.mapany
 
 const ArgTypes = Vector{Any}
 
+@static if VERSION ≥ v"1.11.0-DEV.1498"
+    import .CC: get_inference_world
+    using Base: get_world_counter
+else
+    import .CC: get_world_counter, get_world_counter as get_inference_world
+end
+
 Base.@kwdef mutable struct CthulhuConfig
     enable_highlighter::Bool = false
     highlighter::Cmd = `pygmentize -l`
@@ -33,11 +40,13 @@ Base.@kwdef mutable struct CthulhuConfig
     hide_type_stable::Bool = false
     remarks::Bool = false
     with_effects::Bool = false
+    exception_type::Bool = false
     inline_cost::Bool = false
     type_annotations::Bool = true
     annotate_source::Bool = true   # overrides optimize, although the current setting is preserved
     inlay_types_vscode::Bool = true
     diagnostics_vscode::Bool = true
+    jump_always::Bool = false
 end
 
 """
@@ -62,11 +71,13 @@ end
 - `iswarn::Bool`: Initial state of "warn" toggle. Defaults to `false`.
 - `remarks::Bool` Initial state of "remarks" toggle. Defaults to `false`.
 - `with_effects::Bool` Intial state of "effects" toggle. Defaults to `false`.
+- `exception_type::Bool` `Intial state of "exception type" toggle. Defaults to `false`.
 - `inline_cost::Bool` Initial state of "inlining costs" toggle. Defaults to `false`.
 - `type_annotations::Bool` Initial state of "type annnotations" toggle. Defaults to `true`.
 - `annotate_source::Bool` Initial state of "Source". Defaults to `true`.
 - `inlay_types_vscode::Bool` Initial state of "vscode: inlay types" toggle. Defaults to `true`
 - `diagnostics_vscode::Bool` Initial state of "Vscode: diagnostics" toggle. Defaults to `true`
+- `jump_always::Bool` Initial state of "jump to source always" toggle. Defaults to `false`.
 """
 const CONFIG = CthulhuConfig()
 
@@ -95,7 +106,17 @@ export ascend
     @descend_code_typed
 
 Evaluates the arguments to the function or macro call, determines their
-types, and calls `code_typed` on the resulting expression.
+types, and calls [`descend_code_typed`](@ref) on the resulting expression.
+See [`Cthulhu.CONFIG`](@ref) for options and their defaults.
+
+# Examples
+```julia
+julia> @descend_code_typed sin(1)
+[...]
+
+julia> @descend_code_typed optimize=false sin(1)
+[...]
+```
 """
 macro descend_code_typed(ex0...)
     InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :descend_code_typed, ex0)
@@ -114,7 +135,23 @@ end
     @descend_code_warntype
 
 Evaluates the arguments to the function or macro call, determines their
-types, and calls `code_warntype` on the resulting expression.
+types, and calls [`descend_code_warntype`](@ref) on the resulting expression.
+See [`Cthulhu.CONFIG`](@ref) for options and their defaults.
+
+# Examples
+```julia
+julia> function foo()
+           T = rand() > 0.5 ? Int64 : Float64
+           sin(rand(T))
+       end
+foo (generic function with 1 method)
+
+julia> @descend_code_warntype foo()
+[...]
+
+julia> @descend_code_warntype hide_type_stable=true foo()
+[...]
+```
 """
 macro descend_code_warntype(ex0...)
     InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :descend_code_warntype, ex0)
@@ -123,9 +160,22 @@ end
 """
     @descend
 
-Shortcut for [`@descend_code_typed`](@ref).
+Evaluates the arguments to the function or macro call, determines their
+types, and calls [`descend`](@ref) on the resulting expression.
+See [`Cthulhu.CONFIG`](@ref) for options and their defaults.
+
+# Examples
+```julia
+julia> @descend sin(1)
+[...]
+
+julia> @descend iswarn=false foo()
+[...]
+```
 """
-const var"@descend" = var"@descend_code_typed"
+macro descend(ex0...)
+    InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :descend, ex0)
+end
 
 """
     descend_code_typed(f, argtypes=Tuple{...}; kwargs...)
@@ -136,6 +186,7 @@ const var"@descend" = var"@descend_code_typed"
 Given a function and a tuple-type, interactively explore the output of
 `code_typed` by descending into `invoke` statements. Type enter to select an
 `invoke` to descend into, select `↩`  to ascend, and press `q` or `control-c` to quit.
+See [`Cthulhu.CONFIG`](@ref) for `kwargs` and their defaults.
 
 # Usage:
 ```julia
@@ -150,13 +201,13 @@ julia> descend_code_typed(Tuple{typeof(sin), Int})
 
 julia> descend_code_typed() do
            T = rand() > 0.5 ? Int64 : Float64
-           sin(rand(T)
+           sin(rand(T))
        end
 [...]
 ```
 """
 descend_code_typed(@nospecialize(args...); kwargs...) =
-    _descend_with_error_handling(args...; iswarn=false, kwargs...)
+    _descend_with_error_handling(args...; annotate_source=false, iswarn=false, kwargs...)
 
 """
     descend_code_warntype(f, argtypes=Tuple{...}; kwargs...)
@@ -167,6 +218,7 @@ descend_code_typed(@nospecialize(args...); kwargs...) =
 Given a function and a tuple-type, interactively explore the output of
 `code_warntype` by descending into `invoke` statements. Type enter to select an
 `invoke` to descend into, select `↩` to ascend, and press `q` or `control-c` to quit.
+See [`Cthulhu.CONFIG`](@ref) for `kwargs` and their defaults.
 
 # Usage:
 ```julia
@@ -181,13 +233,13 @@ julia> descend_code_warntype(Tuple{typeof(sin), Int})
 
 julia> descend_code_warntype() do
            T = rand() > 0.5 ? Int64 : Float64
-           sin(rand(T)
+           sin(rand(T))
        end
 [...]
 ```
 """
 descend_code_warntype(@nospecialize(args...); kwargs...) =
-    _descend_with_error_handling(args...; iswarn=true, kwargs...)
+    _descend_with_error_handling(args...; annotate_source=false, iswarn=true, optimize=false, kwargs...)
 
 function _descend_with_error_handling(@nospecialize(f), @nospecialize(argtypes = default_tt(f)); kwargs...)
     ft = Core.Typeof(f)
@@ -223,46 +275,71 @@ end
 default_terminal() = REPL.LineEdit.terminal(Base.active_repl)
 
 """
-    descend
+    descend(f, argtypes=Tuple{...}; kwargs...)
+    descend(tt::Type{<:Tuple}; kwargs...)
+    descend(Cthulhu.BOOKMARKS[i])
+    descend(mi::MethodInstance; kwargs...)
 
-Shortcut for [`descend_code_typed`](@ref).
-"""
-const descend = descend_code_typed
+Given a function and a tuple-type, interactively explore the source code of functions
+annotated with inferred types by descending into `invoke` statements. Type enter to select an
+`invoke` to descend into, select `↩` to ascend, and press `q` or `control-c` to quit.
+See [`Cthulhu.CONFIG`](@ref) for `kwargs` and their defaults.
 
-descend_code_typed(interp::AbstractInterpreter, mi::MethodInstance; kwargs...) =
-    _descend_with_error_handling(interp, mi; iswarn=false, kwargs...)
+# Usage:
+```julia
+julia> descend(sin, (Int,))
+[...]
 
-function codeinst_rt(code::CodeInstance)
+julia> descend(sin, Tuple{Int})
+[...]
+
+julia> descend(Tuple{typeof(sin), Int})
+[...]
+
+julia> descend() do
+           T = rand() > 0.5 ? Int64 : Float64
+           sin(rand(T))
+       end
+[...]
+```
+"""
+descend(@nospecialize(args...); kwargs...) =
+    _descend_with_error_handling(args...; iswarn=true, kwargs...)
+
+@static if VERSION ≥ v"1.11.0-DEV.207"
+    using .CC: cached_return_type
+else
+function cached_return_type(code::CodeInstance)
     rettype = code.rettype
-    if isdefined(code, :rettype_const)
-        rettype_const = code.rettype_const
-        if isa(rettype_const, Vector{Any}) && !(Vector{Any} <: rettype)
-            return Core.PartialStruct(rettype, rettype_const)
-        elseif isa(rettype_const, Core.PartialOpaque) && rettype <: Core.OpaqueClosure
-            return rettype_const
-        elseif isa(rettype_const, CC.InterConditional) && !(CC.InterConditional <: rettype)
-            return rettype_const
-        else
-            return Const(rettype_const)
-        end
+    isdefined(code, :rettype_const) || return rettype
+    rettype_const = code.rettype_const
+    if isa(rettype_const, Vector{Any}) && !(Vector{Any} <: rettype)
+        return Core.PartialStruct(rettype, rettype_const)
+    elseif isa(rettype_const, Core.PartialOpaque) && rettype <: Core.OpaqueClosure
+        return rettype_const
+    elseif isa(rettype_const, CC.InterConditional) && !(CC.InterConditional <: rettype)
+        return rettype_const
+    else
+        return Const(rettype_const)
+    end
+end
+end
+
+function cached_exception_type(code::CodeInstance)
+    @static if VERSION ≥ v"1.11.0-DEV.945"
+        return code.exctype
     else
-        return rettype
+        return nothing
     end
 end
 
 get_effects(codeinst::CodeInstance) = CC.decode_effects(codeinst.ipo_purity_bits)
 get_effects(codeinst::CodeInfo) = CC.decode_effects(codeinst.purity)
 get_effects(src::InferredSource) = src.effects
-get_effects(unopt::Dict{Union{MethodInstance, InferenceResult}, InferredSource}, mi::MethodInstance) =
-    haskey(unopt, mi) ? get_effects(unopt[mi]) : Effects()
 get_effects(result::InferenceResult) = result.ipo_effects
-@static if VERSION ≥ v"1.9-"
-    get_effects(result::CC.ConstPropResult) = get_effects(result.result)
-    get_effects(result::CC.ConcreteResult) = result.effects
-    get_effects(result::CC.SemiConcreteResult) = result.effects
-else
-    get_effects(result::CC.ConstResult) = result.effects
-end
+get_effects(result::CC.ConstPropResult) = get_effects(result.result)
+get_effects(result::CC.ConcreteResult) = result.effects
+get_effects(result::CC.SemiConcreteResult) = result.effects
 
 # `@constprop :aggressive` here in order to make sure the constant propagation of `allow_no_src`
 @constprop :aggressive function lookup(interp::CthulhuInterpreter, mi::MethodInstance, optimize::Bool; allow_no_src::Bool=false)
@@ -275,7 +352,8 @@ end
 
 function lookup_optimized(interp::CthulhuInterpreter, mi::MethodInstance, allow_no_src::Bool=false)
     codeinst = interp.opt[mi]
-    rt = codeinst_rt(codeinst)
+    rt = cached_return_type(codeinst)
+    exct = cached_exception_type(codeinst)
     opt = codeinst.inferred
     if opt !== nothing
         opt = opt::OptimizedSource
@@ -297,19 +375,19 @@ function lookup_optimized(interp::CthulhuInterpreter, mi::MethodInstance, allow_
         error("couldn't find the source; inspect `Main.interp` and `Main.mi`")
     end
     effects = get_effects(codeinst)
-    return (; src, rt, infos, slottypes, effects, codeinf)
+    return (; src, rt, exct, infos, slottypes, effects, codeinf)
 end
 
 function lookup_unoptimized(interp::CthulhuInterpreter, mi::MethodInstance)
     codeinf = src = copy(interp.unopt[mi].src)
-    rt = interp.unopt[mi].rt
+    (; rt, exct) = interp.unopt[mi]
     infos = interp.unopt[mi].stmt_info
     effects = get_effects(interp.unopt[mi])
     slottypes = src.slottypes
     if isnothing(slottypes)
         slottypes = Any[ Any for i = 1:length(src.slotflags) ]
     end
-    return (; src, rt, infos, slottypes, effects, codeinf)
+    return (; src, rt, exct, infos, slottypes, effects, codeinf)
 end
 
 function lookup_constproped(interp::CthulhuInterpreter, override::InferenceResult, optimize::Bool)
@@ -327,11 +405,12 @@ function lookup_constproped_optimized(interp::CthulhuInterpreter, override::Infe
         # e.g. when we switch from constant-prop' unoptimized source
         src = CC.copy(opt.ir)
         rt = override.result
+        exct = @static hasfield(InferenceResult, :exc_result) ? override.exc_result : nothing
         infos = src.stmts.info
         slottypes = src.argtypes
         codeinf = opt.src
         effects = opt.effects
-        return (; src, rt, infos, slottypes, effects, codeinf)
+        return (; src, rt, exct, infos, slottypes, effects, codeinf)
     else
         # the source might be unavailable at this point,
         # when a result is fully constant-folded etc.
@@ -345,24 +424,25 @@ function lookup_constproped_unoptimized(interp::CthulhuInterpreter, override::In
         unopt = interp.unopt[override.linfo]
     end
     codeinf = src = copy(unopt.src)
-    rt = unopt.rt
+    (; rt, exct) = unopt
     infos = unopt.stmt_info
     effects = get_effects(unopt)
     slottypes = src.slottypes
     if isnothing(slottypes)
         slottypes = Any[ Any for i = 1:length(src.slotflags) ]
     end
-    return (; src, rt, infos, slottypes, effects, codeinf)
+    return (; src, rt, exct, infos, slottypes, effects, codeinf)
 end
 
 function lookup_semiconcrete(interp::CthulhuInterpreter, override::SemiConcreteCallInfo, optimize::Bool)
     src = CC.copy(override.ir)
     rt = get_rt(override)
+    exct = Any # TODO
     infos = src.stmts.info
     slottypes = src.argtypes
     effects = get_effects(override)
     (; codeinf) = lookup(interp, get_mi(override), optimize)
-    return (; src, rt, infos, slottypes, effects, codeinf)
+    return (; src, rt, exct, infos, slottypes, effects, codeinf)
 end
 
 function get_override(@nospecialize(info))
@@ -379,19 +459,21 @@ end
 ##
 function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::AbstractCursor;
     override::Union{Nothing,InferenceResult,SemiConcreteCallInfo} = nothing,
-    debuginfo::Union{Symbol,DebugInfo}       = CONFIG.debuginfo,                     # default is compact debuginfo
-    optimize::Bool                           = CONFIG.optimize,                      # default is true
-    interruptexc::Bool                       = CONFIG.interruptexc,
-    iswarn::Bool                             = CONFIG.iswarn,                        # default is false
-    hide_type_stable::Union{Nothing,Bool}    = CONFIG.hide_type_stable,
-    verbose::Union{Nothing,Bool}             = nothing,
-    remarks::Bool                            = CONFIG.remarks&!CONFIG.optimize,      # default is false
-    with_effects::Bool                       = CONFIG.with_effects,                  # default is false
-    inline_cost::Bool                        = CONFIG.inline_cost&CONFIG.optimize,   # default is false
-    type_annotations::Bool                   = CONFIG.type_annotations,              # default is true
-    annotate_source::Bool                    = CONFIG.annotate_source,               # default is true
-    inlay_types_vscode::Bool                 = CONFIG.inlay_types_vscode,            # default is true
-    diagnostics_vscode::Bool                 = CONFIG.diagnostics_vscode,            # default is true
+    debuginfo::Union{Symbol,DebugInfo}    = CONFIG.debuginfo,                     # default is compact debuginfo
+    optimize::Bool                        = CONFIG.optimize,                      # default is true
+    interruptexc::Bool                    = CONFIG.interruptexc,
+    iswarn::Bool                          = CONFIG.iswarn,                        # default is false
+    hide_type_stable::Union{Nothing,Bool} = CONFIG.hide_type_stable,
+    verbose::Union{Nothing,Bool}          = nothing,
+    remarks::Bool                         = CONFIG.remarks&!CONFIG.optimize,      # default is false
+    with_effects::Bool                    = CONFIG.with_effects,                  # default is false
+    exception_type::Bool                  = CONFIG.exception_type,                # default is false
+    inline_cost::Bool                     = CONFIG.inline_cost&CONFIG.optimize,   # default is false
+    type_annotations::Bool                = CONFIG.type_annotations,              # default is true
+    annotate_source::Bool                 = CONFIG.annotate_source,               # default is true
+    inlay_types_vscode::Bool              = CONFIG.inlay_types_vscode,            # default is true
+    diagnostics_vscode::Bool              = CONFIG.diagnostics_vscode,            # default is true
+    jump_always::Bool                     = CONFIG.jump_always,                   # default is false
     )
 
     if isnothing(hide_type_stable)
@@ -413,7 +495,9 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs
         do_typeinf!(new_interp, new_mi)
         _descend(term, new_interp, new_mi;
                  debuginfo, optimize, interruptexc, iswarn, hide_type_stable, remarks,
-                 with_effects, inline_cost, type_annotations, annotate_source, inlay_types_vscode, diagnostics_vscode)
+                 with_effects, exception_type,
+                 inline_cost, type_annotations, annotate_source,
+                 inlay_types_vscode, diagnostics_vscode)
     end
     custom_toggles = Cthulhu.custom_toggles(interp)
     if !(custom_toggles isa Vector{CustomToggle})
@@ -424,9 +508,9 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs
     end
     while true
         if isa(override, InferenceResult)
-            (; src, rt, infos, slottypes, codeinf, effects) = lookup_constproped(interp, curs, override, optimize & !annotate_source)
+            (; src, rt, exct, infos, slottypes, codeinf, effects) = lookup_constproped(interp, curs, override, optimize & !annotate_source)
         elseif isa(override, SemiConcreteCallInfo)
-                (; src, rt, infos, slottypes, codeinf, effects) = lookup_semiconcrete(interp, curs, override, optimize & !annotate_source)
+                (; src, rt, exct, infos, slottypes, codeinf, effects) = lookup_semiconcrete(interp, curs, override, optimize & !annotate_source)
         else
             if optimize && !annotate_source
                 codeinst = get_optimized_codeinst(interp, curs)
@@ -436,7 +520,8 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs
                         # but make something up that looks plausible.
                         callsites = Callsite[]
                         if display_CI
-                            callsite = Callsite(-1, MICallInfo(codeinst.def, codeinst.rettype, get_effects(codeinst)), :invoke)
+                            exct = @static VERSION ≥ v"1.11.0-DEV.945" ? codeinst.exct : nothing
+                            callsite = Callsite(-1, MICallInfo(codeinst.def, codeinst.rettype, get_effects(codeinst), exct), :invoke)
                             println(iostream)
                             println(iostream, "│ ─ $callsite")
                             println(iostream, "│  return ", Const(codeinst.rettype_const))
@@ -453,7 +538,7 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs
                     end
                 end
             end
-            (; src, rt, infos, slottypes, effects, codeinf) = lookup(interp, curs, optimize & !annotate_source)
+            (; src, rt, exct, infos, slottypes, effects, codeinf) = lookup(interp, curs, optimize & !annotate_source)
         end
         mi = get_mi(curs)
         src = preprocess_ci!(src, mi, optimize & !annotate_source, CONFIG)
@@ -462,11 +547,28 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs
         else
             @assert length(src.code) == length(infos)
         end
-        callsites, sourcenodes = find_callsites(interp, src, infos, mi, slottypes, optimize & !annotate_source, annotate_source)
+        infkey = override isa InferenceResult ? override : mi
+        @static if VERSION ≥ v"1.11.0-DEV.1127"
+        pc2excts = exception_type ? get_excts(interp, infkey) : nothing
+        else
+        if exception_type
+            @warn "Statement-wise and call-wise exception type information is available only on v\"1.11.0-DEV.1127\" and later"
+        end
+        pc2excts = nothing
+        end
+        callsites, sourcenodes = find_callsites(interp, src, infos, mi, slottypes, optimize & !annotate_source, annotate_source, pc2excts)
+
+        if jump_always
+            if isdefined(Main, :VSCodeServer) && Main.VSCodeServer isa Module && isdefined(Main.VSCodeServer, :openfile)
+                Main.VSCodeServer.openfile(whereis(mi.def::Method)...; preserve_focus=true)
+            else
+                edit(whereis(mi.def::Method)...)
+            end
+        end
 
         if display_CI
-            pc2remarks = remarks ? get_remarks(interp, override !== nothing ? override : mi) : nothing
-            pc2effects = with_effects ? get_effects(interp, override !== nothing ? override : mi) : nothing
+            pc2remarks = remarks ? get_remarks(interp, infkey) : nothing
+            pc2effects = with_effects ? get_effects(interp, infkey) : nothing
             printstyled(IOContext(iostream, :limit=>true), mi.def, '\n'; bold=true)
             if debuginfo == DInfo.compact
                 str = let debuginfo=debuginfo, src=src, codeinf=codeinf, rt=rt, mi=mi,
@@ -476,13 +578,14 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs
                         :color => true,
                         :displaysize => displaysize(iostream), # displaysize doesn't propagate otherwise
                         :SOURCE_SLOTNAMES => codeinf === nothing ? false : Base.sourceinfo_slotnames(codeinf),
-                        :with_effects => with_effects)
+                        :with_effects => with_effects,
+                        :exception_type => exception_type)
                     stringify(ioctx) do lambda_io
-                        cthulhu_typed(lambda_io, debuginfo, annotate_source ? codeinf : src, rt, effects, mi;
+                        cthulhu_typed(lambda_io, debuginfo, annotate_source ? codeinf : src, rt, exct, effects, mi;
                                       iswarn, optimize, hide_type_stable,
-                                      pc2remarks, pc2effects,
+                                      pc2remarks, pc2effects, pc2excts,
                                       inline_cost, type_annotations, annotate_source, inlay_types_vscode, diagnostics_vscode,
-                                      interp)
+                                      jump_always, interp)
                     end
                 end
                 # eliminate trailing indentation (see first item in bullet list in PR #189)
@@ -494,12 +597,13 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs
             else
                 lambda_io = IOContext(iostream,
                     :SOURCE_SLOTNAMES => Base.sourceinfo_slotnames(codeinf),
-                    :with_effects => with_effects)
-                cthulhu_typed(lambda_io, debuginfo, src, rt, effects, mi;
+                    :with_effects => with_effects,
+                    :exception_type => exception_type)
+                cthulhu_typed(lambda_io, debuginfo, src, rt, exct, effects, mi;
                               iswarn, optimize, hide_type_stable,
-                              pc2remarks, pc2effects,
+                              pc2remarks, pc2effects, pc2excts,
                               inline_cost, type_annotations, annotate_source, inlay_types_vscode, diagnostics_vscode,
-                              interp)
+                              jump_always, interp)
             end
             view_cmd = cthulhu_typed
         else
@@ -509,8 +613,14 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs
         @label show_menu
 
         shown_callsites = annotate_source ? sourcenodes : callsites
-        menu = CthulhuMenu(shown_callsites, with_effects, optimize & !annotate_source, iswarn&get(iostream, :color, false)::Bool, hide_type_stable, custom_toggles; menu_options...)
-        usg = usage(view_cmd, annotate_source, optimize, iswarn, hide_type_stable, debuginfo, remarks, with_effects, inline_cost, type_annotations, CONFIG.enable_highlighter, inlay_types_vscode, diagnostics_vscode, custom_toggles)
+        menu = CthulhuMenu(shown_callsites, with_effects, exception_type,
+                           optimize & !annotate_source,
+                           iswarn&get(iostream, :color, false)::Bool,
+                           hide_type_stable, custom_toggles; menu_options...)
+        usg = usage(view_cmd, annotate_source, optimize, iswarn, hide_type_stable,
+                    debuginfo, remarks, with_effects, exception_type, inline_cost,
+                    type_annotations, CONFIG.enable_highlighter, inlay_types_vscode,
+                    diagnostics_vscode, jump_always, custom_toggles)
         cid = request(term, usg, menu)
         toggle = menu.toggle
 
@@ -554,7 +664,9 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs
                         end
                     end
                 end
-                menu = CthulhuMenu(show_sub_callsites, with_effects, optimize & !annotate_source, false, false, custom_toggles; sub_menu=true, menu_options...)
+                menu = CthulhuMenu(show_sub_callsites, with_effects, exception_type,
+                                   optimize & !annotate_source, false, false, custom_toggles;
+                                   sub_menu=true, menu_options...)
                 cid = request(term, "", menu)
                 if cid == length(sub_callsites) + 1
                     continue
@@ -599,12 +711,17 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs
                      override = get_override(info), debuginfo,
                      optimize, interruptexc,
                      iswarn, hide_type_stable,
-                     remarks, with_effects, inline_cost, type_annotations, annotate_source, inlay_types_vscode, diagnostics_vscode)
+                     remarks, with_effects, exception_type, inline_cost,
+                     type_annotations, annotate_source,
+                     inlay_types_vscode, diagnostics_vscode,
+                     jump_always)
 
         elseif toggle === :warn
             iswarn ⊻= true
         elseif toggle === :with_effects
             with_effects ⊻= true
+        elseif toggle === :exception_type
+            exception_type ⊻= true
         elseif toggle === :hide_type_stable
             hide_type_stable ⊻= true
         elseif toggle === :inlay_types_vscode
@@ -669,6 +786,8 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs
         elseif toggle === :edit
             edit(whereis(mi.def::Method)...)
             display_CI = false
+        elseif toggle === :jump_always
+            jump_always ⊻= true
         elseif toggle === :typed
             view_cmd = cthulhu_typed
             annotate_source = false
@@ -679,8 +798,9 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs
             display_CI = true
         elseif toggle === :ast || toggle === :llvm || toggle === :native
             view_cmd = CODEVIEWS[toggle]
+            world = get_inference_world(interp)
             println(iostream)
-            view_cmd(iostream, mi, optimize, debuginfo, interp, CONFIG)
+            view_cmd(iostream, mi, optimize, debuginfo, world, CONFIG)
             display_CI = false
         else
             local i = findfirst(ct->ct.toggle === toggle, custom_toggles)
@@ -716,15 +836,10 @@ function do_typeinf!(interp::AbstractInterpreter, mi::MethodInstance)
     return nothing
 end
 
-function get_specialization(@nospecialize(TT))
-    match = Base._which(TT)
-    mi = specialize_method(match)
-    return mi
-end
+get_specialization(@nospecialize tt::Type{<:Tuple}) = specialize_method(Base._which(tt))
 
-function get_specialization(@nospecialize(F), @nospecialize(TT))
-    return get_specialization(Base.signature_type(F, TT))
-end
+get_specialization(@nospecialize(f), @nospecialize(tt=default_tt(f))) =
+    get_specialization(Base.signature_type(f, tt))
 
 function mkinterp(interp::AbstractInterpreter, @nospecialize(args...))
     interp′ = CthulhuInterpreter(interp)
@@ -780,6 +895,7 @@ function ascend(term, mi; interp::AbstractInterpreter=NativeInterpreter(), kwarg
                 miparent = instance(node.parent.data.nd)
                 ulocs = find_caller_of(interp, miparent, mi; allow_unspecialized=true)
                 if !isempty(ulocs)
+                    ulocs = [(k[1], maybe_fix_path(String(k[2])), k[3]) => v for (k, v) in ulocs]
                     strlocs = [string(" "^k[3] * '"', k[2], "\", ", k[1], ": lines ", v) for (k, v) in ulocs]
                     explain_inlining = length(ulocs) > 1 ? "(including inlined callers represented by indentation) " : ""
                     push!(strlocs, "Browse typed code")
@@ -794,8 +910,8 @@ function ascend(term, mi; interp::AbstractInterpreter=NativeInterpreter(), kwarg
                         end
                         choice2 = TerminalMenus.request(term, promptstr, linemenu; cursor=choice2)
                         if 0 < choice2 < length(strlocs)
-                            loc = ulocs[choice2]
-                            edit(String(loc[1][2]), first(loc[2]))
+                            loc, lines = ulocs[choice2]
+                            edit(loc[2], first(lines))
                         elseif choice2 == length(strlocs)
                             browsecodetyped = true
                             break
@@ -803,6 +919,9 @@ function ascend(term, mi; interp::AbstractInterpreter=NativeInterpreter(), kwarg
                     end
                 end
             end
+            if !isa(mi, MethodInstance)
+                error("You can only descend into known calls. If you tried to descend into a runtime-dispatched signature, try its caller instead.")
+            end
             # The main application of `ascend` is finding cases of non-inferrability, so the
             # warn highlighting is useful.
             interp′ = CthulhuInterpreter(interp)
@@ -824,13 +943,17 @@ ascend
 
 using PrecompileTools
 @setup_workload begin
-    input = Base.link_pipe!(Pipe(), reader_supports_async=true, writer_supports_async=true)
-    term = REPL.Terminals.TTYTerminal("dumb", input.out, devnull, devnull)
-    write(input.in, 'q')
-    @compile_workload begin
-        descend(gcd, (Int, Int); terminal=term)
-        # declare we are done with streams
-        close(input.in)
+    try
+        input = Base.link_pipe!(Pipe(), reader_supports_async=true, writer_supports_async=true)
+        term = REPL.Terminals.TTYTerminal("dumb", input.out, devnull, devnull)
+        write(input.in, 'q')
+        @compile_workload begin
+            descend(gcd, (Int, Int); terminal=term)
+            # declare we are done with streams
+            close(input.in)
+        end
+    catch err
+        @error "Errorred while running the precompile workload, the package may or may not work but latency will be long" exeption=(err,catch_backtrace())
     end
 end
 
diff --git a/src/backedges.jl b/src/backedges.jl
index 99b6ce9..b33cec3 100644
--- a/src/backedges.jl
+++ b/src/backedges.jl
@@ -88,13 +88,67 @@ function buildframes(st::Vector{StackTraces.StackFrame})
     return ipframes
 end
 
-# Extension API
+## Extension API
+
+# With hindsight, this API was poorly designed and would be worth fixing in the next breaking release.
+# See issue #582
+
+"""
+    edges = backedges(obj)
+
+Return an iterable of children (callers) of `obj`. An `edge` should support [`Cthulhu.method`](@ref)
+and [`Cthulhu.specTypes`](@ref), as well as [`Cthulhu.nextnode`](@ref) to get the next node object.
+
+Part of the `ascend` API.
+"""
+function backedges end
+
+"""
+    inst = instance(obj)
+
+Return the current node data corresponding to `obj`. `inst` should support [`Cthulhu.method`](@ref)
+and [`Cthulhu.specTypes`](@ref).
+
+Part of the `ascend` API.
+"""
+function instance end
+
+"""
+    m = method(edge)
+
+Return the method corresponding to `edge`.
+
+Part of the `ascend` API.
+"""
+function method end
+
+"""
+    sig = specTypes(edge)
+
+Return the signature of `edge`.
+
+Part of the `ascend` API.
+"""
+function specTypes end
+
+"""
+    childobj = nextnode(obj, edge)
+
+Return the next node object from `obj` indicated by `edge`.
+
+Part of the `ascend` API.
+"""
+function nextnode end
+
+
 backedges(mi::MethodInstance) = isdefined(mi, :backedges) ? mi.backedges : _emptybackedges
 method(mi::MethodInstance) = mi.def
 specTypes(mi::MethodInstance) = mi.specTypes
 instance(mi::MethodInstance) = mi
 nextnode(mi, edge) = edge
 
+instance(@nospecialize(tt::Type)) = tt
+
 instance(sfs::Vector{StackTraces.StackFrame}) = isempty(sfs) ? CC.Timings.ROOTmi : sfs[end].linfo::MethodInstance # we checked this type condition within `buildframes`
 method(sfs::Vector{StackTraces.StackFrame}) = method(instance(sfs))
 backedges(sframes::Vector{StackTraces.StackFrame}) = (ret = sframes[2:end]; isempty(ret) ? () : (ret,))
@@ -102,9 +156,9 @@ backedges(sframes::Vector{StackTraces.StackFrame}) = (ret = sframes[2:end]; isem
 instance(ipframes::Vector{IPFrames}) = isempty(ipframes) ? CC.Timings.ROOTmi : instance(ipframes[1].sfs)
 backedges(ipframes::Vector{IPFrames}) = (ret = ipframes[2:end]; isempty(ret) ? () : (ret,))
 
-function callstring(io::IO, obj)
-    name = isa(method(obj), Method) ? method(obj).name : :toplevel
-    show_tuple_as_call(nonconcrete_red, IOContext(io, :color=>true), name, specTypes(obj))
+function callstring(io::IO, edge)
+    name = isa(method(edge), Method) ? method(edge).name : :toplevel
+    show_tuple_as_call(nonconcrete_red, IOContext(io, :color=>true), name, specTypes(edge))
     return String(take!(io))
 end
 function callstring(io::IO, sfs::Vector{StackTraces.StackFrame})
@@ -125,16 +179,18 @@ end
 
 function treelist(mi)
     io = IOBuffer()
-    str = callstring(io, mi)
-    treelist!(Node(Data(str, instance(mi))), io, mi, "", Base.IdSet{typeof(instance(mi))}())
+    imi = instance(mi)
+    str = callstring(io, imi)
+    treelist!(Node(Data(str, imi)), io, mi, "", Base.IdSet{typeof(imi)}())
 end
 function treelist!(parent::Node, io::IO, mi, indent::AbstractString, visited::Base.IdSet)
-    mi ∈ visited && return parent
-    push!(visited, instance(mi))
+    imi = instance(mi)
+    imi ∈ visited && return parent
+    push!(visited, imi)
     indent *= " "
     for edge in backedges(mi)
         str = indent * callstring(io, edge)
-        child = Node(Data(str, instance(edge)), parent)
+        child = Node(typeof(parent.data)(str, instance(edge)), parent)
         treelist!(child, io, nextnode(mi, edge), indent, visited)
     end
     return parent
diff --git a/src/callsite.jl b/src/callsite.jl
index 5410c32..a8959a8 100644
--- a/src/callsite.jl
+++ b/src/callsite.jl
@@ -7,17 +7,19 @@ struct MICallInfo <: CallInfo
     mi::MethodInstance
     rt
     effects::Effects
-    function MICallInfo(mi::MethodInstance, @nospecialize(rt), effects)
+    exct
+    function MICallInfo(mi::MethodInstance, @nospecialize(rt), effects, @nospecialize(exct=nothing))
         if isa(rt, LimitedAccuracy)
-            return LimitedCallInfo(new(mi, ignorelimited(rt), effects))
+            return LimitedCallInfo(new(mi, ignorelimited(rt), effects, exct))
         else
-            return new(mi, rt, effects)
+            return new(mi, rt, effects, exct)
         end
     end
 end
 get_mi(ci::MICallInfo) = ci.mi
-get_rt(ci::CallInfo) = ci.rt
+get_rt(ci::MICallInfo) = ci.rt
 get_effects(ci::MICallInfo) = ci.effects
+get_exct(ci::MICallInfo) = ci.exct
 
 abstract type WrappedCallInfo <: CallInfo end
 
@@ -27,6 +29,7 @@ ignorewrappers(ci::WrappedCallInfo) = ignorewrappers(get_wrapped(ci))
 get_mi(ci::WrappedCallInfo) = get_mi(ignorewrappers(ci))
 get_rt(ci::WrappedCallInfo) = get_rt(ignorewrappers(ci))
 get_effects(ci::WrappedCallInfo) = get_effects(ignorewrappers(ci))
+get_exct(ci::WrappedCallInfo) = get_exct(ignorewrappers(ci))
 
 # only appears when inspecting pre-optimization states
 struct LimitedCallInfo <: WrappedCallInfo
@@ -38,9 +41,12 @@ struct RTCallInfo <: CallInfo
     f
     argtyps
     rt
+    exct
 end
+get_rt(ci::RTCallInfo) = ci.rt
 get_mi(ci::RTCallInfo) = nothing
 get_effects(ci::RTCallInfo) = Effects()
+get_exct(ci::RTCallInfo) = ci.exct
 
 # uncached callsite, we can't recurse into this call
 struct UncachedCallInfo <: WrappedCallInfo
@@ -56,6 +62,7 @@ end
 get_mi(::PureCallInfo) = nothing
 get_rt(pci::PureCallInfo) = pci.rt
 get_effects(::PureCallInfo) = EFFECTS_TOTAL
+get_exct(::PureCallInfo) = Union{}
 
 # Failed
 struct FailedCallInfo <: CallInfo
@@ -64,7 +71,8 @@ struct FailedCallInfo <: CallInfo
 end
 get_mi(ci::FailedCallInfo) = fail(ci)
 get_rt(ci::FailedCallInfo) = fail(ci)
-get_effects(ci::FailedCallInfo) = Effects()
+get_effects(ci::FailedCallInfo) = fail(ci)
+get_exct(ci::FailedCallInfo) = fail(ci)
 function fail(ci::FailedCallInfo)
     @warn "MethodInstance extraction failed." ci.sig ci.rt
     return nothing
@@ -77,7 +85,8 @@ struct GeneratedCallInfo <: CallInfo
 end
 get_mi(genci::GeneratedCallInfo) = fail(genci)
 get_rt(genci::GeneratedCallInfo) = fail(genci)
-get_effects(genci::GeneratedCallInfo) = Effects()
+get_effects(genci::GeneratedCallInfo) = fail(genci)
+get_exct(genci::GeneratedCallInfo) = fail(genci)
 function fail(genci::GeneratedCallInfo)
     @warn "Can't extract MethodInstance from call to generated functions." genci.sig genci.rt
     return nothing
@@ -86,13 +95,16 @@ end
 struct MultiCallInfo <: CallInfo
     sig
     rt
+    exct
     callinfos::Vector{CallInfo}
+    MultiCallInfo(@nospecialize(sig), @nospecialize(rt), callinfos::Vector{CallInfo},
+                  @nospecialize(exct=nothing)) =
+        new(sig, rt, exct, callinfos)
 end
-# actual code-error
 get_mi(ci::MultiCallInfo) = error("Can't extract MethodInstance from multiple call informations")
-get_effects(mci::MultiCallInfo) = @static VERSION ≥ v"1.9-" ?
-    mapreduce(get_effects, CC.merge_effects, mci.callinfos) :
-    mapreduce(get_effects, CC.tristate_merge, mci.callinfos)
+get_rt(ci::MultiCallInfo) = ci.rt
+get_effects(mci::MultiCallInfo) = mapreduce(get_effects, CC.merge_effects, mci.callinfos)
+get_exct(ci::MultiCallInfo) = ci.exct
 
 struct TaskCallInfo <: CallInfo
     ci::CallInfo
@@ -100,6 +112,7 @@ end
 get_mi(tci::TaskCallInfo) = get_mi(tci.ci)
 get_rt(tci::TaskCallInfo) = get_rt(tci.ci)
 get_effects(tci::TaskCallInfo) = get_effects(tci.ci)
+get_exct(tci::TaskCallInfo) = get_exct(tci.ci)
 
 struct InvokeCallInfo <: CallInfo
     ci::CallInfo
@@ -108,6 +121,7 @@ end
 get_mi(ici::InvokeCallInfo) = get_mi(ici.ci)
 get_rt(ici::InvokeCallInfo) = get_rt(ici.ci)
 get_effects(ici::InvokeCallInfo) = get_effects(ici.ci)
+get_exct(ici::InvokeCallInfo) = get_exct(ici.ci)
 
 # OpaqueClosure CallInfo
 struct OCCallInfo <: CallInfo
@@ -117,6 +131,7 @@ end
 get_mi(occi::OCCallInfo) = get_mi(occi.ci)
 get_rt(occi::OCCallInfo) = get_rt(occi.ci)
 get_effects(occi::OCCallInfo) = get_effects(occi.ci)
+get_exct(occi::OCCallInfo) = get_exct(occi.ci)
 
 # Special handling for ReturnTypeCall
 struct ReturnTypeCallInfo <: CallInfo
@@ -125,6 +140,7 @@ end
 get_mi((; vmi)::ReturnTypeCallInfo) = isa(vmi, FailedCallInfo) ? nothing : get_mi(vmi)
 get_rt((; vmi)::ReturnTypeCallInfo) = Type{isa(vmi, FailedCallInfo) ? Union{} : widenconst(get_rt(vmi))}
 get_effects(::ReturnTypeCallInfo) = EFFECTS_TOTAL
+get_exct(::ReturnTypeCallInfo) = Union{} # FIXME
 
 struct ConstPropCallInfo <: CallInfo
     mi::CallInfo
@@ -133,6 +149,7 @@ end
 get_mi(cpci::ConstPropCallInfo) = cpci.result.linfo
 get_rt(cpci::ConstPropCallInfo) = get_rt(cpci.mi)
 get_effects(cpci::ConstPropCallInfo) = get_effects(cpci.result)
+get_exct(cpci::ConstPropCallInfo) = get_exct(cpci.mi)
 
 struct ConcreteCallInfo <: CallInfo
     mi::CallInfo
@@ -141,6 +158,7 @@ end
 get_mi(ceci::ConcreteCallInfo) = get_mi(ceci.mi)
 get_rt(ceci::ConcreteCallInfo) = get_rt(ceci.mi)
 get_effects(ceci::ConcreteCallInfo) = get_effects(ceci.mi)
+get_exct(cici::ConcreteCallInfo) = get_exct(ceci.mi)
 
 struct SemiConcreteCallInfo <: CallInfo
     mi::CallInfo
@@ -149,6 +167,7 @@ end
 get_mi(scci::SemiConcreteCallInfo) = get_mi(scci.mi)
 get_rt(scci::SemiConcreteCallInfo) = get_rt(scci.mi)
 get_effects(scci::SemiConcreteCallInfo) = get_effects(scci.mi)
+get_exct(scci::SemiConcreteCallInfo) = get_exct(scci.mi)
 
 # CUDA callsite
 struct CuCallInfo <: CallInfo
@@ -189,22 +208,22 @@ function headstring(@nospecialize(T))
     end
 end
 
-function __show_limited(limiter, name, tt, @nospecialize(rt), effects)
+function __show_limited(limiter, name, tt, @nospecialize(rt), effects, @nospecialize(exct=nothing))
     vastring(@nospecialize(T)) = (isvarargtype(T) ? headstring(T)*"..." : string(T)::String)
 
     # If effects are explicitly turned on, make sure to print them, even
     # if there otherwise isn't space for them, since the effects are the
     # most important piece of information if turned on.
     with_effects = get(limiter, :with_effects, false)::Bool
+    exception_type = get(limiter, :exception_type, false)::Bool && exct !== nothing
 
-    if with_effects
-        limiter.width += textwidth(repr(effects)) + 1
-    end
-
+    with_effects && (limiter.width += textwidth(repr(effects)) + 1)
+    exception_type && (limiter.width += textwidth(string(exct)) + 1)
     if !has_space(limiter, name)
         print(limiter, '…')
         @goto print_effects
     end
+
     print(limiter, string(name))
     pstrings = String[vastring(T) for T in tt]
     headstrings = String[
@@ -236,15 +255,28 @@ function __show_limited(limiter, name, tt, @nospecialize(rt), effects)
         print(limiter, "::…")
     end
 
-@label print_effects
+    @label print_effects
     if with_effects
         # Print effects unlimited
         print(limiter.io, " ", effects)
     end
+    if exception_type
+        print(limiter.io, ' ', ExctWrapper(exct))
+    end
 
     return nothing
 end
 
+struct ExctWrapper
+    exct
+    ExctWrapper(@nospecialize exct) = new(exct)
+end
+
+function Base.show(io::IO, (;exct)::ExctWrapper)
+    color = exct === Union{} ? :green : :yellow
+    printstyled(io, "(↑::", exct, ")"; color)
+end
+
 function show_callinfo(limiter, mici::MICallInfo)
     mi = mici.mi
     tt = (Base.unwrap_unionall(mi.specTypes)::DataType).parameters[2:end]
@@ -254,7 +286,8 @@ function show_callinfo(limiter, mici::MICallInfo)
         name = mi.def.name
     end
     rt = get_rt(mici)
-    __show_limited(limiter, name, tt, rt, get_effects(mici))
+    exct = get_exct(mici)
+    __show_limited(limiter, name, tt, rt, get_effects(mici), exct)
 end
 
 function show_callinfo(limiter, ci::Union{MultiCallInfo, FailedCallInfo, GeneratedCallInfo})
@@ -380,8 +413,7 @@ function print_callsite_info(limiter::IO, info::OCCallInfo)
     show_callinfo(limiter, info.ci)
 end
 
-const is_expected_union = @static VERSION ≥ v"1.9-" ?
-    InteractiveUtils.is_expected_union : Base.is_expected_union
+const is_expected_union = InteractiveUtils.is_expected_union
 
 function Base.show(io::IO, c::Callsite)
     limit = get(io, :limit, false)::Bool
@@ -474,6 +506,7 @@ end
 
 # maybe_callsite for higher-level inputs
 maybe_callsite(cs::Callsite, callee::MethodInstance) = maybe_callsite(cs.info, callee)
+maybe_callsite(cs::Callsite, @nospecialize(tt::Type)) = maybe_callsite(cs.info, tt)
 maybe_callsite(info::CallInfo, callee::MethodInstance) = maybe_callsite(get_mi(info), callee)
 # Special CallInfo cases:
 function maybe_callsite(info::MultiCallInfo, callee::MethodInstance)
@@ -485,4 +518,19 @@ end
 maybe_callsite(info::PureCallInfo, mi::MethodInstance) = mi.specTypes <: Tuple{mapany(Core.Typeof ∘ unwrapconst, info.argtypes)...}
 maybe_callsite(info::RTCallInfo, mi::MethodInstance) = false
 
+function maybe_callsite(info::RTCallInfo, @nospecialize(tt::Type))
+    isa(tt, Union) && return maybe_callsite(info, tt.a) || maybe_callsite(info, tt.b)
+    isa(tt, DataType) || return false
+    typeof(info.f) === tt.parameters[1] || return false
+    for (a, b) in zip(info.argtyps, tt.parameters[2:end])
+        a === b || return false
+    end
+    return true
+end
+function maybe_callsite(info::MICallInfo, @nospecialize(tt::Type))
+    return tt <: info.mi.specTypes
+end
+
+maybe_callsite(info::CallInfo, @nospecialize(tt::Type)) = false
+
 unwrapconst(@nospecialize(arg)) = arg isa Core.Const ? arg.val : arg
diff --git a/src/codeview.jl b/src/codeview.jl
index fc33e68..bc55aa4 100644
--- a/src/codeview.jl
+++ b/src/codeview.jl
@@ -20,44 +20,28 @@ function highlight(io, x, lexer, config::CthulhuConfig)
     end
 end
 
-function cthulhu_llvm(io::IO, mi, optimize, debuginfo, interp::CthulhuInterpreter,
+function cthulhu_llvm(io::IO, mi, optimize::Bool, debuginfo, world::UInt,
                       config::CthulhuConfig, dump_module::Bool=false, raw::Bool=false)
-    dump = @static if VERSION ≥ v"1.10.0-DEV.1386"
-        InteractiveUtils._dump_function_llvm(
-            mi, get_world_counter(interp),
-            #=wrapper=# false, !raw,
-            dump_module, optimize, debuginfo != DInfo.none ? :source : :none,
-            Base.CodegenParams())
-    else
-        InteractiveUtils._dump_function_linfo_llvm(
-            mi, get_world_counter(interp),
-            #=wrapper=# false, #=strip_ir_metadata=# true,
-            dump_module, optimize, debuginfo != DInfo.none ? :source : :none,
-            Base.CodegenParams())
-    end
+    dump = InteractiveUtils._dump_function_llvm(
+        mi, world,
+        #=wrapper=# false, !raw,
+        dump_module, optimize, debuginfo != DInfo.none ? :source : :none,
+        Base.CodegenParams())
     highlight(io, dump, "llvm", config)
 end
 
-function cthulhu_native(io::IO, mi, optimize, debuginfo, interp::CthulhuInterpreter,
+function cthulhu_native(io::IO, mi, ::Bool, debuginfo, world::UInt,
                         config::CthulhuConfig, dump_module::Bool=false, raw::Bool=false)
-    dump = @static if VERSION ≥ v"1.10.0-DEV.1386"
-        if dump_module
-            InteractiveUtils._dump_function_native_assembly(
-                mi, get_world_counter(interp),
-                #=wrapper=# false, #=syntax=# config.asm_syntax,
-                debuginfo != DInfo.none ? :source : :none,
-                #=binary=# false, raw,
-                Base.CodegenParams())
-        else
-            InteractiveUtils._dump_function_native_disassembly(
-                mi, get_world_counter(interp),
-                #=wrapper=# false, #=syntax=# config.asm_syntax,
-                debuginfo != DInfo.none ? :source : :none,
-                #=binary=# false)
-        end
+    if dump_module
+        dump = InteractiveUtils._dump_function_native_assembly(
+            mi, world,
+            #=wrapper=# false, #=syntax=# config.asm_syntax,
+            debuginfo != DInfo.none ? :source : :none,
+            #=binary=# false, raw,
+            Base.CodegenParams())
     else
-        InteractiveUtils._dump_function_linfo_native(
-            mi, get_world_counter(interp),
+        dump = InteractiveUtils._dump_function_native_disassembly(
+            mi, world,
             #=wrapper=# false, #=syntax=# config.asm_syntax,
             debuginfo != DInfo.none ? :source : :none,
             #=binary=# false)
@@ -65,7 +49,7 @@ function cthulhu_native(io::IO, mi, optimize, debuginfo, interp::CthulhuInterpre
     highlight(io, dump, "asm", config)
 end
 
-function cthulhu_ast(io::IO, mi, optimize, debuginfo, ::CthulhuInterpreter, config::CthulhuConfig)
+function cthulhu_ast(io::IO, mi, ::Bool, debuginfo, ::UInt, config::CthulhuConfig)
     meth = mi.def::Method
     ast = definition(Expr, meth)
     if ast!==nothing
@@ -106,7 +90,7 @@ function cthulhu_warntype(io::IO, debuginfo::AnyDebugInfo,
     if inline_cost
         isa(mi, MethodInstance) || error("Need a MethodInstance to show inlining costs. Call `cthulhu_typed` directly instead.")
     end
-    cthulhu_typed(io, debuginfo, src, rt, effects, mi; iswarn=true, optimize, hide_type_stable, inline_cost, interp)
+    cthulhu_typed(io, debuginfo, src, rt, nothing, effects, mi; iswarn=true, optimize, hide_type_stable, inline_cost, interp)
     return nothing
 end
 
@@ -121,11 +105,14 @@ end
 cthulhu_typed(io::IO, debuginfo::DebugInfo, args...; kwargs...) =
     cthulhu_typed(io, Symbol(debuginfo), args...; kwargs...)
 function cthulhu_typed(io::IO, debuginfo::Symbol,
-    src::Union{CodeInfo,IRCode}, @nospecialize(rt), effects::Effects, mi::Union{Nothing,MethodInstance};
+    src::Union{CodeInfo,IRCode}, @nospecialize(rt), @nospecialize(exct),
+    effects::Effects, mi::Union{Nothing,MethodInstance};
     iswarn::Bool=false, hide_type_stable::Bool=false, optimize::Bool=true,
-    pc2remarks::Union{Nothing,PC2Remarks}=nothing, pc2effects::Union{Nothing,PC2Effects}=nothing,
+    pc2remarks::Union{Nothing,PC2Remarks}=nothing,
+    pc2effects::Union{Nothing,PC2Effects}=nothing,
+    pc2excts::Union{Nothing,PC2Excts}=nothing,
     inline_cost::Bool=false, type_annotations::Bool=true, annotate_source::Bool=false,
-    inlay_types_vscode::Bool=false, diagnostics_vscode::Bool=false,
+    inlay_types_vscode::Bool=false, diagnostics_vscode::Bool=false, jump_always::Bool=false,
     interp::AbstractInterpreter=CthulhuInterpreter())
 
     debuginfo = IRShow.debuginfo(debuginfo)
@@ -140,9 +127,6 @@ function cthulhu_typed(io::IO, debuginfo::Symbol,
             # We empty the body when filling kwargs
             istruncated = isempty(children(body))
             idxend = istruncated ? JuliaSyntax.last_byte(sig) : lastindex(tsn.source)
-            if any(iszero, src.codelocs)
-                @warn "Some line information is missing, type-assignment may be incomplete"
-            end
             if src.slottypes === nothing
                 @warn "Inference terminated in an incomplete state due to argument-type changes during recursion"
             end
@@ -155,10 +139,10 @@ function cthulhu_typed(io::IO, debuginfo::Symbol,
             if !TypedSyntax.inlay_hints_available_vscode() || isnothing(functionloc(mi)[1])
                 inlay_types_vscode = false
             end
-  
+
             vscode_io = IOContext(
-                lambda_io, 
-                :inlay_hints => inlay_types_vscode ? Dict{String,Vector{TypedSyntax.InlayHint}}() : nothing , 
+                jump_always && inlay_types_vscode ? devnull : lambda_io,
+                :inlay_hints => inlay_types_vscode ? Dict{String,Vector{TypedSyntax.InlayHint}}() : nothing ,
                 :diagnostics => diagnostics_vscode ? TypedSyntax.Diagnostic[] : nothing
             )
 
@@ -167,7 +151,7 @@ function cthulhu_typed(io::IO, debuginfo::Symbol,
             else
                 printstyled(vscode_io, tsn; type_annotations, iswarn, hide_type_stable, idxend)
             end
-            
+
             callsite_diagnostics = TypedSyntax.Diagnostic[]
             if (diagnostics_vscode || inlay_types_vscode)
                 vscode_io = IOContext(devnull, :inlay_hints=>vscode_io[:inlay_hints], :diagnostics=>vscode_io[:diagnostics])
@@ -184,8 +168,8 @@ function cthulhu_typed(io::IO, debuginfo::Symbol,
             !isnothing(vscode_io[:diagnostics]) && append!(callsite_diagnostics, vscode_io[:diagnostics])
             TypedSyntax.display_diagnostics_vscode(callsite_diagnostics)
             TypedSyntax.display_inlay_hints_vscode(vscode_io)
- 
-            println(lambda_io)
+
+            (jump_always && inlay_types_vscode) || println(lambda_io)
             istruncated && @info "This method only fills in default arguments; descend into the body method to see the full source."
             return nothing
         end
@@ -195,7 +179,6 @@ function cthulhu_typed(io::IO, debuginfo::Symbol,
         # we're working on pre-optimization state, need to ignore `LimitedAccuracy`
         src = copy(src)
         src.ssavaluetypes = mapany(ignorelimited, src.ssavaluetypes::Vector{Any})
-        src.rettype = ignorelimited(src.rettype)
 
         if src.slotnames !== nothing
             slotnames = Base.sourceinfo_slotnames(src)
@@ -218,10 +201,11 @@ function cthulhu_typed(io::IO, debuginfo::Symbol,
         end
         cst = Vector{Int}(undef, length(code))
         params = CC.OptimizationParams(interp)
+        sparams = CC.VarState[CC.VarState(sparam, false) for sparam in mi.sparam_vals]
         @static if VERSION ≥ v"1.11.0-DEV.32"
-            CC.statement_costs!(cst, code, src, sptypes(mi.sparam_vals), params)
+            CC.statement_costs!(cst, code, src, sparams, params)
         else
-            CC.statement_costs!(cst, code, src, sptypes(mi.sparam_vals), false, params)
+            CC.statement_costs!(cst, code, src, sparams, false, params)
         end
         total_cost = sum(cst)
         nd = ndigits(total_cost)
@@ -248,28 +232,27 @@ function cthulhu_typed(io::IO, debuginfo::Symbol,
         end
     end
     # postprinter configuration
-    __postprinter = if type_annotations
+    ___postprinter = if type_annotations
         iswarn ? InteractiveUtils.warntype_type_printer : IRShow.default_expr_type_printer
     else
         Returns(nothing)
     end
-    _postprinter = if isa(src, CodeInfo) && !isnothing(pc2effects)
-        @static if VERSION ≥ v"1.9-"
-            function (io::IO; idx::Int, @nospecialize(kws...))
-                __postprinter(io; idx, kws...)
-                local effects = get(pc2effects, idx, nothing)
-                effects === nothing && return
-                print(io, ' ', effects)
-            end
-        else
-            function (io::IO, @nospecialize(typ), used::Bool)
-                __postprinter(io, typ, used)
-                haskey(io, :idx) || return
-                idx = io[:idx]::Int
-                local effects = get(pc2effects, idx, nothing)
-                effects === nothing && return
-                print(io, ' ', effects)
-            end
+    __postprinter = if isa(src, CodeInfo) && !isnothing(pc2effects)
+        function (io::IO; idx::Int, @nospecialize(kws...))
+            ___postprinter(io; idx, kws...)
+            local effects = get(pc2effects, idx, nothing)
+            effects === nothing && return
+            print(io, ' ', effects)
+        end
+    else
+        ___postprinter
+    end
+    _postprinter = if isa(src, CodeInfo) && !isnothing(pc2excts)
+        function (io::IO; idx::Int, @nospecialize(kws...))
+            __postprinter(io; idx, kws...)
+            local exct = get(pc2excts, idx, nothing)
+            exct === nothing && return
+            print(io, ' ', ExctWrapper(exct))
         end
     else
         __postprinter
@@ -277,21 +260,10 @@ function cthulhu_typed(io::IO, debuginfo::Symbol,
     postprinter = if isa(src, CodeInfo) && !isnothing(pc2remarks)
         sort!(pc2remarks)
         unique!(pc2remarks) # abstract interpretation may have visited a same statement multiple times
-        @static if VERSION ≥ v"1.9-"
-            function (io::IO; idx::Int, @nospecialize(kws...))
-                _postprinter(io; idx, kws...)
-                for i = searchsorted(pc2remarks, idx=>"", by=((idx,msg),)->idx)
-                    printstyled(io, ' ', pc2remarks[i].second; color=:light_black)
-                end
-            end
-        else
-            function (io::IO, @nospecialize(typ), used::Bool)
-                _postprinter(io, typ, used)
-                haskey(io, :idx) || return
-                idx = io[:idx]::Int
-                for i = searchsorted(pc2remarks, idx=>"", by=((idx,msg),)->idx)
-                    printstyled(io, ' ', pc2remarks[i].second; color=:light_black)
-                end
+        function (io::IO; idx::Int, @nospecialize(kws...))
+            _postprinter(io; idx, kws...)
+            for i = searchsorted(pc2remarks, idx=>"", by=((idx,msg),)->idx)
+                printstyled(io, ' ', pc2remarks[i].second; color=:light_black)
             end
         end
     else
@@ -305,12 +277,7 @@ function cthulhu_typed(io::IO, debuginfo::Symbol,
 
     if iswarn
         print(lambda_io, "Body")
-        @static if VERSION ≥ v"1.9-"
-            # https://github.com/JuliaLang/julia/pull/46574
-            InteractiveUtils.warntype_type_printer(lambda_io; type=rettype, used=true)
-        else
-            InteractiveUtils.warntype_type_printer(lambda_io, rettype, true)
-        end
+        InteractiveUtils.warntype_type_printer(lambda_io; type=rettype, used=true)
         if get(lambda_io, :with_effects, false)::Bool
             print(lambda_io, ' ', effects)
         end
@@ -320,7 +287,7 @@ function cthulhu_typed(io::IO, debuginfo::Symbol,
         cfg = src isa IRCode ? src.cfg : Core.Compiler.compute_basic_blocks(src.code)
         max_bb_idx_size = length(string(length(cfg.blocks)))
         str = irshow_config.line_info_preprinter(lambda_io, " "^(max_bb_idx_size + 2), -1)
-        callsite = Callsite(0, MICallInfo(mi, rettype, effects), :invoke)
+        callsite = Callsite(0, MICallInfo(mi, rettype, effects, exct), :invoke)
         println(lambda_io, "∘ ", "─"^(max_bb_idx_size), str, " ", callsite)
     end
 
@@ -328,7 +295,7 @@ function cthulhu_typed(io::IO, debuginfo::Symbol,
     return nothing
 end
 
-function descend_into_callsite!(io::IO, tsn::TypedSyntaxNode; 
+function descend_into_callsite!(io::IO, tsn::TypedSyntaxNode;
     iswarn::Bool, hide_type_stable::Bool, type_annotations::Bool)
     sig, body = children(tsn)
     # We empty the body when filling kwargs
@@ -340,10 +307,10 @@ function descend_into_callsite!(io::IO, tsn::TypedSyntaxNode;
 end
 
 function add_callsites!(d::AbstractDict, visited_mis::AbstractSet, diagnostics::AbstractVector,
-    mi::MethodInstance, source_mi::MethodInstance=mi; 
+    mi::MethodInstance, source_mi::MethodInstance=mi;
     optimize::Bool=true, annotate_source::Bool=false,
     interp::AbstractInterpreter=CthulhuInterpreter())
-    
+
     callsites, src, rt = try
         (; src, rt, infos, slottypes, effects, codeinf) = lookup(interp, mi, optimize & !annotate_source)
 
@@ -354,7 +321,7 @@ function add_callsites!(d::AbstractDict, visited_mis::AbstractSet, diagnostics::
             @assert length(src.code) == length(infos)
         end
 
-        # We pass false as it doesn't affect callsites and skips fetching the method definition 
+        # We pass false as it doesn't affect callsites and skips fetching the method definition
         # using CodeTracking which is slow
         callsites, _ = find_callsites(interp, src, infos, mi, slottypes, optimize & !annotate_source, false)
         callsites, src, rt
@@ -376,7 +343,7 @@ function add_callsites!(d::AbstractDict, visited_mis::AbstractSet, diagnostics::
         return nothing
     end
     tsn, _ = get_typed_sourcetext(mi, src, rt; warn=false)
-    isnothing(tsn) && return nothing 
+    isnothing(tsn) && return nothing
     sig, body = children(tsn)
     # We empty the body when filling kwargs
     istruncated = isempty(children(body))
@@ -387,10 +354,10 @@ function add_callsites!(d::AbstractDict, visited_mis::AbstractSet, diagnostics::
     if haskey(d, key)
         if !isnothing(d[key]) && mi != d[key].mi
             d[key] = nothing
-            push!(diagnostics, 
+            push!(diagnostics,
                 TypedSyntax.Diagnostic(
-                    isnothing(functionloc(mi)[1]) ? string(mi.file) : functionloc(mi)[1], mi.def.line, 
-                    TypedSyntax.DiagnosticKinds.Information, 
+                    isnothing(functionloc(mi)[1]) ? string(mi.file) : functionloc(mi)[1], mi.def.line,
+                    TypedSyntax.DiagnosticKinds.Information,
                     "Cthulhu disabled: This function was called multiple times with different argument types"
                 )
             )
@@ -400,25 +367,13 @@ function add_callsites!(d::AbstractDict, visited_mis::AbstractSet, diagnostics::
     end
 end
 
-@static if VERSION >= v"1.10.0-DEV.552"
-    using Core.Compiler: VarState
-    sptypes(sparams) = VarState[VarState.(sparams, false)...]
-else
-    sptypes(sparams) = Any[sparams...]
-end
-
 function show_variables(io, src, slotnames)
     println(io, "Variables")
     slottypes = src.slottypes
     for i = 1:length(slotnames)
         print(io, "  ", slotnames[i])
         if isa(slottypes, Vector{Any})
-            @static if VERSION ≥ v"1.9-"
-                # https://github.com/JuliaLang/julia/pull/46574
-                InteractiveUtils.warntype_type_printer(io; type=slottypes[i], used=true)
-            else
-                InteractiveUtils.warntype_type_printer(io, slottypes[i], true)
-            end
+            InteractiveUtils.warntype_type_printer(io; type=slottypes[i], used=true)
         end
         println(io)
     end
@@ -466,7 +421,7 @@ const BOOKMARKS = Bookmark[]
 function Base.show(
     io::IO, ::MIME"text/plain", b::Bookmark;
     optimize::Bool=false, debuginfo::AnyDebugInfo=:none, iswarn::Bool=false, hide_type_stable::Bool=false)
-    world = get_world_counter(b.interp)
+    world = get_inference_world(b.interp)
     CI, rt = InteractiveUtils.code_typed(b; optimize)
     (; interp, mi) = b
     (; effects) = lookup(interp, mi, optimize)
@@ -476,7 +431,7 @@ function Base.show(
         return
     end
     println(io, "Cthulhu.Bookmark (world: ", world, ")")
-    cthulhu_typed(io, debuginfo, CI, rt, effects, b.mi; iswarn, optimize, hide_type_stable, b.interp)
+    cthulhu_typed(io, debuginfo, CI, rt, nothing, effects, b.mi; iswarn, optimize, hide_type_stable, b.interp)
 end
 
 function InteractiveUtils.code_typed(b::Bookmark; optimize::Bool=true)
@@ -484,12 +439,7 @@ function InteractiveUtils.code_typed(b::Bookmark; optimize::Bool=true)
     (; src, rt, codeinf) = lookup(interp, mi, optimize)
     src = preprocess_ci!(src, mi, optimize, CONFIG)
     if src isa IRCode
-        @static if VERSION ≥ v"1.10.0-DEV.870"
-            CC.replace_code_newstyle!(codeinf, src)
-        else
-            nargs = Int((mi.def::Method).nargs) - 1
-            CC.replace_code_newstyle!(codeinf, src, nargs)
-        end
+        CC.replace_code_newstyle!(codeinf, src)
     end
     return codeinf => rt
 end
@@ -524,7 +474,7 @@ InteractiveUtils.code_llvm(
     b.mi,
     optimize,
     debuginfo === :source,
-    b.interp,
+    get_inference_world(b.interp),
     config,
     dump_module,
     raw,
@@ -545,7 +495,7 @@ InteractiveUtils.code_native(
     b.mi,
     optimize,
     debuginfo === :source,
-    b.interp,
+    get_inference_world(b.interp),
     config,
     dump_module,
     raw,
diff --git a/src/interface.jl b/src/interface.jl
index c7e5e04..9c9428b 100644
--- a/src/interface.jl
+++ b/src/interface.jl
@@ -68,20 +68,23 @@ missing `$AbstractCursor` API:
 """)
 navigate(curs::CthulhuCursor, callsite::Callsite) = CthulhuCursor(get_mi(callsite))
 
-get_remarks(::AbstractInterpreter, ::Union{MethodInstance,InferenceResult}) = nothing
-get_remarks(interp::CthulhuInterpreter, key::Union{MethodInstance,InferenceResult}) = get(interp.remarks, key, nothing)
-get_remarks(::AbstractInterpreter, ::SemiConcreteCallInfo) = PC2Remarks()
+get_remarks(::AbstractInterpreter, ::InferenceKey) = nothing
+get_remarks(interp::CthulhuInterpreter, key::InferenceKey) = get(interp.remarks, key, nothing)
 
-get_effects(::AbstractInterpreter, ::Union{MethodInstance,InferenceResult}) = nothing
-get_effects(interp::CthulhuInterpreter, key::Union{MethodInstance,InferenceResult}) = get(interp.effects, key, nothing)
-get_effects(::AbstractInterpreter, ::SemiConcreteCallInfo) = PC2Effects()
+get_effects(::AbstractInterpreter, ::InferenceKey) = nothing
+get_effects(interp::CthulhuInterpreter, key::InferenceKey) = get(interp.effects, key, nothing)
+
+get_excts(::AbstractInterpreter, ::InferenceKey) = nothing
+get_excts(interp::CthulhuInterpreter, key::InferenceKey) = get(interp.exception_types, key, nothing)
 
 # This method is optional, but should be implemented if there is
 # a sensible default cursor for a MethodInstance
 AbstractCursor(interp::AbstractInterpreter, mi::MethodInstance) = CthulhuCursor(mi)
 
-get_effects(interp::CthulhuInterpreter, mi::MethodInstance, opt::Bool) =
-    get_effects(opt ? interp.opt : interp.unopt, mi)
+function get_effects(interp::CthulhuInterpreter, mi::MethodInstance, opt::Bool)
+    effects = opt ? interp.opt : interp.unopt
+    return haskey(effects, mi) ? get_effects(effects[mi]) : Effects()
+end
 
 mutable struct CustomToggle
     onoff::Bool
diff --git a/src/interpreter.jl b/src/interpreter.jl
index a2968ac..d25f928 100644
--- a/src/interpreter.jl
+++ b/src/interpreter.jl
@@ -1,21 +1,16 @@
-using .CC: AbstractInterpreter, NativeInterpreter, InferenceState, OptimizationState,
-    CodeInfo, CodeInstance, InferenceResult, WorldRange, WorldView, IRCode, SSAValue
-
-@static if VERSION ≥ v"1.9-"
-    const CCCallInfo = CC.CallInfo
-    const NoCallInfo = CC.NoCallInfo
-else
-    const CCCallInfo = Any
-    const NoCallInfo = Nothing
-end
+using .CC: AbstractInterpreter, CallInfo as CCCallInfo, CodeInfo, CodeInstance,
+    InferenceParams, InferenceResult, InferenceState, IRCode, NativeInterpreter,
+    NoCallInfo, OptimizationParams, OptimizationState, SSAValue, WorldRange, WorldView
 
 struct InferredSource
     src::CodeInfo
     stmt_info::Vector{CCCallInfo}
     effects::Effects
     rt::Any
-    InferredSource(src::CodeInfo, stmt_info::Vector{CCCallInfo}, effects, @nospecialize(rt)) =
-        new(src, stmt_info, effects, rt)
+    exct::Any
+    InferredSource(src::CodeInfo, stmt_info::Vector{CCCallInfo}, effects, @nospecialize(rt),
+                   @nospecialize(exct)) =
+        new(src, stmt_info, effects, rt, exct)
 end
 
 struct OptimizedSource
@@ -25,187 +20,205 @@ struct OptimizedSource
     effects::Effects
 end
 
+const InferenceKey = Union{MethodInstance,InferenceResult}
+const InferenceDict{T} = IdDict{InferenceKey, T}
+const OptimizationDict = IdDict{MethodInstance, CodeInstance}
 const PC2Remarks = Vector{Pair{Int, String}}
 const PC2Effects = Dict{Int, Effects}
+const PC2Excts = Dict{Int, Any}
 
 struct CthulhuInterpreter <: AbstractInterpreter
     native::AbstractInterpreter
 
-    unopt::Dict{Union{MethodInstance,InferenceResult}, InferredSource}
-    opt::Dict{MethodInstance, CodeInstance}
+    unopt::InferenceDict{InferredSource}
+    opt::OptimizationDict
 
-    remarks::Dict{Union{MethodInstance,InferenceResult}, PC2Remarks}
-    effects::Dict{Union{MethodInstance,InferenceResult}, PC2Effects}
+    remarks::InferenceDict{PC2Remarks}
+    effects::InferenceDict{PC2Effects}
+    exception_types::InferenceDict{PC2Excts}
 end
 
 function CthulhuInterpreter(interp::AbstractInterpreter=NativeInterpreter())
     return CthulhuInterpreter(
         interp,
-        Dict{Union{MethodInstance,InferenceResult}, InferredSource}(),
-        Dict{MethodInstance, CodeInstance}(),
-        Dict{Union{MethodInstance,InferenceResult}, PC2Remarks}(),
-        Dict{Union{MethodInstance,InferenceResult}, PC2Effects}())
+        InferenceDict{InferredSource}(),
+        OptimizationDict(),
+        InferenceDict{PC2Remarks}(),
+        InferenceDict{PC2Effects}(),
+        InferenceDict{PC2Excts}())
 end
 
-import .CC: InferenceParams, OptimizationParams, get_world_counter,
-    get_inference_cache, code_cache, lock_mi_inference, unlock_mi_inference, method_table,
-    inlining_policy
-using Base: @invoke
-
 CC.InferenceParams(interp::CthulhuInterpreter) = InferenceParams(interp.native)
+@static if VERSION ≥ v"1.11.0-DEV.851"
+CC.OptimizationParams(interp::CthulhuInterpreter) =
+    OptimizationParams(OptimizationParams(interp.native); preserve_local_sources=true)
+else
 CC.OptimizationParams(interp::CthulhuInterpreter) = OptimizationParams(interp.native)
-CC.get_world_counter(interp::CthulhuInterpreter) = get_world_counter(interp.native)
-CC.get_inference_cache(interp::CthulhuInterpreter) = get_inference_cache(interp.native)
-
-# No need to do any locking since we're not putting our results into the runtime cache
-CC.lock_mi_inference(interp::CthulhuInterpreter, mi::MethodInstance) = nothing
-CC.unlock_mi_inference(interp::CthulhuInterpreter, mi::MethodInstance) = nothing
-CC.method_table(interp::CthulhuInterpreter) = method_table(interp.native)
-struct CthulhuCache
-    cache::Dict{MethodInstance, CodeInstance}
 end
-
-CC.code_cache(interp::CthulhuInterpreter) = WorldView(CthulhuCache(interp.opt), WorldRange(get_world_counter(interp)))
-CC.get(wvc::WorldView{CthulhuCache}, mi::MethodInstance, default) = get(wvc.cache.cache, mi, default)
-CC.haskey(wvc::WorldView{CthulhuCache}, mi::MethodInstance) = haskey(wvc.cache.cache, mi)
-CC.setindex!(wvc::WorldView{CthulhuCache}, ci::CodeInstance, mi::MethodInstance) = setindex!(wvc.cache.cache, ci, mi)
+#=CC.=#get_inference_world(interp::CthulhuInterpreter) = get_inference_world(interp.native)
+CC.get_inference_cache(interp::CthulhuInterpreter) = CC.get_inference_cache(interp.native)
 
 CC.may_optimize(interp::CthulhuInterpreter) = true
 CC.may_compress(interp::CthulhuInterpreter) = false
 CC.may_discard_trees(interp::CthulhuInterpreter) = false
 CC.verbose_stmt_info(interp::CthulhuInterpreter) = true
 
+CC.method_table(interp::CthulhuInterpreter) = CC.method_table(interp.native)
+
+@static if VERSION ≥ v"1.11.0-DEV.1552"
+# Since the cache for `CthulhuInterpreter` is volatile and does not involve with the
+# internal code cache, technically, there's no requirement to supply `cache_owner` as an
+# identifier for the internal code cache. However, the definition of `cache_owner` is
+# necessary for utilizing the default `CodeInstance` constructor, define the overload here.
+struct CthulhuCacheToken
+    token
+end
+CC.cache_owner(interp::CthulhuInterpreter) = CthulhuCacheToken(CC.cache_owner(interp.native))
+end
+
+struct CthulhuCache
+    cache::OptimizationDict
+end
+CC.code_cache(interp::CthulhuInterpreter) = WorldView(CthulhuCache(interp.opt), WorldRange(get_inference_world(interp)))
+CC.get(wvc::WorldView{CthulhuCache}, mi::MethodInstance, default) = get(wvc.cache.cache, mi, default)
+CC.haskey(wvc::WorldView{CthulhuCache}, mi::MethodInstance) = haskey(wvc.cache.cache, mi)
+CC.setindex!(wvc::WorldView{CthulhuCache}, ci::CodeInstance, mi::MethodInstance) = setindex!(wvc.cache.cache, ci, mi)
+
 function CC.add_remark!(interp::CthulhuInterpreter, sv::InferenceState, msg)
-    key = CC.any(sv.result.overridden_by_const) ? sv.result : sv.linfo
+    key = (@static VERSION ≥ v"1.12.0-DEV.317" ? CC.is_constproped(sv) : CC.any(sv.result.overridden_by_const)) ? sv.result : sv.linfo
     push!(get!(PC2Remarks, interp.remarks, key), sv.currpc=>msg)
 end
 
-@static if VERSION ≥ v"1.9-"
 function CC.merge_effects!(interp::CthulhuInterpreter, sv::InferenceState, effects::Effects)
-    key = CC.any(sv.result.overridden_by_const) ? sv.result : sv.linfo
+    key = (@static VERSION ≥ v"1.12.0-DEV.317" ? CC.is_constproped(sv) : CC.any(sv.result.overridden_by_const)) ? sv.result : sv.linfo
     pc2effects = get!(interp.effects, key, PC2Effects())
     pc2effects[sv.currpc] = CC.merge_effects(get!(pc2effects, sv.currpc, EFFECTS_TOTAL), effects)
     @invoke CC.merge_effects!(interp::AbstractInterpreter, sv::InferenceState, effects::Effects)
 end
-end
-
-@static if VERSION ≤ v"1.10.0-DEV.221"
-function CC.type_annotate!(interp::CthulhuInterpreter, sv::InferenceState, run_optimizer::Bool)
-    changemap = @invoke CC.type_annotate!(interp::AbstractInterpreter, sv::InferenceState, run_optimizer::Bool)
-    changemap === nothing && return nothing
-    key = CC.any(sv.result.overridden_by_const) ? sv.result : sv.linfo
-    pc2remarks = get(interp.remarks, key, nothing)
-    if pc2remarks !== nothing
-        sort!(pc2remarks)
-        unique!(pc2remarks)
-        for (idx, v) in enumerate(changemap)
-            if v == typemin(Int)
-                for i = searchsorted(pc2remarks, idx=>"", by=((idx,msg),)->idx)
-                    @assert false "remarks found in unreached region"
-                end
-            end
-        end
-        for (idx, v) in enumerate(changemap)
-            if v < 0
-                for i = searchsorted(pc2remarks, idx=>"", by=((idx,msg),)->idx)
-                    pc2remarks[i] = pc2remarks[i].first+v => pc2remarks[i].second
-                end
-            end
-        end
-    end
-    pc2effects = get(interp.effects, key, nothing)
-    if pc2effects !== nothing
-        for (idx, v) in enumerate(changemap)
-            if v == typemin(Int)
-                delete!(pc2effects, idx)
-            end
-        end
-        for (idx, v) in enumerate(changemap)
-            if v < 0
-                haskey(pc2effects, idx) || continue
-                pc2effects[idx+v] = pc2effects[idx]
-                delete!(pc2effects, idx)
-            end
-        end
-    end
-    return changemap
-end
-end
 
 function InferredSource(state::InferenceState)
     unoptsrc = copy(state.src)
-    @static if VERSION < v"1.10.0-DEV.1033"
-        # xref: https://github.com/JuliaLang/julia/pull/49378
-        unoptsrc.slottypes = let slottypes = unoptsrc.slottypes
-            slottypes === nothing ? nothing : copy(slottypes)
-        end
-    end
+    exct = @static VERSION ≥ v"1.11.0-DEV.207" ? state.result.exc_result : nothing
     return InferredSource(
         unoptsrc,
         copy(state.stmt_info),
-        isdefined(CC, :Effects) ? state.ipo_effects : nothing,
-        state.result.result)
+        state.ipo_effects,
+        state.result.result,
+        exct)
 end
 
-function CC.finish(state::InferenceState, interp::CthulhuInterpreter)
-    res = @invoke CC.finish(state::InferenceState, interp::AbstractInterpreter)
-    key = CC.any(state.result.overridden_by_const) ? state.result : state.linfo
+function cthulhu_finish(@specialize(finishfunc), state::InferenceState, interp::CthulhuInterpreter)
+    res = @invoke finishfunc(state::InferenceState, interp::AbstractInterpreter)
+    key = (@static VERSION ≥ v"1.12.0-DEV.317" ? CC.is_constproped(state) : CC.any(state.result.overridden_by_const)) ? state.result : state.linfo
     interp.unopt[key] = InferredSource(state)
     return res
 end
 
-function create_cthulhu_source(@nospecialize(x), effects::Effects)
-    isa(x, OptimizationState) || return x
-    ir = x.ir::IRCode
-    return OptimizedSource(ir, x.src, x.src.inlineable, effects)
+function create_cthulhu_source(@nospecialize(opt), effects::Effects)
+    isa(opt, OptimizationState) || return opt
+    @static if VERSION ≥ v"1.11-"
+        # get the (theoretically) same effect as the jl_compress_ir -> jl_uncompress_ir -> inflate_ir round-trip
+        ir = CC.compact!(CC.cfg_simplify!(CC.copy(opt.ir::IRCode)))
+    else
+        # TODO do the round-trip here?
+        ir = CC.copy(opt.ir::IRCode)
+    end
+    return OptimizedSource(ir, opt.src, opt.src.inlineable, effects)
+end
+
+@static if VERSION ≥ v"1.12.0-DEV.734"
+CC.finishinfer!(state::InferenceState, interp::CthulhuInterpreter) = cthulhu_finish(CC.finishinfer!, state, interp)
+function CC.finish!(interp::CthulhuInterpreter, caller::InferenceState;
+                    can_discard_trees::Bool=false)
+    result = caller.result
+    result.src = create_cthulhu_source(result.src, result.ipo_effects)
+    return @invoke CC.finish!(interp::AbstractInterpreter, caller::InferenceState;
+                              can_discard_trees)
+end
+
+elseif VERSION ≥ v"1.11.0-DEV.737"
+CC.finish(state::InferenceState, interp::CthulhuInterpreter) = cthulhu_finish(CC.finish, state, interp)
+function CC.finish!(interp::CthulhuInterpreter, caller::InferenceState)
+    result = caller.result
+    opt = result.src
+    result.src = create_cthulhu_source(opt, result.ipo_effects)
+    if opt isa CC.OptimizationState
+        CC.ir_to_codeinf!(opt)
+    end
+    return nothing
+end
+function CC.transform_result_for_cache(::CthulhuInterpreter, ::MethodInstance, ::WorldRange,
+                                       result::InferenceResult)
+    return result.src
 end
 
-@static if VERSION ≥ v"1.9-"
-function CC.transform_result_for_cache(interp::CthulhuInterpreter,
-    linfo::MethodInstance, valid_worlds::WorldRange, result::InferenceResult)
+else # VERSION < v"1.11.0-DEV.737"
+CC.finish(state::InferenceState, interp::CthulhuInterpreter) = cthulhu_finish(CC.finish, state, interp)
+function CC.transform_result_for_cache(::CthulhuInterpreter, ::MethodInstance, ::WorldRange,
+                                       result::InferenceResult)
     return create_cthulhu_source(result.src, result.ipo_effects)
 end
-else
-function CC.transform_result_for_cache(interp::CthulhuInterpreter,
-    linfo::MethodInstance, valid_worlds::WorldRange, @nospecialize(inferred_result),
-    ipo_effects::CC.Effects)
-    return create_cthulhu_source(inferred_result, ipo_effects)
-end
+function CC.finish!(interp::CthulhuInterpreter, caller::InferenceResult)
+    caller.src = create_cthulhu_source(caller.src, caller.ipo_effects)
 end
 
-@static if VERSION ≥ v"1.9-"
+end # @static if
+
+@static if VERSION ≥ v"1.12.0-DEV.45"
+function CC.src_inlining_policy(interp::CthulhuInterpreter,
+    @nospecialize(src), @nospecialize(info::CCCallInfo), stmt_flag::UInt32)
+    if isa(src, OptimizedSource)
+        if CC.is_stmt_inline(stmt_flag) || src.isinlineable
+            return true
+        end
+        return false
+    else
+        @assert src isa CC.IRCode || src === nothing "invalid Cthulhu code cache"
+        # the default inlining policy may try additional effor to find the source in a local cache
+        return @invoke CC.src_inlining_policy(interp::AbstractInterpreter,
+            src::Any, info::CCCallInfo, stmt_flag::UInt32)
+    end
+end
+CC.retrieve_ir_for_inlining(cached_result::CodeInstance, src::OptimizedSource) =
+    CC.retrieve_ir_for_inlining(cached_result.def, src.ir::IRCode, true)
+CC.retrieve_ir_for_inlining(mi::MethodInstance, src::OptimizedSource, preserve_local_sources::Bool) =
+    CC.retrieve_ir_for_inlining(mi, src.ir, preserve_local_sources)
+elseif VERSION ≥ v"1.11.0-DEV.879"
 function CC.inlining_policy(interp::CthulhuInterpreter,
-    @nospecialize(src), @nospecialize(info::CCCallInfo), stmt_flag::UInt8, mi::MethodInstance, argtypes::Vector{Any})
+    @nospecialize(src), @nospecialize(info::CCCallInfo), stmt_flag::UInt32)
     if isa(src, OptimizedSource)
         if CC.is_stmt_inline(stmt_flag) || src.isinlineable
             return src.ir
         end
     else
-        @assert src isa CC.SemiConcreteResult || src === nothing "invalid Cthulhu code cache"
+        @assert src isa CC.IRCode || src === nothing "invalid Cthulhu code cache"
         # the default inlining policy may try additional effor to find the source in a local cache
         return @invoke CC.inlining_policy(interp::AbstractInterpreter,
-            src::Any, info::CCCallInfo, stmt_flag::UInt8, mi::MethodInstance, argtypes::Vector{Any})
+            src::Any, info::CCCallInfo, stmt_flag::UInt32)
     end
     return nothing
 end
 else
 function CC.inlining_policy(interp::CthulhuInterpreter,
-    @nospecialize(src), stmt_flag::UInt8, mi::MethodInstance, argtypes::Vector{Any})
-    @assert isa(src, OptimizedSource) || isnothing(src)
+    @nospecialize(src), @nospecialize(info::CCCallInfo),
+    stmt_flag::(@static VERSION ≥ v"1.11.0-DEV.377" ? UInt32 : UInt8),
+    mi::MethodInstance, argtypes::Vector{Any})
     if isa(src, OptimizedSource)
         if CC.is_stmt_inline(stmt_flag) || src.isinlineable
             return src.ir
         end
     else
+        @assert src isa CC.SemiConcreteResult || src === nothing "invalid Cthulhu code cache"
         # the default inlining policy may try additional effor to find the source in a local cache
         return @invoke CC.inlining_policy(interp::AbstractInterpreter,
-            nothing, stmt_flag::UInt8, mi::MethodInstance, argtypes::Vector{Any})
+            src::Any, info::CCCallInfo,
+            stmt_flag::(@static VERSION ≥ v"1.11.0-DEV.377" ? UInt32 : UInt8),
+            mi::MethodInstance, argtypes::Vector{Any})
     end
     return nothing
 end
 end
 
-@static if isdefined(CC, :AbsIntState)
 function CC.IRInterpretationState(interp::CthulhuInterpreter,
     code::CodeInstance, mi::MethodInstance, argtypes::Vector{Any}, world::UInt)
     inferred = @atomic :monotonic code.inferred
@@ -214,18 +227,22 @@ function CC.IRInterpretationState(interp::CthulhuInterpreter,
     ir = CC.copy(inferred.ir)
     src = inferred.src
     method_info = CC.MethodInfo(src)
+    if isdefined(Base, :__has_internal_change) && Base.__has_internal_change(v"1.12-alpha", :codeinfonargs)
+        argtypes = CC.va_process_argtypes(CC.optimizer_lattice(interp), argtypes, src.nargs, src.isva)
+    elseif VERSION >= v"1.12.0-DEV.341"
+        argtypes = CC.va_process_argtypes(CC.optimizer_lattice(interp), argtypes, mi)
+    end
     return CC.IRInterpretationState(interp, method_info, ir, mi, argtypes, world,
-                                    src.min_world, src.max_world)
-end
-elseif VERSION ≥ v"1.9-"
-function CC.codeinst_to_ir(interp::CthulhuInterpreter, code::CodeInstance)
-    inferred = @atomic :monotonic code.inferred
-    inferred === nothing && return nothing
-    inferred = inferred::OptimizedSource
-    return CC.copy(inferred.ir)
-end
+                                    code.min_world, code.max_world)
 end
 
-function CC.finish!(interp::CthulhuInterpreter, caller::InferenceResult)
-    caller.src = create_cthulhu_source(caller.src, caller.ipo_effects)
+@static if VERSION ≥ v"1.11.0-DEV.1127"
+function CC.update_exc_bestguess!(interp::CthulhuInterpreter, @nospecialize(exct),
+                                  frame::InferenceState)
+    key = (@static VERSION ≥ v"1.12.0-DEV.317" ? CC.is_constproped(frame) : CC.any(frame.result.overridden_by_const)) ? frame.result : frame.linfo
+    pc2excts = get!(PC2Excts, interp.exception_types, key)
+    pc2excts[frame.currpc] = CC.tmerge(CC.typeinf_lattice(interp), exct, get(pc2excts, frame.currpc, Union{}))
+    return @invoke CC.update_exc_bestguess!(interp::AbstractInterpreter, exct::Any,
+                                            frame::InferenceState)
+end
 end
diff --git a/src/preferences.jl b/src/preferences.jl
index ea750f4..d8b8241 100644
--- a/src/preferences.jl
+++ b/src/preferences.jl
@@ -1,4 +1,3 @@
-
 """
 ```julia
 save_config!(config::CthulhuConfig=CONFIG)
@@ -38,6 +37,7 @@ function save_config!(config::CthulhuConfig=CONFIG)
         "annotate_source" => config.annotate_source,
         "inlay_types_vscode" => config.inlay_types_vscode,
         "diagnostics_vscode" => config.diagnostics_vscode,
+        "jump_always" => config.jump_always,
     )
 end
 
@@ -57,4 +57,5 @@ function read_config!(config::CthulhuConfig)
     config.annotate_source = @load_preference("annotate_source", config.annotate_source)
     config.inlay_types_vscode = @load_preference("inlay_types_vscode", config.inlay_types_vscode)
     config.diagnostics_vscode = @load_preference("diagnostics_vscode", config.diagnostics_vscode)
+    config.jump_always = @load_preference("jump_always", config.jump_always)
 end
diff --git a/src/reflection.jl b/src/reflection.jl
index 6f55991..ca54498 100644
--- a/src/reflection.jl
+++ b/src/reflection.jl
@@ -20,7 +20,8 @@ end
 
 function find_callsites(interp::AbstractInterpreter, CI::Union{Core.CodeInfo, IRCode},
                         stmt_infos::Union{Vector{CCCallInfo}, Nothing}, mi::Core.MethodInstance,
-                        slottypes::Vector{Any}, optimize::Bool=true, annotate_source::Bool=false)
+                        slottypes::Vector{Any}, optimize::Bool=true, annotate_source::Bool=false,
+                        pc2excts::Union{Nothing,PC2Excts}=nothing)
     sptypes = sptypes_from_meth_instance(mi)
     callsites, sourcenodes = Callsite[], Union{TypedSyntax.MaybeTypedSyntaxNode,Callsite}[]
     if isa(CI, IRCode)
@@ -53,7 +54,8 @@ function find_callsites(interp::AbstractInterpreter, CI::Union{Core.CodeInfo, IR
                                       t = argextype(arg, CI, sptypes, slottypes)
                                       return ignorelimited(t)
                                   end, args)
-                callinfos = process_info(interp, info, argtypes, rt, optimize)
+                exct = isnothing(pc2excts) ? nothing : get(pc2excts, id, nothing)
+                callinfos = process_info(interp, info, argtypes, rt, optimize, exct)
                 isempty(callinfos) && continue
                 callsite = let
                     if length(callinfos) == 1
@@ -85,7 +87,7 @@ function find_callsites(interp::AbstractInterpreter, CI::Union{Core.CodeInfo, IR
                         func = args[6]
                         ftype = widenconst(argextype(func, CI, sptypes, slottypes))
                         sig = Tuple{ftype}
-                        callsite = Callsite(id, TaskCallInfo(callinfo(sig, nothing; world=get_world_counter(interp))), head)
+                        callsite = Callsite(id, TaskCallInfo(callinfo(sig, nothing; world=get_inference_world(interp))), head)
                     end
                 end
             end
@@ -97,7 +99,7 @@ function find_callsites(interp::AbstractInterpreter, CI::Union{Core.CodeInfo, IR
                 mi = get_mi(info)
                 meth = mi.def
                 if isa(meth, Method) && nameof(meth.module) === :CUDAnative && meth.name === :cufunction
-                    callsite = transform(Val(:CuFunction), callsite, c, CI, mi, slottypes; world=get_world_counter(interp))
+                    callsite = transform(Val(:CuFunction), callsite, c, CI, mi, slottypes; world=get_inference_world(interp))
                 end
             end
 
@@ -116,68 +118,70 @@ function find_callsites(interp::AbstractInterpreter, CI::Union{Core.CodeInfo, IR
 end
 
 function process_const_info(interp::AbstractInterpreter, @nospecialize(thisinfo),
-    argtypes::ArgTypes, @nospecialize(rt), @nospecialize(result), optimize::Bool)
+    argtypes::ArgTypes, @nospecialize(rt), @nospecialize(result), optimize::Bool,
+    @nospecialize(exct))
     is_cached(@nospecialize(key)) = can_descend(interp, key, optimize)
 
     if isnothing(result)
         return thisinfo
-    elseif (@static VERSION ≥ v"1.9-" && true) && isa(result, CC.ConcreteResult)
+    elseif (@static VERSION ≥ v"1.11.0-DEV.851" && true) && result isa CC.VolatileInferenceResult
+        # NOTE we would not hit this case since `finish!(::CthulhuInterpreter, frame::InferenceState)`
+        #      will always transform `frame.result.src` to `OptimizedSource` when frame is inferred
+        return thisinfo
+    elseif isa(result, CC.ConcreteResult)
         linfo = result.mi
         effects = get_effects(result)
-        mici = MICallInfo(linfo, rt, effects)
+        mici = MICallInfo(linfo, rt, effects, exct)
         return ConcreteCallInfo(mici, argtypes)
-    elseif (@static VERSION ≥ v"1.9-" && true) && isa(result, CC.ConstPropResult)
+    elseif isa(result, CC.ConstPropResult)
         result = result.result
         linfo = result.linfo
         effects = get_effects(result)
-        mici = MICallInfo(linfo, rt, effects)
+        mici = MICallInfo(linfo, rt, effects, exct)
         return ConstPropCallInfo(is_cached(optimize ? linfo : result) ? mici : UncachedCallInfo(mici), result)
-    elseif (@static VERSION ≥ v"1.9-" && true) && isa(result, CC.SemiConcreteResult)
+    elseif isa(result, CC.SemiConcreteResult)
         linfo = result.mi
         effects = get_effects(result)
-        mici = MICallInfo(linfo, rt, effects)
+        mici = MICallInfo(linfo, rt, effects, exct)
         return SemiConcreteCallInfo(mici, result.ir)
-    elseif (@static !(VERSION ≥ v"1.9-") && true) && isa(result, CC.ConstResult)
-        linfo = result.mi
-        effects = get_effects(result)
-        mici = MICallInfo(linfo, rt, effects)
-        return ConcreteCallInfo(mici, argtypes)
     else
         @assert isa(result, CC.InferenceResult)
         linfo = result.linfo
         effects = get_effects(result)
-        mici = MICallInfo(linfo, rt, effects)
+        mici = MICallInfo(linfo, rt, effects, exct)
         return ConstPropCallInfo(is_cached(optimize ? linfo : result) ? mici : UncachedCallInfo(mici), result)
     end
 end
 
-function process_info(interp::AbstractInterpreter, @nospecialize(info::CCCallInfo), argtypes::ArgTypes, @nospecialize(rt), optimize::Bool)
+function process_info(interp::AbstractInterpreter, @nospecialize(info::CCCallInfo),
+                      argtypes::ArgTypes, @nospecialize(rt), optimize::Bool,
+                      @nospecialize(exct))
     is_cached(@nospecialize(key)) = can_descend(interp, key, optimize)
-    process_recursive(@nospecialize(newinfo)) = process_info(interp, newinfo, argtypes, rt, optimize)
+    process_recursive(@nospecialize(newinfo)) = process_info(interp, newinfo, argtypes, rt, optimize, exct)
 
     if isa(info, MethodResultPure)
         if isa(info.info, CC.ReturnTypeCallInfo)
             # xref: https://github.com/JuliaLang/julia/pull/45299#discussion_r871939049
             info = info.info # cascade to the special handling below
         else
-            return Any[PureCallInfo(argtypes, rt)]
+            return CallInfo[PureCallInfo(argtypes, rt)]
         end
     end
     if isa(info, MethodMatchInfo)
         if info.results === missing
-            return []
+            return CallInfo[]
         end
         matches = info.results.matches
-        return mapany(matches) do match::Core.MethodMatch
+        return CallInfo[let
             mi = specialize_method(match)
             effects = get_effects(interp, mi, false)
-            mici = MICallInfo(mi, rt, effects)
-            return is_cached(mi) ? mici : UncachedCallInfo(mici)
-        end
+            mici = MICallInfo(mi, rt, effects, exct)
+            is_cached(mi) ? mici : UncachedCallInfo(mici)
+        end for match::Core.MethodMatch in matches]
     elseif isa(info, UnionSplitInfo)
-        return mapreduce(process_recursive, vcat, info.matches; init=[])::Vector{Any}
+        return mapreduce(process_recursive, vcat, info.matches; init=CallInfo[])::Vector{CallInfo}
     elseif isa(info, UnionSplitApplyCallInfo)
-        return mapreduce(process_recursive, vcat, info.infos; init=[])::Vector{Any}
+        return mapreduce(process_recursive, vcat, info.infos; init=CallInfo[])::Vector{CallInfo}
     elseif isa(info, ApplyCallInfo)
         # XXX: This could probably use its own info. For now,
         # we ignore any implicit iterate calls.
@@ -185,32 +189,32 @@ function process_info(interp::AbstractInterpreter, @nospecialize(info::CCCallInf
     elseif isa(info, ConstCallInfo)
         infos = process_recursive(info.call)
         @assert length(infos) == length(info.results)
-        return mapany(enumerate(info.results)) do (i, result)
-            process_const_info(interp, infos[i], argtypes, rt, result, optimize)
-        end
+        return CallInfo[let
+            process_const_info(interp, infos[i], argtypes, rt, result, optimize, exct)
+        end for (i, result) in enumerate(info.results)]
     elseif isa(info, CC.InvokeCallInfo)
         mi = specialize_method(info.match; preexisting=true)
         effects = get_effects(interp, mi, false)
         thisinfo = MICallInfo(mi, rt, effects)
-        innerinfo = process_const_info(interp, thisinfo, argtypes, rt, info.result, optimize)
+        innerinfo = process_const_info(interp, thisinfo, argtypes, rt, info.result, optimize, exct)
         info = InvokeCallInfo(innerinfo)
-        return Any[info]
+        return CallInfo[info]
     elseif isa(info, CC.OpaqueClosureCallInfo)
         mi = specialize_method(info.match; preexisting=true)
         effects = get_effects(interp, mi, false)
         thisinfo = MICallInfo(mi, rt, effects)
-        innerinfo = process_const_info(interp, thisinfo, argtypes, rt, info.result, optimize)
+        innerinfo = process_const_info(interp, thisinfo, argtypes, rt, info.result, optimize, exct)
         info = OCCallInfo(innerinfo)
-        return Any[info]
+        return CallInfo[info]
     elseif isa(info, CC.OpaqueClosureCreateInfo)
         # TODO: Add ability to descend into OCs at creation site
-        return []
-    elseif (@static VERSION ≥ v"1.9-" && true) && isa(info, CC.FinalizerInfo)
+        return CallInfo[]
+    elseif isa(info, CC.FinalizerInfo)
         # TODO: Add ability to descend into finalizers at creation site
-        return []
+        return CallInfo[]
     elseif isa(info, CC.ReturnTypeCallInfo)
         newargtypes = argtypes[2:end]
-        callinfos = process_info(interp, info.info, newargtypes, unwrapType(widenconst(rt)), optimize)
+        callinfos = process_info(interp, info.info, newargtypes, unwrapType(widenconst(rt)), optimize, exct)
         if length(callinfos) == 1
             vmi = only(callinfos)
         else
@@ -219,13 +223,13 @@ function process_info(interp::AbstractInterpreter, @nospecialize(info::CCCallInf
             sig = Tuple{widenconst(newargtypes[1]), argt.parameters...}
             vmi = FailedCallInfo(sig, Union{})
         end
-        return Any[ReturnTypeCallInfo(vmi)]
+        return CallInfo[ReturnTypeCallInfo(vmi)]
     elseif info == NoCallInfo()
         f = unwrapconst(argtypes[1])
-        isa(f, Core.Builtin) && return []
-        return [RTCallInfo(f, argtypes[2:end], rt)]
+        isa(f, Core.Builtin) && return CallInfo[]
+        return CallInfo[RTCallInfo(f, argtypes[2:end], rt, exct)]
     elseif info === false
-        return []
+        return CallInfo[]
     else
         @eval Main begin
             interp = $interp
@@ -257,11 +261,7 @@ function preprocess_ci!(ci::CodeInfo, mi::MethodInstance, optimize, config::Cthu
         argtypes = CC.matching_cache_argtypes(mi, nothing, false)[1]
         ir = CC.inflate_ir(ci, sptypes_from_meth_instance(mi), argtypes)
         ir = dce!(ir)
-        @static if VERSION ≥ v"1.10.0-DEV.870"
-            ci = CC.replace_code_newstyle!(ci, ir)
-        else
-            ci = CC.replace_code_newstyle!(ci, ir, length(argtypes)-1)
-        end
+        ci = CC.replace_code_newstyle!(ci, ir)
     end
     return ci
 end
@@ -299,7 +299,7 @@ function callinfo(sig, rt, max_methods=-1; world=get_world_counter())
     return MultiCallInfo(sig, rt, callinfos)
 end
 
-function find_caller_of(interp::AbstractInterpreter, callee::MethodInstance, caller::MethodInstance; allow_unspecialized::Bool=false)
+function find_caller_of(interp::AbstractInterpreter, callee::Union{MethodInstance,Type}, caller::MethodInstance; allow_unspecialized::Bool=false)
     interp′ = CthulhuInterpreter(interp)
     do_typeinf!(interp′, caller)
     locs = Tuple{Core.LineInfoNode,Int}[]
@@ -309,7 +309,7 @@ function find_caller_of(interp::AbstractInterpreter, callee::MethodInstance, cal
         callsites, _ = find_callsites(interp′, src, infos, caller, slottypes, optimize)
         callsites = allow_unspecialized ? filter(cs->maybe_callsite(cs, callee), callsites) :
                                           filter(cs->is_callsite(cs, callee), callsites)
-        foreach(cs -> add_sourceline!(locs, src, cs.id), callsites)
+        foreach(cs -> add_sourceline!(locs, src, cs.id, caller), callsites)
     end
     # Consolidate by method, but preserve the order
     prlookup = Dict{Tuple{Symbol,Symbol},Int}()
@@ -335,29 +335,35 @@ function find_caller_of(interp::AbstractInterpreter, callee::MethodInstance, cal
     return ulocs
 end
 
-function add_sourceline!(locs, CI, stmtidx::Int)
-    if isa(CI, IRCode)
-        stack = Base.IRShow.compute_loc_stack(CI.linetable, CI.stmts.line[stmtidx])
+function add_sourceline!(locs::Vector{Tuple{Core.LineInfoNode,Int}}, src::Union{CodeInfo,IRCode}, stmtidx::Int, caller::MethodInstance)
+    @static if VERSION ≥ v"1.12.0-DEV.173"
+    stack = Base.IRShow.buildLineInfoNode(src.debuginfo, caller, stmtidx)
+    for (i, di) in enumerate(stack)
+        loc = Core.LineInfoNode(Main, di.method, di.file, di.line, zero(Int32))
+        push!(locs, (loc, i-1))
+    end
+    else # VERSION < v"1.12.0-DEV.173"
+    if isa(src, IRCode)
+        stack = Base.IRShow.compute_loc_stack(src.linetable, src.stmts.line[stmtidx])
         for (i, idx) in enumerate(stack)
-            line = CI.linetable[idx]
+            line = src.linetable[idx]
             line.line == 0 && continue
-            push!(locs, (CI.linetable[idx], i-1))
+            push!(locs, (src.linetable[idx], i-1))
         end
     else
-        push!(locs, (CI.linetable[CI.codelocs[stmtidx]], 0))
+        push!(locs, (src.linetable[src.codelocs[stmtidx]], 0))
     end
+    end # @static if
     return locs
 end
 
 function get_typed_sourcetext(mi::MethodInstance, src::CodeInfo, @nospecialize(rt); warn::Bool=true)
-    meth = mi.def::Method
-    tsn, mappings = TypedSyntax.tsn_and_mappings(meth, src, rt; warn, strip_macros=true)
-    return truncate_if_defaultargs!(tsn, mappings, meth)
-    return tsn, mappings
+    tsn, mappings = TypedSyntax.tsn_and_mappings(mi, src, rt; warn, strip_macros=true)
+    return truncate_if_defaultargs!(tsn, mappings, mi.def::Method)
 end
 
 function get_typed_sourcetext(mi::MethodInstance, ::IRCode, @nospecialize(rt); kwargs...)
-    src, rt = TypedSyntax.getsrc(mi)
+    src, rt = TypedSyntax.code_typed1_tsn(mi)
     return get_typed_sourcetext(mi, src, rt; kwargs...)
 end
 
@@ -381,11 +387,7 @@ function truncate_if_defaultargs!(tsn, mappings, meth)
     return tsn, mappings
 end
 
-if isdefined(Core, :kwcall)
-    is_kw_dispatch(meth::Method) = meth.name == :kwcall || Base.unwrap_unionall(meth.sig).parameters[1] === typeof(Core.kwcall) || !isempty(Base.kwarg_decl(meth))
-else
-    is_kw_dispatch(meth::Method) = endswith(string(meth.name), "##kw") || !isempty(Base.kwarg_decl(meth))
-end
+is_kw_dispatch(meth::Method) = meth.name == :kwcall || Base.unwrap_unionall(meth.sig).parameters[1] === typeof(Core.kwcall) || !isempty(Base.kwarg_decl(meth))
 
 function tag_runtime(node::TypedSyntaxNode, info)
     node.runtime = isa(info, RTCallInfo)
diff --git a/src/ui.jl b/src/ui.jl
index 3fd52fb..18ff7ee 100644
--- a/src/ui.jl
+++ b/src/ui.jl
@@ -13,7 +13,7 @@ mutable struct CthulhuMenu <: TerminalMenus.ConfiguredMenu{TerminalMenus.Config}
     custom_toggles::Vector{CustomToggle}
 end
 
-function show_as_line(callsite::Callsite, with_effects::Bool, optimize::Bool, iswarn::Bool)
+function show_as_line(callsite::Callsite, with_effects::Bool, exception_type::Bool, optimize::Bool, iswarn::Bool)
     reduced_displaysize = displaysize(stdout)::Tuple{Int,Int} .- (0, 3)
     sprint() do io
         show(IOContext(io,
@@ -21,15 +21,17 @@ function show_as_line(callsite::Callsite, with_effects::Bool, optimize::Bool, is
             :displaysize  => reduced_displaysize,
             :optimize     => optimize,
             :iswarn       => iswarn,
-            :color        => iswarn | with_effects,
-            :with_effects => with_effects),
+            :color        => iswarn | with_effects | exception_type,
+            :with_effects => with_effects,
+            :exception_type => exception_type),
             callsite)
     end
 end
 
-function CthulhuMenu(callsites, with_effects::Bool, optimize::Bool, iswarn::Bool, hide_type_stable::Bool,
+function CthulhuMenu(callsites, with_effects::Bool, exception_type::Bool,
+                     optimize::Bool, iswarn::Bool, hide_type_stable::Bool,
                      custom_toggles::Vector{CustomToggle}; pagesize::Int=10, sub_menu = false, kwargs...)
-    options = build_options(callsites, with_effects, optimize, iswarn, hide_type_stable)
+    options = build_options(callsites, with_effects, exception_type, optimize, iswarn, hide_type_stable)
     length(options) < 1 && error("CthulhuMenu must have at least one option")
 
     # if pagesize is -1, use automatic paging
@@ -47,15 +49,15 @@ function CthulhuMenu(callsites, with_effects::Bool, optimize::Bool, iswarn::Bool
     return CthulhuMenu(options, pagesize, pageoffset, selected, nothing, sub_menu, config, custom_toggles)
 end
 
-build_options(callsites::Vector{Callsite}, with_effects::Bool, optimize::Bool, iswarn::Bool, ::Bool) =
-    vcat(map(callsite->show_as_line(callsite, with_effects, optimize, iswarn), callsites), ["↩"])
-function build_options(callsites, with_effects::Bool, optimize::Bool, iswarn::Bool, hide_type_stable::Bool)
+build_options(callsites::Vector{Callsite}, with_effects::Bool, exception_type::Bool, optimize::Bool, iswarn::Bool, ::Bool) =
+    vcat(map(callsite->show_as_line(callsite, with_effects, exception_type, optimize, iswarn), callsites), ["↩"])
+function build_options(callsites, with_effects::Bool, exception_type::Bool, optimize::Bool, iswarn::Bool, hide_type_stable::Bool)
     reduced_displaysize::Int = (displaysize(stdout)::Tuple{Int,Int})[2] - 3
     nd::Int = -1
 
     shown_callsites = map(callsites) do node
         if isa(node, Callsite)
-            show_as_line(node, with_effects, optimize, iswarn)
+            show_as_line(node, with_effects, exception_type, optimize, iswarn)
         else
             if nd == -1
                 nd = TypedSyntax.ndigits_linenumbers(node)
@@ -92,8 +94,10 @@ function stringify(@nospecialize(f), context::IOContext)
 end
 
 const debugcolors = (:nothing, :light_black, :yellow)
-function usage(@nospecialize(view_cmd), annotate_source, optimize, iswarn, hide_type_stable, debuginfo, remarks, with_effects, inline_cost, type_annotations, highlight, inlay_types_vscode, diagnostics_vscode,
-    custom_toggles::Vector{CustomToggle})
+function usage(@nospecialize(view_cmd), annotate_source, optimize, iswarn, hide_type_stable,
+               debuginfo, remarks, with_effects, exception_type, inline_cost,
+               type_annotations, highlight, inlay_types_vscode, diagnostics_vscode,
+               jump_always, custom_toggles::Vector{CustomToggle})
     colorize(use_color::Bool, c::Char) = stringify() do io
         use_color ? printstyled(io, c; color=:cyan) : print(io, c)
     end
@@ -106,7 +110,8 @@ function usage(@nospecialize(view_cmd), annotate_source, optimize, iswarn, hide_
         colorize(iswarn, 'w'), "]arn, [",
         colorize(hide_type_stable, 'h'), "]ide type-stable statements, [",
         colorize(type_annotations, 't'), "]ype annotations, [",
-        colorize(highlight, 's'), "]yntax highlight for Source/LLVM/Native")
+        colorize(highlight, 's'), "]yntax highlight for Source/LLVM/Native, [",
+        colorize(jump_always, 'j'), "]ump to source always"),
     if TypedSyntax.inlay_hints_available_vscode()
         print(ioctx, ", [",
         colorize(inlay_types_vscode, 'v'), "]scode: inlay types")
@@ -122,7 +127,8 @@ function usage(@nospecialize(view_cmd), annotate_source, optimize, iswarn, hide_
                 printstyled(io, 'd'; color=debugcolors[Int(debuginfo)+1])
             end, "]ebuginfo, [",
             colorize(remarks, 'r'), "]emarks, [",
-            colorize(with_effects, 'e'), "]ffects, [",
+            colorize(with_effects, 'e'), "]ffects, ",
+            "e[", colorize(exception_type, 'x'), "]ception types, [",
             colorize(inline_cost, 'i'), "]nlining costs")
     end
     for i = 1:length(custom_toggles)
@@ -156,6 +162,7 @@ const TOGGLES = Dict(
     UInt32('d') => :debuginfo,
     UInt32('r') => :remarks,
     UInt32('e') => :with_effects,
+    UInt32('x') => :exception_type,
     UInt32('i') => :inline_cost,
     UInt32('t') => :type_annotations,
     UInt32('s') => :highlighter,
@@ -169,7 +176,8 @@ const TOGGLES = Dict(
     UInt32('R') => :revise,
     UInt32('E') => :edit,
     UInt32('v') => :inlay_types_vscode,
-    UInt32('V') => :diagnostics_vscode
+    UInt32('V') => :diagnostics_vscode,
+    UInt32('j') => :jump_always
 )
 
 function TerminalMenus.keypress(m::CthulhuMenu, key::UInt32)


Test patch:
diff --git a/TypedSyntax/test/exhaustive.jl b/TypedSyntax/test/exhaustive.jl
index f81c8c3..dfbc7a6 100644
--- a/TypedSyntax/test/exhaustive.jl
+++ b/TypedSyntax/test/exhaustive.jl
@@ -26,7 +26,7 @@ const goodmis = Core.MethodInstance[]
                 continue
             end
             try
-                tsn, _ = TypedSyntax.tsn_and_mappings(m, src, rt, ret...; warn=false)
+                tsn, _ = TypedSyntax.tsn_and_mappings(mi, src, rt, ret...; warn=false)
                 @test isa(tsn, TypedSyntaxNode)
                 push!(goodmis, mi)
             catch
diff --git a/TypedSyntax/test/runtests.jl b/TypedSyntax/test/runtests.jl
index bc880f3..b1677ea 100644
--- a/TypedSyntax/test/runtests.jl
+++ b/TypedSyntax/test/runtests.jl
@@ -1,7 +1,8 @@
 using JuliaSyntax: JuliaSyntax, SyntaxNode, children, child, sourcetext, kind, @K_str
-using TypedSyntax: TypedSyntax, TypedSyntaxNode, getsrc
+using TypedSyntax: TypedSyntax, TypedSyntaxNode
 using Dates, InteractiveUtils, Test
 
+has_name_typ(node, name::Symbol, @nospecialize(Ts::Tuple)) = kind(node) == K"Identifier" && node.val === name && node.typ in Ts
 has_name_typ(node, name::Symbol, @nospecialize(T)) = kind(node) == K"Identifier" && node.val === name && node.typ === T
 has_name_notyp(node, name::Symbol) = has_name_typ(node, name, nothing)
 
@@ -15,8 +16,7 @@ include("test_module.jl")
     """
     rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN1.jl")
     TSN.eval(Expr(rootnode))
-    src, _ = getsrc(TSN.f, (Float32, Int, Float64))
-    tsn = TypedSyntaxNode(rootnode, src)
+    tsn = TypedSyntaxNode(rootnode, TSN.f, (Float32, Int, Float64))
     sig, body = children(tsn)
     @test children(sig)[2].typ === Float32
     @test children(sig)[3].typ === Int
@@ -33,8 +33,7 @@ include("test_module.jl")
     """
     rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN2.jl")
     TSN.eval(Expr(rootnode))
-    src, _ = getsrc(TSN.g, (Int16, Int16, Int32))
-    tsn = TypedSyntaxNode(rootnode, src)
+    tsn = TypedSyntaxNode(rootnode, TSN.g, (Int16, Int16, Int32))
     sig, body = children(tsn)
     @test length(children(sig)) == 4
     @test children(body)[2].typ === Int32
@@ -46,13 +45,12 @@ include("test_module.jl")
     st = "math(x) = x + sin(x + π / 4)"
     rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN2.jl")
     TSN.eval(Expr(rootnode))
-    src, _ = getsrc(TSN.math, (Int,))
-    tsn = TypedSyntaxNode(rootnode, src)
+    tsn = TypedSyntaxNode(rootnode, TSN.math, (Int,))
     sig, body = children(tsn)
     @test has_name_typ(child(body, 1), :x, Int)
     @test has_name_typ(child(body, 3, 2, 1), :x, Int)
     pi4 = child(body, 3, 2, 3)
-    @test kind(pi4) == K"call" && pi4.typ == typeof(π / 4)
+    @test kind(pi4) == K"call" && pi4.typ === Core.Const(π / 4)
     tsn = TypedSyntaxNode(TSN.has2xa, (Real,))
     @test tsn.typ === Any
     sig, body = children(tsn)
@@ -70,8 +68,7 @@ include("test_module.jl")
     st = "math2(x) = sin(x) + sin(x)"
     rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN2.jl")
     TSN.eval(Expr(rootnode))
-    src, _ = getsrc(TSN.math2, (Int,))
-    tsn = TypedSyntaxNode(rootnode, src)
+    tsn = TypedSyntaxNode(rootnode, TSN.math2, (Int,))
     sig, body = children(tsn)
     @test body.typ === Float64
     @test_broken child(body, 1).typ === Float64
@@ -91,8 +88,7 @@ include("test_module.jl")
         )
         rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN3.jl")
         TSN.eval(Expr(rootnode))
-        src, _ = getsrc(TSN.firstfirst, (Vector{Vector{Real}},))
-        tsn = TypedSyntaxNode(rootnode, src)
+        tsn = TypedSyntaxNode(rootnode, TSN.firstfirst, (Vector{Vector{Real}},))
         sig, body = children(tsn)
         @test child(body, idxsinner...).typ === nothing
         @test child(body, idxsouter...).typ === Vector{Real}
@@ -150,8 +146,7 @@ include("test_module.jl")
         """
     rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN4.jl")
     TSN.eval(Expr(rootnode))
-    src, rt = getsrc(TSN.setlist!, (Vector{Vector{Float32}}, Vector{Vector{UInt8}}, Int, Int))
-    tsn = TypedSyntaxNode(rootnode, src)
+    tsn = TypedSyntaxNode(rootnode, TSN.setlist!, (Vector{Vector{Float32}}, Vector{Vector{UInt8}}, Int, Int))
     sig, body = children(tsn)
     nodelist = child(body, 1, 2, 1, 1)                             # `listget`
     @test sourcetext(nodelist) == "listget" && nodelist.typ === Vector{Vector{UInt8}}
@@ -175,8 +170,7 @@ include("test_module.jl")
     """
     rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN5.jl")
     TSN.eval(Expr(rootnode))
-    src, rt = getsrc(TSN.callfindmin, (Vector{Float64},))
-    tsn = TypedSyntaxNode(rootnode, src)
+    tsn = TypedSyntaxNode(rootnode, TSN.callfindmin, (Vector{Float64},))
     sig, body = children(tsn)
     t = child(body, 1, 1)
     @test kind(t) == K"tuple"
@@ -220,18 +214,18 @@ include("test_module.jl")
     tsn = TypedSyntaxNode(TSN.nestedgenerators, (Int, Int))
     sig, body = children(tsn)
     @test kind(body) == K"generator"
-    @test body.typ <: Base.Iterators.Flatten
+    @test TypedSyntax.unwrapinternal(body.typ) <: Base.Iterators.Flatten
     tsn = TypedSyntaxNode(TSN.nestedgenerators, (Int,))
     sig, body = children(tsn)
     @test kind(body) == K"generator"
-    @test body.typ <: Base.Iterators.Flatten
+    @test TypedSyntax.unwrapinternal(body.typ) <: Base.Iterators.Flatten
     tsn = TypedSyntaxNode(TSN.nestedexplicit, (Int,))
     sig, body = children(tsn)
     @test kind(body) == K"comprehension"
     @test body.typ <: Vector
     node = child(body, 1)
     @test kind(node) == K"generator"
-    @test node.typ <: Base.Generator
+    @test TypedSyntax.unwrapinternal(node.typ) <: Base.Generator
 
     # Broadcasting
     tsn = TypedSyntaxNode(TSN.fbroadcast, (Vector{Int},))
@@ -244,9 +238,9 @@ include("test_module.jl")
     sig, body = children(tsn)
     @test body.typ === Float64
     cnode = child(body, 2)
+    @test cnode.typ === Vector{Float64}
     cnodef = child(cnode, 1, 2, 1)
     @test kind(cnodef) == K"Identifier" && cnodef.val == :materialize
-    @test cnode.typ === Vector{Float64}
     cnode = child(body, 2, 2)
     cnodef = child(cnode, 1, 2, 1)
     @test kind(cnodef) == K"Identifier" && cnodef.val == :broadcasted
@@ -255,12 +249,7 @@ include("test_module.jl")
     sig, body = children(tsn)
     node = child(body, 2)
     src = tsn.typedsource
-    if isa(src.code[1], GlobalRef)
-        @test kind(node) == K"dotcall" && node.typ === Vector{String}
-    else
-        # We aren't quite handling this properly yet
-        @test_broken kind(node) == K"dotcall" && node.typ === Vector{String}
-    end
+    @test kind(node) == K"dotcall" && node.typ === Vector{String}
     tsn = TypedSyntaxNode(TSN.bcast415, (TSN.B415, Float64))
     sig, body = children(tsn)
     @test child(body, 1).typ === Float64
@@ -280,23 +269,23 @@ include("test_module.jl")
     """
     rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN6.jl")
     TSN.eval(Expr(rootnode))
-    src, rt = getsrc(TSN.avoidzero, (Int,))
+    inferred_result = TypedSyntax.get_inferred_result(TSN.avoidzero, (Int,))
+    src, rt, mi = inferred_result.src, inferred_result.rt, inferred_result.mi
     # src looks like this:
     #   %1 = Main.TSN.:(var"#avoidzero#6")(true, #self#, x)::Float64
     #        return %1
     # Consequently there is nothing to match, but at least we shouldn't error
-    tsn = TypedSyntaxNode(rootnode, src)
+    tsn = TypedSyntaxNode(rootnode, src, mi)
     @test isa(tsn, TypedSyntaxNode)
     @test rt === Float64
     # Try the kwbodyfunc
     m = which(TSN.avoidzero, (Int,))
-    src, rt = getsrc(Base.bodyfunction(m), (Bool, typeof(TSN.avoidzero), Int,))
-    tsn = TypedSyntaxNode(rootnode, src)
+    tsn = TypedSyntaxNode(rootnode, Base.bodyfunction(m), (Bool, typeof(TSN.avoidzero), Int,))
     sig, body = children(tsn)
     isz = child(body, 2, 1, 1)
     @test kind(isz) == K"call" && child(isz, 1).val == :iszero
     @test isz.typ === Bool
-    @test child(body, 2, 1, 2).typ == Float64
+    @test child(body, 2, 1, 2).typ === Core.Const(NaN)
 
     # default positional arguments
     tsn = TypedSyntaxNode(TSN.defaultarg, (Float32,))
@@ -314,7 +303,7 @@ include("test_module.jl")
     tsn = TypedSyntaxNode(TSN.hasdefaulttypearg, (Type{Float32},))
     sig, body = children(tsn)
     arg = child(sig, 1, 2, 1)
-    @test kind(arg) == K"::" && arg.typ === Type{Float32}
+    @test kind(arg) == K"::" && arg.typ === Core.Const(Float32)
     tsn = TypedSyntaxNode(TSN.hasdefaulttypearg, ())
     sig, body = children(tsn)
     arg = child(sig, 1, 2, 1)
@@ -339,25 +328,26 @@ include("test_module.jl")
     @test tsn.typ == Union{Int,Float64}
     sig, body = children(tsn)
     @test has_name_typ(child(sig, 2), :list, Vector{Float64})
-    @test has_name_typ(child(body, 1, 1), :s, Int)
+    @test has_name_typ(child(body, 1, 1), :s, Core.Const(0))
     @test has_name_typ(child(body, 2, 1, 1), :x, Float64)
     node = child(body, 2, 2, 1)
     @test kind(node) == K"+="
-    @test has_name_typ(child(node, 1), :s, Float64)   # if this line runs, the LHS now has type `Float64`
+    @test has_name_typ(child(node, 1), :s, Float64) ||   # if this line runs, the LHS now has type `Float64`
+          has_name_typ(child(node, 1), :s, Union{Float64, Int})   # but Julia 1.11 infers this still as the Union
     @test has_name_typ(child(node, 2), :x, Float64)
     @test has_name_typ(child(body, 3, 1), :s, Union{Float64, Int})
     tsn = TypedSyntaxNode(TSN.summer_iterate, (Vector{Float64},))
     @test tsn.typ == Union{Int,Float64}
     sig, body = children(tsn)
-    @test has_name_typ(child(body, 2, 1), :ret, Union{Nothing, Tuple{Float64, Int64}})
+    @test has_name_typ(child(body, 2, 1), :ret, Union{Nothing, Tuple{Float64, Int}})
     @test has_name_typ(child(body, 3, 2, 1, 1, 1), :x, Float64)
 
     # `where`, unnamed arguments, and types-as-arguments
     tsn = TypedSyntaxNode(TSN.zerowhere, (Vector{Int16},))
     sig, body = children(tsn)
     @test child(sig, 1, 2).typ === Vector{Int16}
-    @test body.typ === Int16
-    @test has_name_typ(child(body, 2), :T, Type{Int16})
+    @test body.typ === Core.Const(Int16(0))
+    @test has_name_typ(child(body, 2), :T, (Core.Const(Int16), Type{Int16}))
     # tsn = TypedSyntaxNode(TSN.vaparam, (Matrix{Float32}, (String, Bool)))    # fails on `which`
     m = @which TSN.vaparam(rand(3,3), ("hello", false))
     mi = first(specializations(m))
@@ -377,10 +367,10 @@ include("test_module.jl")
     @test has_name_typ(child(body, 2), :Bool, Type{Bool})
     tsn = TypedSyntaxNode(TSN.unnamedargs, (Type{Matrix{Float32}}, Type{Int}))
     sig, body = children(tsn)
+    @test child(sig, 1, 2).typ === Core.Const(Matrix{Float32})
+    @test child(sig, 1, 3).typ === Core.Const(Int)
     m = @which TSN.unnamedargs(Matrix{Float32}, Int, Int)
     fbody = Base.bodyfunction(m)
-    @test child(sig, 1, 2).typ === Type{Matrix{Float32}}
-    @test child(sig, 1, 3).typ === Type{Int}
     m = @which TSN.unnamedargs(Matrix{Float32}, Int; a="hello")
     mi = nothing
     for _mi in specializations(m)
@@ -394,8 +384,8 @@ include("test_module.jl")
     end
     tsn = TypedSyntaxNode(mi)
     sig, body = children(tsn)
-    @test child(sig, 1, 2).typ === Type{Matrix{Float32}}
-    @test child(sig, 1, 3).typ === Type{Int}
+    @test child(sig, 1, 2).typ === Core.Const(Matrix{Float32})
+    @test child(sig, 1, 3).typ === Core.Const(Int)
     @test has_name_notyp(child(sig, 1, 4, 1), :c)
     @test has_name_typ(child(sig, 1, 5, 1, 1), :a, String)
     m = @which TSN.unnamedargs(Matrix{Float32}, Int, :c; a="hello")
@@ -409,8 +399,8 @@ include("test_module.jl")
     end
     tsn = TypedSyntaxNode(mi)
     sig, body = children(tsn)
-    @test child(sig, 1, 2).typ === Type{Matrix{Float32}}
-    @test child(sig, 1, 3).typ === Type{Int}
+    @test child(sig, 1, 2).typ === Core.Const(Matrix{Float32})
+    @test child(sig, 1, 3).typ === Core.Const(Int)
     @test child(sig, 1, 4, 1).typ === Symbol
     @test child(sig, 1, 5, 1, 1).typ === String
     mbody = only(methods(fbody))
@@ -424,8 +414,8 @@ include("test_module.jl")
     end
     tsn = TypedSyntaxNode(mi)
     sig, body = children(tsn)
-    @test child(sig, 1, 2).typ === Type{Matrix{Float32}}
-    @test child(sig, 1, 3).typ === Type{Int}
+    @test child(sig, 1, 2).typ === Core.Const(Matrix{Float32})
+    @test child(sig, 1, 3).typ === Core.Const(Int)
     @test child(sig, 1, 4, 1).typ === Symbol
     @test child(sig, 1, 5, 1, 1).typ === String
     tsn = TypedSyntaxNode(TSN.unnamedargs2, (Type{Matrix}, Symbol))
@@ -464,7 +454,7 @@ include("test_module.jl")
     src = tsn.typedsource
     @test Symbol("kwargs...") ∈ src.slotnames
     sig, body = children(tsn)
-    @test child(body, 2, 1).typ <: Base.Iterators.Pairs
+    @test TypedSyntax.unwrapinternal(child(body, 2, 1).typ) <: Base.Iterators.Pairs
 
     # quoted symbols that could be confused for function definition
     tsn = TypedSyntaxNode(TSN.isexpreq, (Expr,))
@@ -477,10 +467,10 @@ include("test_module.jl")
     sig, body = children(tsn)
     errnode = child(body, 1, 2)
     errf = child(errnode, 1)
-    @test errnode.typ === nothing && errf.typ === typeof(Base.throw_boundserror)
+    @test errnode.typ === nothing && errf.typ === Core.Const(Base.throw_boundserror)
     retnode = child(body, 2)
     @test kind(retnode) == K"return"
-    @test retnode.typ === nothing || retnode.typ === Nothing
+    @test retnode.typ === Core.Const(nothing) || retnode.typ === nothing  # julia 1.10 doesn't assign a type to the Core.ReturnNode
 
     # Globals & scoped assignment
     tsn = TypedSyntaxNode(TSN.setglobal, (Char,))
@@ -492,7 +482,7 @@ include("test_module.jl")
     tsn = TypedSyntaxNode(TSN.myoftype, (Float64, Int))
     sig, body = children(tsn)
     node = child(body, 1)
-    @test node.typ === Type{Float64}
+    @test node.typ === Core.Const(Float64)
     tsn = TypedSyntaxNode(TSN.DefaultArray{Float32}, (Vector{Int}, Int))
     sig, body = children(tsn)
     @test kind(sig) == K"where"
@@ -520,18 +510,15 @@ include("test_module.jl")
     @test_broken body.typ == Int
 
     # Construction from MethodInstance
-    src, rt = TypedSyntax.getsrc(TSN.myoftype, (Float64, Int))
-    tsn = TypedSyntaxNode(src.parent)
+    tsn = TypedSyntaxNode(TSN.myoftype, (Float64, Int))
     sig, body = children(tsn)
     node = child(body, 1)
-    @test node.typ === Type{Float64}
+    @test node.typ === Core.Const(Float64)
 
     # UnionAll in signature (issue #409)
-    @static if VERSION ≥ v"1.9-"
-        tsn = TypedSyntaxNode(Core.kwcall, (NamedTuple, typeof(issorted), Vector{Int}))
-        sig, body = children(tsn)
-        @test has_name_typ(child(sig, 2), :itr, Vector{Int})
-    end
+    tsn = TypedSyntaxNode(Core.kwcall, (NamedTuple, typeof(issorted), Vector{Int}))
+    sig, body = children(tsn)
+    @test has_name_typ(child(sig, 2), :itr, Vector{Int})
 
     # Empty `return` (issue #458)
     tsn = TypedSyntaxNode(TSN.f458, ())
@@ -552,7 +539,7 @@ include("test_module.jl")
     @test TypedSyntax.num_positional_args(tsn) == 2
 
     # Display
-    tsn = TypedSyntaxNode(TSN.mysin, (Int,))
+    tsn = TypedSyntaxNode(TSN.mysin, (Int64,))
     str = sprint(tsn; context=:color=>false) do io, obj
         printstyled(io, obj; hide_type_stable=false)
     end
@@ -580,9 +567,9 @@ include("test_module.jl")
     str = sprint(tsn; context=:color=>false) do io, obj
         printstyled(io, obj; hide_type_stable=false)
     end
-    @test   occursin("s::$Int = 0::$Int", str)
+    @test   occursin("s::$Int = 0::$Int", str) || occursin("s::Core.Const(0) = 0::Core.Const(0)", str)
     @test !occursin("(s::$Int = 0::$Int)", str)
-    @test occursin("(s::Float64 += x::Float64)::Float64", str)
+    @test occursin("(s::Float64 += x::Float64)::Float64", str) || occursin("(s::Union{Float64, $Int} += x::Float64)::Float64", str)
     tsn = TypedSyntaxNode(TSN.zerowhere, (Vector{Int16},))
     str = sprint(tsn; context=:color=>true) do io, obj
         printstyled(io, obj; iswarn=true, hide_type_stable=false)
@@ -602,7 +589,7 @@ include("test_module.jl")
     end
     @test occursin("[1]\e[36m::Float32\e[39m", str)
     @test occursin("[2]\e[36m::Float32\e[39m", str)
-    tsn = TypedSyntaxNode(TSN.simplef, (Int, Float64))
+    tsn = TypedSyntaxNode(TSN.simplef, (Int64, Float64))
     str = sprint(tsn; context=:color=>false) do io, obj
         printstyled(io, obj; hide_type_stable=false)
     end
@@ -628,6 +615,32 @@ include("test_module.jl")
         printstyled(io, obj; hide_type_stable=false)
     end
     @test !occursin("::Type{Dict{String, Any}}", str)
+    tsn = TypedSyntaxNode(TSN.obfuscated, (Float64,))
+    str = sprint(tsn; context=:color=>false) do io, obj
+        printstyled(io, obj; hide_type_stable=false)
+    end
+    @test occursin("::Core.Const(sin)", str) || occursin("::typeof(sin)", str)
+    tsn = TypedSyntaxNode(TSN.calls_helper, (Float32,))
+    str = sprint(tsn; context=:color=>false) do io, obj
+        printstyled(io, obj; hide_type_stable=false)
+    end
+    @test !occursin("Core.Const", str)
+    tsn = TypedSyntaxNode(TSN.calls_helper1, (Float32,))
+    str = sprint(tsn; context=:color=>false) do io, obj
+        printstyled(io, obj; hide_type_stable=false)
+    end
+    @test !occursin("Core.Const", str)
+    tsn = TypedSyntaxNode(TSN.calls_helper2, (Float32,))
+    str = sprint(tsn; context=:color=>false) do io, obj
+        printstyled(io, obj; hide_type_stable=false)
+    end
+    @test !occursin("Core.Const", str)
+    tsn = TypedSyntaxNode(TSN.allbutfirst, (Vector{Bool},))
+    str = sprint(tsn; context=:color=>false) do io, obj
+        printstyled(io, obj; hide_type_stable=false)
+    end
+    @test occursin("2:end", str)
+
 
     # issue #413
     @test TypedSyntax.is_small_union_or_tunion(Union{})
@@ -643,10 +656,34 @@ include("test_module.jl")
     @test isa(tsnc, TypedSyntaxNode)
 
     # issue 487
-    m = which(TSN.f487, (Int,))
-    src, rt = getsrc(TSN.f487, (Int,))
+    inferred_result = TypedSyntax.get_inferred_result(TSN.f487, (Int,))
+    src, mi = inferred_result.src, inferred_result.mi
     rt = Core.Const(1)
-    tsn, _ = TypedSyntax.tsn_and_mappings(m, src, rt)
+    tsn, _ = TypedSyntax.tsn_and_mappings(mi, src, rt)
+    @test_nowarn str = sprint(tsn; context=:color=>false) do io, obj
+        printstyled(io, obj; hide_type_stable=false)
+    end
+
+    # issue 491
+    tsn = TypedSyntaxNode(+, (Int, Int)) # need a node, not important what it is
+    @test_nowarn TypedSyntax.type_annotation_mode(tsn, Union{}; type_annotations=true, hide_type_stable=false)
+
+    # issue 492
+    @static if isdefined(Base, :_tuple_unique_fieldtypes)
+    tsn = TypedSyntaxNode(Base._tuple_unique_fieldtypes, (Any,))
+    @test_nowarn str = sprint(tsn; context=:color=>false) do io, obj
+        printstyled(io, obj; hide_type_stable=false)
+    end
+    end
+
+    # issue 493
+    tsn = TypedSyntaxNode(TSN.f493, ())
+    @test_nowarn str = sprint(tsn; context=:color=>false) do io, obj
+        printstyled(io, obj; hide_type_stable=false)
+    end
+
+    # issue 506
+    tsn, _ = TypedSyntax.tsn_and_mappings(collect, (typeof(Base.Generator(+, 1:2)),))
     @test_nowarn str = sprint(tsn; context=:color=>false) do io, obj
         printstyled(io, obj; hide_type_stable=false)
     end
@@ -667,12 +704,36 @@ module VSCodeServer
         return nothing
     end
 end
-module TestVSCodeExt # stops modules defined in test files from overwriting stuff from previous test
-using Test, ..VSCodeServer
-
-@testset "VSCode TypedSyntax.jl" begin
-    @testset "test_vscode.jl" begin
-        include("test_vscode.jl")
-    end
+using TypedSyntax: InlayHint, Diagnostic, InlayHintKinds
+
+@testset "test_vscode.jl" begin
+    # VSCode
+    tsn = TypedSyntaxNode(TSN.fVSCode, (Int,))
+
+    io = IOContext(devnull, :inlay_hints=>Dict{String, Vector{InlayHint}}(), :diagnostics=>Diagnostic[])
+    printstyled(io, tsn)
+    @test getproperty.(first(values(io[:inlay_hints])), :kind) == [InlayHintKinds.Nothing, InlayHintKinds.Type, InlayHintKinds.Nothing] && getproperty.(first(values(io[:inlay_hints])), :label) == ["::Union{Float64, $Int}", "(", ")::Union{Float64, $Int}"]
+    @test length(io[:diagnostics]) == 2
+
+    io = IOContext(devnull, :inlay_hints=>Dict{String, Vector{InlayHint}}(), :diagnostics=>Diagnostic[])
+    printstyled(io, tsn; hide_type_stable=false)
+    @test getproperty.(first(values(io[:inlay_hints])), :kind) == vcat(InlayHintKinds.Type, InlayHintKinds.Nothing, repeat([InlayHintKinds.Type], 15), InlayHintKinds.Nothing) && getproperty.(first(values(io[:inlay_hints])), :label) == ["::$Int"
+    "::Union{Float64, $Int}"
+    "::$Int"
+    "("
+    "::$Int"
+    ")::$Int"
+    "::$Int"
+    "("
+    "::$Int"
+    ")::$Int"
+    "("
+    "::$Int"
+    "("
+    "::$Int"
+    ")::Bool"
+    "::Core.Const(-1)"
+    "::Core.Const(1.0)"
+    ")::Union{Float64, $Int}"]
+    @test length(io[:diagnostics]) == 2
 end
-end
\ No newline at end of file
diff --git a/TypedSyntax/test/test_module.jl b/TypedSyntax/test/test_module.jl
index 2cfc430..64fe596 100644
--- a/TypedSyntax/test/test_module.jl
+++ b/TypedSyntax/test/test_module.jl
@@ -50,10 +50,10 @@ zerowhere(::AbstractArray{T}) where T<:Real = zero(T)
 vaparam(a::AbstractArray{T,N}, I::NTuple{N,Any}) where {T,N} = N
 @inline function myplustv(x::T, y::Integer) where {T<:AbstractChar}  # vendored copy of +(::T, ::Integer) where T<:AbstractChar
     if x isa Char
-        u = Int32((bitcast(UInt32, x) >> 24) % Int8)
+        u = Int32((Base.bitcast(UInt32, x) >> 24) % Int8)
         if u >= 0 # inline the runtime fast path
             z = u + y
-            return 0 <= z < 0x80 ? bitcast(Char, (z % UInt32) << 24) : Char(UInt32(z))
+            return 0 <= z < 0x80 ? Base.bitcast(Char, (z % UInt32) << 24) : Char(UInt32(z))
         end
     end
     return T(Int32(x) + Int32(y))
@@ -237,4 +237,28 @@ f482b(x) = Dict{String,Any}(x)
 # Issue 487
 f487(x) = 1
 
+function f493()
+    T = rand() > 0.5 ? Int64 : Float64
+    sum(rand(T, 100))
+end
+
+function obfuscated(x)
+    f = sin
+    return f(x)
+end
+
+module Internal
+export helper
+helper(x) = x+1
+module MoreInternal
+helper2(x) = x+2
+end
+end
+using .Internal
+calls_helper(x) = helper(x)
+calls_helper1(x) = Internal.helper(x)
+calls_helper2(x) = Internal.MoreInternal.helper2(x)
+
+allbutfirst(list) = list[2:end]
+
 end
diff --git a/TypedSyntax/test/test_vscode.jl b/TypedSyntax/test/test_vscode.jl
deleted file mode 100644
index d64bd7b..0000000
--- a/TypedSyntax/test/test_vscode.jl
+++ /dev/null
@@ -1,668 +0,0 @@
-module test_vscode
-
-using ..VSCodeServer
-using JuliaSyntax: JuliaSyntax, SyntaxNode, children, child, sourcetext, kind, @K_str
-using TypedSyntax: TypedSyntax, TypedSyntaxNode, getsrc, InlayHint, Diagnostic, InlayHintKinds
-using Dates, InteractiveUtils, Test
-
-has_name_typ(node, name::Symbol, @nospecialize(T)) = kind(node) == K"Identifier" && node.val === name && node.typ === T
-has_name_notyp(node, name::Symbol) = has_name_typ(node, name, nothing)
-
-include("test_module.jl")
-
-@testset "TypedSyntax.jl" begin
-    specializations(m::Method) = isdefined(Base, :specializations) ? Base.specializations(m) : m.specializations
-
-    st = """
-    f(x, y, z) = x * y + z
-    """
-    rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN1.jl")
-    TSN.eval(Expr(rootnode))
-    src, _ = getsrc(TSN.f, (Float32, Int, Float64))
-    tsn = TypedSyntaxNode(rootnode, src)
-    sig, body = children(tsn)
-    @test children(sig)[2].typ === Float32
-    @test children(sig)[3].typ === Int
-    @test children(sig)[4].typ === Float64
-    @test body.typ === Float64   # aggregate output
-    @test children(body)[1].typ === Float32
-
-    # Multiline
-    st = """
-    function g(a, b, c)
-        x = a + b
-        return x + c
-    end
-    """
-    rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN2.jl")
-    TSN.eval(Expr(rootnode))
-    src, _ = getsrc(TSN.g, (Int16, Int16, Int32))
-    tsn = TypedSyntaxNode(rootnode, src)
-    sig, body = children(tsn)
-    @test length(children(sig)) == 4
-    @test children(body)[2].typ === Int32
-    # Check that `x` gets an assigned type
-    nodex = child(body, 1, 1)
-    @test nodex.typ === Int16
-
-    # Target ambiguity
-    st = "math(x) = x + sin(x + π / 4)"
-    rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN2.jl")
-    TSN.eval(Expr(rootnode))
-    src, _ = getsrc(TSN.math, (Int,))
-    tsn = TypedSyntaxNode(rootnode, src)
-    sig, body = children(tsn)
-    @test has_name_typ(child(body, 1), :x, Int)
-    @test has_name_typ(child(body, 3, 2, 1), :x, Int)
-    pi4 = child(body, 3, 2, 3)
-    @test kind(pi4) == K"call" && pi4.typ == typeof(π / 4)
-    tsn = TypedSyntaxNode(TSN.has2xa, (Real,))
-    @test tsn.typ === Any
-    sig, body = children(tsn)
-    @test has_name_typ(child(sig, 2), :x, Real)
-    @test has_name_typ(child(body, 1, 2), :x, Real)
-    @test has_name_typ(child(body, 1, 1), :x, Any)
-    tsn = TypedSyntaxNode(TSN.has2xb, (Real,))
-    @test tsn.typ === Any
-    sig, body = children(tsn)
-    @test has_name_typ(child(sig, 2), :x, Real)
-    @test has_name_typ(child(body, 1, 2), :x, Real)
-    @test has_name_typ(child(body, 1, 1), :x, Any)
-
-    # Target duplication
-    st = "math2(x) = sin(x) + sin(x)"
-    rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN2.jl")
-    TSN.eval(Expr(rootnode))
-    src, _ = getsrc(TSN.math2, (Int,))
-    tsn = TypedSyntaxNode(rootnode, src)
-    sig, body = children(tsn)
-    @test body.typ === Float64
-    @test_broken child(body, 1).typ === Float64
-    tsn = TypedSyntaxNode(TSN.simplef, Tuple{Float32, Int32})
-    sig, body = children(tsn)
-    @test has_name_typ(child(body, 1, 2, 1), :a, Float32)
-    @test has_name_typ(child(body, 1, 2, 3), :a, Float32)
-
-    # Inner functions
-    for (st, idxsinner, idxsouter) in (
-        ("firstfirst(c) = map(x -> first(x), first(c))", (2, 2), (3,)),
-        ("""
-        firstfirst(c) = map(first(c)) do x
-            first(x)
-        end
-        """, (3, 1), (1, 2))
-        )
-        rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN3.jl")
-        TSN.eval(Expr(rootnode))
-        src, _ = getsrc(TSN.firstfirst, (Vector{Vector{Real}},))
-        tsn = TypedSyntaxNode(rootnode, src)
-        sig, body = children(tsn)
-        @test child(body, idxsinner...).typ === nothing
-        @test child(body, idxsouter...).typ === Vector{Real}
-    end
-
-    # body macros
-    tsn = TypedSyntaxNode(TSN.hasmacro, (Tuple{Int,Int}, Char))
-    sig, body = children(tsn)
-    @test has_name_typ(child(body, 2, 2, 2), :t, Tuple{Int,Int})
-    @test has_name_typ(child(body, 2, 3), :x, Char)
-
-    # signature macros
-    tsn = TypedSyntaxNode(TSN.nospec, (Any,))
-    sig, body = children(tsn)
-    node = child(sig, 2)
-    @test kind(node) == K"macrocall"
-    @test child(node, 1).val == Symbol("@nospecialize")
-    @test has_name_typ(child(node, 2), :x, Any)
-    @test body.typ === Any
-    tsn = TypedSyntaxNode(TSN.nospec2, (AbstractVecOrMat,))
-    sig, body = children(tsn)
-    node = child(sig, 2)
-    @test kind(node) == K"macrocall"
-    @test child(node, 1).val == Symbol("@nospecialize")
-    arg = child(node, 2)
-    @test kind(arg) == K"::"
-    @test has_name_typ(child(arg, 1), :x, AbstractVecOrMat)
-    @test body.typ === Any
-    tsn = TypedSyntaxNode(eltype, (TSN.ReadOnly,))
-    @test tsn.typ === Type{Int}
-
-    # signature return-type annotations
-    tsn = TypedSyntaxNode(TSN.withrt, (IO,))
-    @test tsn.typ === Bool
-    sig, body = children(tsn)
-    @test has_name_typ(child(sig, 1, 2, 1), :io, IO)
-    tsn = TypedSyntaxNode(TSN.mytimes, (Bool,Float16))
-    sig, body = children(tsn)
-    @test has_name_typ(child(sig, 1, 1, 2, 1), :x, Bool)
-    @test has_name_typ(child(sig, 1, 1, 3, 1), :y, Float16)
-
-    # operators
-    tsn = TypedSyntaxNode(+, (TSN.MyInt, TSN.MyInt))
-    sig, body = children(tsn)
-    @test has_name_typ(child(sig, 2, 1), :a, TSN.MyInt)
-    tsn = TypedSyntaxNode(-, (TSN.MyInt, TSN.MyInt))
-    sig, body = children(tsn)
-    @test has_name_typ(child(sig, 2, 1), :a, TSN.MyInt)
-
-    # `ref` indexing
-    st = """
-        function setlist!(listset, listget, i, j)
-            listset[i+1][j+1] = listget[i][j]
-        end
-        """
-    rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN4.jl")
-    TSN.eval(Expr(rootnode))
-    src, rt = getsrc(TSN.setlist!, (Vector{Vector{Float32}}, Vector{Vector{UInt8}}, Int, Int))
-    tsn = TypedSyntaxNode(rootnode, src)
-    sig, body = children(tsn)
-    nodelist = child(body, 1, 2, 1, 1)                             # `listget`
-    @test sourcetext(nodelist) == "listget" && nodelist.typ === Vector{Vector{UInt8}}
-    @test nodelist.parent.typ === Vector{UInt8}                    # `listget[i]`
-    @test sourcetext(child(nodelist.parent, 2)) == "i"
-    @test nodelist.parent.parent.typ === UInt8                     # `listget[i][j]`
-
-    nodelist = child(body, 1, 1, 1, 1)                             # `listset`
-    @test sourcetext(nodelist) == "listset" && nodelist.typ === Vector{Vector{Float32}}
-    @test sourcetext(child(nodelist.parent, 2)) == "i+1"
-    @test nodelist.parent.typ === Vector{Float32}                  # `listset[i+1]`
-    @test kind(nodelist.parent.parent.parent) == K"="              # the `setindex!` call
-
-    # tuple-destructuring
-    st = """
-        function callfindmin(list)
-            val, idx = findmin(list)
-            x, y = idx, val
-            return y
-        end
-    """
-    rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN5.jl")
-    TSN.eval(Expr(rootnode))
-    src, rt = getsrc(TSN.callfindmin, (Vector{Float64},))
-    tsn = TypedSyntaxNode(rootnode, src)
-    sig, body = children(tsn)
-    t = child(body, 1, 1)
-    @test kind(t) == K"tuple"
-    @test has_name_typ(child(t, 1), :val, Float64)
-    @test has_name_typ(child(t, 2), :idx, Int)
-    t = child(body, 2, 1)
-    @test kind(t) == K"tuple"
-    @test has_name_typ(child(t, 1), :x, Int)
-    @test has_name_typ(child(t, 2), :y, Float64)
-
-    tsn = TypedSyntaxNode(TSN.bar381, (TSN.Foo381,))
-    sig, body = children(tsn)
-    lhs = child(body, 1, 1)
-    @test has_name_typ(child(lhs, 1), :a, Any)
-    @test has_name_typ(child(lhs, 2, 1), :b1, Any)
-    @test has_name_typ(child(lhs, 2, 2), :b2, Any)
-
-    tsn = TypedSyntaxNode(TSN.extrema2, (Tuple{Int,Int}, Tuple{Int,Int}))
-    sig, body = children(tsn)
-    @test child(sig, 2).typ == Tuple{Int,Int}
-
-    g = Base.Generator(identity, 1.0:4.0)
-    tsn = TypedSyntaxNode(TSN.typeof_first_item, (typeof(g),))
-    sig, body = children(tsn)
-    @test has_name_typ(child(body, 3, 1, 1), :val, Float64)
-
-    # GlobalRefs
-    tsn = TypedSyntaxNode(TSN.getchar1, (Int,))
-    sig, body = children(tsn)
-    @test has_name_typ(child(body, 1), :charset1, Any)
-    tsn = TypedSyntaxNode(TSN.getchar2, (Int,))
-    sig, body = children(tsn)
-    @test has_name_typ(child(body, 1), :charset2, typeof(TSN.charset2))
-
-    # Generators & comprehensions
-    tsn = TypedSyntaxNode(TSN.boxedgenerator368, (Int,))
-    sig, body = children(tsn)
-    cnode = child(body, 2)
-    @test kind(cnode) == K"comprehension"
-    @test cnode.typ == Vector
-    tsn = TypedSyntaxNode(TSN.nestedgenerators, (Int, Int))
-    sig, body = children(tsn)
-    @test kind(body) == K"generator"
-    @test body.typ <: Base.Iterators.Flatten
-    tsn = TypedSyntaxNode(TSN.nestedgenerators, (Int,))
-    sig, body = children(tsn)
-    @test kind(body) == K"generator"
-    @test body.typ <: Base.Iterators.Flatten
-    tsn = TypedSyntaxNode(TSN.nestedexplicit, (Int,))
-    sig, body = children(tsn)
-    @test kind(body) == K"comprehension"
-    @test body.typ <: Vector
-    node = child(body, 1)
-    @test kind(node) == K"generator"
-    @test node.typ <: Base.Generator
-
-    # Broadcasting
-    tsn = TypedSyntaxNode(TSN.fbroadcast, (Vector{Int},))
-    sig, body = children(tsn)
-    @test body.typ === Float64
-    cnode = child(body, 2)
-    @test kind(cnode) == K"dotcall"
-    @test cnode.typ == Vector{Float64}
-    tsn = TypedSyntaxNode(TSN.fbroadcast_explicit, (Vector{Int},))
-    sig, body = children(tsn)
-    @test body.typ === Float64
-    cnode = child(body, 2)
-    cnodef = child(cnode, 1, 2, 1)
-    @test kind(cnodef) == K"Identifier" && cnodef.val == :materialize
-    @test cnode.typ === Vector{Float64}
-    cnode = child(body, 2, 2)
-    cnodef = child(cnode, 1, 2, 1)
-    @test kind(cnodef) == K"Identifier" && cnodef.val == :broadcasted
-    @test cnode.typ <: Broadcast.Broadcasted
-    tsn = TypedSyntaxNode(TSN.fbroadcast2, (Vector{Int},))
-    sig, body = children(tsn)
-    node = child(body, 2)
-    src = tsn.typedsource
-    if isa(src.code[1], GlobalRef)
-        @test kind(node) == K"dotcall" && node.typ === Vector{String}
-    else
-        # We aren't quite handling this properly yet
-        @test_broken kind(node) == K"dotcall" && node.typ === Vector{String}
-    end
-    tsn = TypedSyntaxNode(TSN.bcast415, (TSN.B415, Float64))
-    sig, body = children(tsn)
-    @test child(body, 1).typ === Float64
-
-    # Misc lowering
-    tsn = TypedSyntaxNode(TSN.myunique, (AbstractRange,))
-    sig, body = children(tsn)
-    @test has_name_typ(child(body, 2), :r, AbstractRange)
-    @test_broken has_name_typ(child(body, 3, 2), :r, AbstractRange)
-
-    # kwfuncs
-    st = """
-    function avoidzero(x; avoid_zero=true)
-        fx = float(x)
-        return iszero(x) ? oftype(fx, NaN) : fx
-    end
-    """
-    rootnode = JuliaSyntax.parsestmt(SyntaxNode, st; filename="TSN6.jl")
-    TSN.eval(Expr(rootnode))
-    src, rt = getsrc(TSN.avoidzero, (Int,))
-    # src looks like this:
-    #   %1 = Main.TSN.:(var"#avoidzero#6")(true, #self#, x)::Float64
-    #        return %1
-    # Consequently there is nothing to match, but at least we shouldn't error
-    tsn = TypedSyntaxNode(rootnode, src)
-    @test isa(tsn, TypedSyntaxNode)
-    @test rt === Float64
-    # Try the kwbodyfunc
-    m = which(TSN.avoidzero, (Int,))
-    src, rt = getsrc(Base.bodyfunction(m), (Bool, typeof(TSN.avoidzero), Int,))
-    tsn = TypedSyntaxNode(rootnode, src)
-    sig, body = children(tsn)
-    isz = child(body, 2, 1, 1)
-    @test kind(isz) == K"call" && child(isz, 1).val == :iszero
-    @test isz.typ === Bool
-    @test child(body, 2, 1, 2).typ == Float64
-
-    # default positional arguments
-    tsn = TypedSyntaxNode(TSN.defaultarg, (Float32,))
-    sig, body = children(tsn)
-    @test has_name_typ(child(sig, 2), :x, Float32)
-    @test has_name_notyp(child(sig, 3, 1), :y)
-    # there is no argument 2 in tsn.typedsource
-    tsn = TypedSyntaxNode(TSN.defaultarg, (Float32,Int))
-    sig, body = children(tsn)
-    @test has_name_typ(child(sig, 2), :x, Float32)
-    nodearg = child(sig, 3)
-    @test kind(nodearg) == K"="
-    @test has_name_typ(child(nodearg, 1), :y, Int)
-    # default position args that are types
-    tsn = TypedSyntaxNode(TSN.hasdefaulttypearg, (Type{Float32},))
-    sig, body = children(tsn)
-    arg = child(sig, 1, 2, 1)
-    @test kind(arg) == K"::" && arg.typ === Type{Float32}
-    tsn = TypedSyntaxNode(TSN.hasdefaulttypearg, ())
-    sig, body = children(tsn)
-    arg = child(sig, 1, 2, 1)
-    @test_broken kind(arg) == K"::" && arg.typ === Type{Rational{Int}}  # maybe this shouldn't even be true
-
-    # macros in function definition
-    tsn = TypedSyntaxNode(TSN.mysin, (Int,))
-    @test kind(tsn) == K"macrocall"
-    sig, body = children(child(tsn, 2))
-    @test has_name_typ(child(sig, 2, 1), :x, Int)
-    @test has_name_typ(child(body, 1, 1), :xf, Float64)
-
-    # generated functions
-    # This mostly tests that we don't choke on assignments to variables whose names do not show up in the source
-    tsn = TypedSyntaxNode(TSN.generated385, (Vector{Int},))
-    @test kind(tsn) == K"macrocall"
-    sig, body = children(child(tsn, 2))
-    @test has_name_typ(child(sig, 2, 1), :dest, Vector{Int})
-
-    # `for` loops
-    tsn = TypedSyntaxNode(TSN.summer, (Vector{Float64},))
-    @test tsn.typ == Union{Int,Float64}
-    sig, body = children(tsn)
-    @test has_name_typ(child(sig, 2), :list, Vector{Float64})
-    @test has_name_typ(child(body, 1, 1), :s, Int)
-    @test has_name_typ(child(body, 2, 1, 1), :x, Float64)
-    node = child(body, 2, 2, 1)
-    @test kind(node) == K"+="
-    @test has_name_typ(child(node, 1), :s, Float64)   # if this line runs, the LHS now has type `Float64`
-    @test has_name_typ(child(node, 2), :x, Float64)
-    @test has_name_typ(child(body, 3, 1), :s, Union{Float64, Int})
-    tsn = TypedSyntaxNode(TSN.summer_iterate, (Vector{Float64},))
-    @test tsn.typ == Union{Int,Float64}
-    sig, body = children(tsn)
-    @test has_name_typ(child(body, 2, 1), :ret, Union{Nothing, Tuple{Float64, Int64}})
-    @test has_name_typ(child(body, 3, 2, 1, 1, 1), :x, Float64)
-
-    # `where`, unnamed arguments, and types-as-arguments
-    tsn = TypedSyntaxNode(TSN.zerowhere, (Vector{Int16},))
-    sig, body = children(tsn)
-    @test child(sig, 1, 2).typ === Vector{Int16}
-    @test body.typ === Int16
-    @test has_name_typ(child(body, 2), :T, Type{Int16})
-    # tsn = TypedSyntaxNode(TSN.vaparam, (Matrix{Float32}, (String, Bool)))    # fails on `which`
-    m = @which TSN.vaparam(rand(3,3), ("hello", false))
-    mi = first(specializations(m))
-    tsn = TypedSyntaxNode(mi)
-    sig, body = children(tsn)
-    @test has_name_typ(child(sig, 1, 3, 1), :I, Tuple{String, Bool})
-    tsn = TypedSyntaxNode(TSN.myplustv, (Char, UInt8))
-    sig, body = children(TypedSyntax.get_function_def(tsn))
-    @test has_name_typ(child(sig, 1, 2, 1), :x, Char)
-    @test has_name_typ(child(sig, 1, 3, 1), :y, UInt8)
-    tsn = TypedSyntaxNode(TSN.myplustv, (AbstractChar, UInt8))
-    sig, body = children(TypedSyntax.get_function_def(tsn))
-    @test has_name_typ(child(sig, 1, 2, 1), :x, AbstractChar)
-    @test has_name_typ(child(sig, 1, 3, 1), :y, UInt8)
-    tsn = TypedSyntaxNode(TSN.cb, (Vector{Int16}, Int))
-    sig, body = children(tsn)
-    @test has_name_typ(child(body, 2), :Bool, Type{Bool})
-    tsn = TypedSyntaxNode(TSN.unnamedargs, (Type{Matrix{Float32}}, Type{Int}))
-    sig, body = children(tsn)
-    m = @which TSN.unnamedargs(Matrix{Float32}, Int, Int)
-    fbody = Base.bodyfunction(m)
-    @test child(sig, 1, 2).typ === Type{Matrix{Float32}}
-    @test child(sig, 1, 3).typ === Type{Int}
-    m = @which TSN.unnamedargs(Matrix{Float32}, Int; a="hello")
-    mi = nothing
-    for _mi in specializations(m)
-        _mi === nothing && continue
-        nt = _mi.specTypes.parameters[2]
-        nt <: NamedTuple || continue
-        if nt == NamedTuple{(:a,), Tuple{String}}
-            mi = _mi
-            break
-        end
-    end
-    tsn = TypedSyntaxNode(mi)
-    sig, body = children(tsn)
-    @test child(sig, 1, 2).typ === Type{Matrix{Float32}}
-    @test child(sig, 1, 3).typ === Type{Int}
-    @test has_name_notyp(child(sig, 1, 4, 1), :c)
-    @test has_name_typ(child(sig, 1, 5, 1, 1), :a, String)
-    m = @which TSN.unnamedargs(Matrix{Float32}, Int, :c; a="hello")
-    mi = nothing
-    for _mi in specializations(m)
-        _mi === nothing && continue
-        if any(==(Symbol), _mi.specTypes.parameters)
-            mi = _mi
-            break
-        end
-    end
-    tsn = TypedSyntaxNode(mi)
-    sig, body = children(tsn)
-    @test child(sig, 1, 2).typ === Type{Matrix{Float32}}
-    @test child(sig, 1, 3).typ === Type{Int}
-    @test child(sig, 1, 4, 1).typ === Symbol
-    @test child(sig, 1, 5, 1, 1).typ === String
-    mbody = only(methods(fbody))
-    mi = nothing
-    for _mi in specializations(mbody)
-        _mi === nothing && continue
-        if any(==(Symbol), _mi.specTypes.parameters)
-            mi = _mi
-            break
-        end
-    end
-    tsn = TypedSyntaxNode(mi)
-    sig, body = children(tsn)
-    @test child(sig, 1, 2).typ === Type{Matrix{Float32}}
-    @test child(sig, 1, 3).typ === Type{Int}
-    @test child(sig, 1, 4, 1).typ === Symbol
-    @test child(sig, 1, 5, 1, 1).typ === String
-    tsn = TypedSyntaxNode(TSN.unnamedargs2, (Type{Matrix}, Symbol))
-    sig, body = children(tsn)
-    @test child(sig, 2).typ === Type{Matrix}
-    @test child(sig, 3, 1).typ === Symbol
-    @test has_name_notyp(child(sig, 4, 1, 1, 1), :padding)
-
-    # Values as static_parameters
-    tsn = TypedSyntaxNode(TSN.val, (Val{4},))
-    sig, body = children(tsn)
-    @test TypedSyntax.child(body, 1, 1, 1).typ == Core.Const(4)
-
-    # varargs
-    tsn = TypedSyntaxNode(TSN.likevect, (Int, Int))
-    sig, body = children(tsn)
-    nodeva = child(sig, 1, 2)
-    @test kind(nodeva) == K"..."
-    @test has_name_typ(child(nodeva, 1, 1), :X, Tuple{Int,Int})
-    tsn = TypedSyntaxNode(TSN.cbva, (Matrix{Float32}, Int, Int))
-    sig, body = children(tsn)
-    @test body.typ === Bool
-    @test has_name_typ(child(body, 2), :Bool, Type{Bool})
-    @test has_name_typ(child(body, 3), :a, Matrix{Float32})
-    nodeva = child(body, 4)
-    @test kind(nodeva) == K"..."
-    @test has_name_typ(child(nodeva, 1), :i, Tuple{Int,Int})
-    tsn = TypedSyntaxNode(TSN.splats, (Tuple{Int,Int}, Tuple{Int}))
-    sig, body = children(tsn)
-    @test body.typ === Vector{Int}
-    @test has_name_typ(child(body, 2, 1), :x, Tuple{Int,Int})
-    @test has_name_typ(child(body, 3, 1), :y, Tuple{Int})
-    m = @which TSN.anykwargs(; cat=1, dog=2)
-    mi = first(specializations(m))
-    tsn = TypedSyntaxNode(mi)
-    src = tsn.typedsource
-    @test Symbol("kwargs...") ∈ src.slotnames
-    sig, body = children(tsn)
-    @test child(body, 2, 1).typ <: Base.Iterators.Pairs
-
-    # quoted symbols that could be confused for function definition
-    tsn = TypedSyntaxNode(TSN.isexpreq, (Expr,))
-    sig, body = children(tsn)
-    @test has_name_typ(child(sig, 2, 1), :ex, Expr)
-
-    # Unused statements
-    tsn = TypedSyntaxNode(TSN.mycheckbounds, (Vector{Int}, Int))
-    @test tsn.typ === Nothing
-    sig, body = children(tsn)
-    errnode = child(body, 1, 2)
-    errf = child(errnode, 1)
-    @test errnode.typ === nothing && errf.typ === typeof(Base.throw_boundserror)
-    retnode = child(body, 2)
-    @test kind(retnode) == K"return"
-    @test retnode.typ === nothing || retnode.typ === Nothing
-
-    # Globals & scoped assignment
-    tsn = TypedSyntaxNode(TSN.setglobal, (Char,))
-    # Agnostic about whether it's good to tag the type of `myglobal`, but at least `val` should be tagged
-    sig, body = children(tsn)
-    @test has_name_typ(child(body, 1, 1, 2), :val, Char)
-
-    # DataTypes
-    tsn = TypedSyntaxNode(TSN.myoftype, (Float64, Int))
-    sig, body = children(tsn)
-    node = child(body, 1)
-    @test node.typ === Type{Float64}
-    tsn = TypedSyntaxNode(TSN.DefaultArray{Float32}, (Vector{Int}, Int))
-    sig, body = children(tsn)
-    @test kind(sig) == K"where"
-    @test kind(child(sig, 1)) == K"call"
-    f = child(sig, 1, 1)
-    @test kind(f) == K"curly" && f.typ === Type{TSN.DefaultArray{Float32}}
-
-    # Field access & a more complex example
-    tsn = TypedSyntaxNode(Base.getindex, (TSN.DefaultArray{Float64,2,Matrix{Float64}}, Int, Int))
-    @test tsn.typ === Float64
-    sig, body = children(tsn)
-    @test kind(body) == K"?"
-    @test child(body, 1).typ === Bool
-    nodeidx = child(body, 2)
-    @test nodeidx.typ === Float64
-    @test child(nodeidx, 1).typ === Matrix{Float64}
-    default = child(body, 3)
-    @test default.typ === Float64
-    @test child(default, 1).typ === TSN.DefaultArray{Float64,2,Matrix{Float64}}
-
-    # global
-    tsn = TypedSyntaxNode(TSN.in_let, (Int,))
-    sig, body = children(child(tsn, 1))
-    @test has_name_typ(child(sig, 2), :x, Int)
-    @test_broken body.typ == Int
-
-    # Construction from MethodInstance
-    src, rt = TypedSyntax.getsrc(TSN.myoftype, (Float64, Int))
-    tsn = TypedSyntaxNode(src.parent)
-    sig, body = children(tsn)
-    node = child(body, 1)
-    @test node.typ === Type{Float64}
-
-    # UnionAll in signature (issue #409)
-    @static if VERSION ≥ v"1.9-"
-        tsn = TypedSyntaxNode(Core.kwcall, (NamedTuple, typeof(issorted), Vector{Int}))
-        sig, body = children(tsn)
-        @test has_name_typ(child(sig, 2), :itr, Vector{Int})
-    end
-
-    # Empty `return` (issue #458)
-    tsn = TypedSyntaxNode(TSN.f458, ())
-    @test tsn.typ === Nothing
-    sig, body = children(tsn)
-    @test kind(body) == K"return"
-    @test body.typ === Nothing
-
-    # Counting arguments (needed for Cthulhu)
-    # issue #397
-    tsn = TypedSyntaxNode(TSN.f397, (typeof(view([1,2,3], 1:2)),))
-    @test TypedSyntax.num_positional_args(tsn) == 2 # the function is arg1, x is arg2
-    # issue #426
-    tsn = TypedSyntaxNode(getindex, (typeof(TSN.T426), Type{Year},))
-    @test TypedSyntax.num_positional_args(tsn) == 3
-    # issue #433
-    tsn = TypedSyntaxNode(TSN.withrt, (IO,))
-    @test TypedSyntax.num_positional_args(tsn) == 2
-
-    # Display
-    tsn = TypedSyntaxNode(TSN.mysin, (Int,))
-    str = sprint(tsn; context=:color=>false) do io, obj
-        printstyled(io, obj; hide_type_stable=false)
-    end
-    # There are several potential valid ways to print the signature.
-    # Here we allow them all (some tests below may not be as flexible).
-    # Arguably the first seems best, as it has the inferred type
-    # bound more tightly to the object than the type-assertion in the signature,
-    # and is valid syntax that is a bit more sparing in its use of ().
-    @test occursin("function (\$f)(x::Int64::Real)::Float64", str) ||
-      occursin("function (\$f)((x::Int64)::Real)::Float64", str) ||
-      occursin("function (\$f)(x::Real::Int64)::Float64", str) ||
-      occursin("function (\$f)((x::Real)::Int64)::Float64", str)
-    tsn = TypedSyntaxNode(TSN.summer, (Vector{Any},))
-    str = sprint(tsn; context=:color=>true) do io, obj
-        printstyled(io, obj; iswarn=true, hide_type_stable=false)
-    end
-    @test occursin("summer(list\e[36m::Vector{Any}\e[39m)\e[31m::Any", str)
-    @test occursin("s\e[31m::Any\e[39m += x\e[31m::Any\e[39m", str)
-    str = sprint(tsn; context=:color=>true) do io, obj
-        printstyled(io, obj; type_annotations=false)
-    end
-    @test occursin("summer(list)", str)
-    @test occursin("s += x", str)
-    tsn = TypedSyntaxNode(TSN.summer, (Vector{Float64},))
-    str = sprint(tsn; context=:color=>false) do io, obj
-        printstyled(io, obj; hide_type_stable=false)
-    end
-    @test   occursin("s::$Int = 0::$Int", str)
-    @test !occursin("(s::$Int = 0::$Int)", str)
-    @test occursin("(s::Float64 += x::Float64)::Float64", str)
-    tsn = TypedSyntaxNode(TSN.zerowhere, (Vector{Int16},))
-    str = sprint(tsn; context=:color=>true) do io, obj
-        printstyled(io, obj; iswarn=true, hide_type_stable=false)
-    end
-    @test occursin("AbstractArray{T})\e[36m::Vector{Int16}\e[39m", str)
-    @test occursin("where T<:Real)\e[36m::Int16\e[39m", str)
-    str = sprint(tsn; context=:color=>false) do io, obj
-        printstyled(io, obj; hide_type_stable=false)
-    end
-    # One could either have `(::AbstractArray{T})` or `::(AbstractArray{T})`
-    # The latter is more consistent with how we want `-x` to print.
-    @test occursin("(zerowhere(::(AbstractArray{T})::Vector{Int16}) where T<:Real)::Int16", str)
-    @test occursin("zero(T::Type{Int16})", str)
-    tsn = TypedSyntaxNode(TSN.add2, (Vector{Float32},))
-    str = sprint(tsn; context=:color=>true) do io, obj
-        printstyled(io, obj; iswarn=true, hide_type_stable=false)
-    end
-    @test occursin("[1]\e[36m::Float32\e[39m", str)
-    @test occursin("[2]\e[36m::Float32\e[39m", str)
-    tsn = TypedSyntaxNode(TSN.simplef, (Int, Float64))
-    str = sprint(tsn; context=:color=>false) do io, obj
-        printstyled(io, obj; hide_type_stable=false)
-    end
-    @test str === """
-        5 function simplef(a::Int64, b::Float64)::Float64
-        6     z::Int64 = (a::Int64 * a::Int64)::Int64
-        7     return (z::Int64 + b::Float64)::Float64
-        8 end"""
-    tsn = TypedSyntaxNode(TSN.myabs, (Float64,))
-    str = sprint(tsn; context=:color=>false) do io, obj
-        printstyled(io, obj; hide_type_stable=false)
-    end
-    @test occursin("-(x::Float64)::Float64", str)
-
-    # issue #413
-    @test TypedSyntax.is_small_union_or_tunion(Union{})
-    @test TypedSyntax.is_small_union_or_tunion(Union{Tuple{}, Tuple{Int64}})
-
-    # artificial trigger for #414
-    tsn = TypedSyntaxNode(TSN.unnamedargs, (Type{Matrix{Float32}}, Type{Int}))
-    sig, body = children(tsn)
-    @test isa(TypedSyntax.map_signature!(sig, fill(Symbol(""), 3), Any[Any, Core.Const(2), Any]), TypedSyntaxNode)
-
-    # issue #435
-    tsnc = copy(tsn)
-    @test isa(tsnc, TypedSyntaxNode)
-end
-
-@testset "test_vscode.jl" begin
-    # VSCode
-    tsn = TypedSyntaxNode(TSN.fVSCode, (Int64,))
-
-    io = IOContext(devnull, :inlay_hints=>Dict{String, Vector{InlayHint}}(), :diagnostics=>Diagnostic[])
-    printstyled(io, tsn)
-    @test getproperty.(first(values(io[:inlay_hints])), :kind) == [InlayHintKinds.Nothing, InlayHintKinds.Type, InlayHintKinds.Nothing] && getproperty.(first(values(io[:inlay_hints])), :label) == ["::Union{Float64, Int64}", "(", ")::Union{Float64, Int64}"]
-    @test length(io[:diagnostics]) == 2
-
-    io = IOContext(devnull, :inlay_hints=>Dict{String, Vector{InlayHint}}(), :diagnostics=>Diagnostic[])
-    printstyled(io, tsn; hide_type_stable=false)
-    @test getproperty.(first(values(io[:inlay_hints])), :kind) == vcat(InlayHintKinds.Type, InlayHintKinds.Nothing, repeat([InlayHintKinds.Type], 15), InlayHintKinds.Nothing) && getproperty.(first(values(io[:inlay_hints])), :label) == ["::Int64"
-    "::Union{Float64, Int64}"
-    "::Int64"
-    "("
-    "::Int64"
-    ")::Int64"
-    "::Int64"
-    "("
-    "::Int64"
-    ")::Int64"
-    "("
-    "::Int64"
-    "("
-    "::Int64"
-    ")::Bool"
-    "::Int64"
-    "::Float64"
-    ")::Union{Float64, Int64}"]
-    @test length(io[:diagnostics]) == 2
-end
-end
\ No newline at end of file
diff --git a/test/generate_irshow.jl b/test/generate_irshow.jl
index 0e2ddc1..2a95722 100644
--- a/test/generate_irshow.jl
+++ b/test/generate_irshow.jl
@@ -11,7 +11,7 @@ function generate_test_cases(f, tt, fname=string(nameof(f)))
     outputs = Dict()
     tf = (true, false)
     for optimize in tf
-        (; src, infos, mi, rt, effects, slottypes) = cthulhu_info(f, tt; optimize);
+        (; src, infos, mi, rt, exct, effects, slottypes) = cthulhu_info(f, tt; optimize);
         for (debuginfo, iswarn, hide_type_stable, inline_cost, type_annotations) in Iterators.product(
             instances(Cthulhu.DInfo.DebugInfo), tf, tf, tf, tf,
         )
@@ -20,7 +20,7 @@ function generate_test_cases(f, tt, fname=string(nameof(f)))
 
             s = sprint(; context=:color=>true) do io
                 Cthulhu.cthulhu_typed(io, debuginfo,
-                                      src, rt, effects, mi;
+                                      src, rt, exct, effects, mi;
                                       iswarn, hide_type_stable, inline_cost, type_annotations)
             end
             s = strip_base_linenums(s)
diff --git a/test/irutils.jl b/test/irutils.jl
index 4387177..e20fd8d 100644
--- a/test/irutils.jl
+++ b/test/irutils.jl
@@ -1,14 +1,10 @@
 using Core: CodeInfo, ReturnNode, MethodInstance
-using Core.Compiler: IRCode, IncrementalCompact, singleton_type, VarState
+using Core.Compiler: IRCode, IncrementalCompact, singleton_type
 using Base.Meta: isexpr
 using InteractiveUtils: gen_call_with_extracted_types_and_kwargs
 
 argextype(@nospecialize args...) = Core.Compiler.argextype(args...)
-@static if VERSION >= v"1.10.0-DEV.556"
-    argextype(@nospecialize(x), src::CodeInfo) = argextype(x, src, VarState[])
-else
-    argextype(@nospecialize(x), src::CodeInfo) = argextype(x, src, Any[])
-end
+argextype(@nospecialize(x), src::CodeInfo) = argextype(x, src, Core.Compiler.VarState[])
 code_typed1(args...; kwargs...) = first(only(code_typed(args...; kwargs...)))::CodeInfo
 macro code_typed1(ex0...)
     return gen_call_with_extracted_types_and_kwargs(__module__, :code_typed1, ex0)
diff --git a/test/runtests.jl b/test/runtests.jl
index f32cada..f956e3e 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -9,7 +9,7 @@ using Test, PerformanceTestTools
         include("test_codeview.jl")
     end
 
-    # TODO enable this test on nightly
+    # TODO enable these tests
     if false
         @testset "test_irshow.jl" begin
             include("test_irshow.jl")
@@ -17,9 +17,12 @@ using Test, PerformanceTestTools
     else
         @info "skipped test_irshow.jl"
     end
-
-    @testset "test_terminal.jl" begin
-        include("test_terminal.jl")
+    if false
+        @testset "test_terminal.jl" begin
+            include("test_terminal.jl")
+        end
+    else
+        @info "skipped test_terminal.jl"
     end
 
     @testset "test_AbstractInterpreter.jl" begin
@@ -27,59 +30,64 @@ using Test, PerformanceTestTools
     end
 end
 
-module VSCodeServer
-    using TypedSyntax
+# TODO enable the VSCode-related tests
 
-    struct InlineDisplay
-        is_repl::Bool
-    end
-    const INLAY_HINTS_ENABLED = Ref(true)
-    const DIAGNOSTICS_ENABLED = Ref(true)
+# module VSCodeServer
+#     using TypedSyntax
 
-    inlay_hints = []
-    diagnostics = []
+#     struct InlineDisplay
+#         is_repl::Bool
+#     end
+#     const INLAY_HINTS_ENABLED = Ref(true)
+#     const DIAGNOSTICS_ENABLED = Ref(true)
 
-    function Base.display(d::InlineDisplay, x)
-        if x isa Dict{String, Vector{TypedSyntax.InlayHint}}
-            push!(inlay_hints, x)
-        elseif eltype(x) == TypedSyntax.Diagnostic
-            push!(diagnostics, x)
-        end
-        return nothing
-    end
+#     inlay_hints = []
+#     diagnostics = []
 
-    function reset_test_containers()
-        empty!(inlay_hints)
-        empty!(diagnostics)
-    end
-end
-module TestVSCodeExt # stops modules defined in test files from overwriting stuff from previous test
-using Test, PerformanceTestTools, ..VSCodeServer
-@testset "runtests.jl VSCodeExt" begin
-    @testset "test_Cthulhu.jl" begin
-        include("test_Cthulhu.jl")
-    end
+#     function Base.display(d::InlineDisplay, x)
+#         if x isa Dict{String, Vector{TypedSyntax.InlayHint}}
+#             push!(inlay_hints, x)
+#         elseif eltype(x) == TypedSyntax.Diagnostic
+#             push!(diagnostics, x)
+#         end
+#         return nothing
+#     end
 
-    @testset "test_codeview.jl" begin
-        include("test_codeview.jl")
-        include("test_codeview_vscode.jl")
-    end
+#     function reset_test_containers()
+#         empty!(inlay_hints)
+#         empty!(diagnostics)
+#     end
+# end
+# module TestVSCodeExt # stops modules defined in test files from overwriting stuff from previous test
+# using Test, PerformanceTestTools, ..VSCodeServer
+# @testset "runtests.jl VSCodeExt" begin
+#     @testset "test_Cthulhu.jl" begin
+#         include("test_Cthulhu.jl")
+#     end
 
-    # TODO enable this test on nightly
-    if false
-        @testset "test_irshow.jl" begin
-            include("test_irshow.jl")
-        end
-    else
-        @info "skipped test_irshow.jl"
-    end
+#     @testset "test_codeview.jl" begin
+#         include("test_codeview.jl")
+#         include("test_codeview_vscode.jl")
+#     end
 
-    @testset "test_terminal.jl" begin
-        include("test_terminal.jl")
-    end
+#     # TODO enable these tests
+#     if false
+#         @testset "test_irshow.jl" begin
+#             include("test_irshow.jl")
+#         end
+#     else
+#         @info "skipped test_irshow.jl"
+#     end
+#     if false
+#         @testset "test_terminal.jl" begin
+#             include("test_terminal.jl")
+#         end
+#     else
+#         @info "skipped test_terminal.jl"
+#     end
 
-    @testset "test_AbstractInterpreter.jl" begin
-        include("test_AbstractInterpreter.jl")
-    end
-end
-end
\ No newline at end of file
+#     @testset "test_AbstractInterpreter.jl" begin
+#         include("test_AbstractInterpreter.jl")
+#     end
+# end
+# end
diff --git a/test/setup.jl b/test/setup.jl
index 90d9c06..a2a80ae 100644
--- a/test/setup.jl
+++ b/test/setup.jl
@@ -3,13 +3,16 @@ if isdefined(parentmodule(@__MODULE__), :VSCodeServer)
     using ..VSCodeServer
 end
 
-function cthulhu_info(@nospecialize(f), @nospecialize(TT=()); optimize=true)
-    (interp, mi) = Cthulhu.mkinterp(Core.Compiler.NativeInterpreter(), f, TT)
-    (; src, rt, infos, slottypes, effects) = Cthulhu.lookup(interp, mi, optimize; allow_no_src=true)
+function cthulhu_info(@nospecialize(f), @nospecialize(tt=());
+                      optimize=true, interp=Core.Compiler.NativeInterpreter())
+    (interp, mi) = Cthulhu.mkinterp(f, tt; interp)
+    (; src, rt, exct, infos, slottypes, effects) =
+        Cthulhu.lookup(interp, mi, optimize; allow_no_src=true)
     if src !== nothing
-        src = Cthulhu.preprocess_ci!(src, mi, optimize, Cthulhu.CthulhuConfig(dead_code_elimination=true))
+        config = Cthulhu.CthulhuConfig(; dead_code_elimination=true)
+        src = Cthulhu.preprocess_ci!(src, mi, optimize, config)
     end
-    (; interp, src, infos, mi, rt, slottypes, effects)
+    return (; interp, src, infos, mi, rt, exct, slottypes, effects)
 end
 
 function find_callsites_by_ftt(@nospecialize(f), @nospecialize(TT=Tuple{}); optimize=true)
@@ -23,9 +26,3 @@ end
 macro find_callsites_by_ftt(ex0...)
     return InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :find_callsites_by_ftt, ex0)
 end
-
-@static if isdefined(Core.Compiler, :is_foldable_nothrow)
-    using Core.Compiler: is_foldable_nothrow
-else
-    const is_foldable_nothrow = Core.Compiler.is_total
-end
diff --git a/test/test_AbstractInterpreter.jl b/test/test_AbstractInterpreter.jl
index 0402494..ece9669 100644
--- a/test/test_AbstractInterpreter.jl
+++ b/test/test_AbstractInterpreter.jl
@@ -6,45 +6,94 @@ if isdefined(parentmodule(@__MODULE__), :VSCodeServer)
 end
 
 const CC = Core.Compiler
-import Core: MethodInstance, CodeInstance
-import .CC: WorldRange, WorldView, NativeInterpreter
-
-# define new `AbstractInterpreter` that satisfies the minimum interface requirements
-# while managing its cache independently
-macro newinterp(name)
-    cachename = gensym(string(name, "Cache"))
-    name = esc(name)
+
+@static if VERSION ≥ v"1.11.0-DEV.1552"
+macro newinterp(InterpName)
+    InterpCacheName = QuoteNode(Symbol(string(InterpName, "Cache")))
+    InterpName = esc(InterpName)
+    C = Core
+    CC = Core.Compiler
+    quote
+        struct $InterpName <: $CC.AbstractInterpreter
+            meta # additional information
+            world::UInt
+            inf_params::$CC.InferenceParams
+            opt_params::$CC.OptimizationParams
+            inf_cache::Vector{$CC.InferenceResult}
+            function $InterpName(meta = nothing;
+                                 world::UInt = Base.get_world_counter(),
+                                 inf_params::$CC.InferenceParams = $CC.InferenceParams(),
+                                 opt_params::$CC.OptimizationParams = $CC.OptimizationParams(),
+                                 inf_cache::Vector{$CC.InferenceResult} = $CC.InferenceResult[])
+                return new(meta, world, inf_params, opt_params, inf_cache)
+            end
+        end
+        $CC.InferenceParams(interp::$InterpName) = interp.inf_params
+        $CC.OptimizationParams(interp::$InterpName) = interp.opt_params
+        $CC.get_inference_world(interp::$InterpName) = interp.world
+        $CC.get_inference_cache(interp::$InterpName) = interp.inf_cache
+        $CC.cache_owner(::$InterpName) = $InterpCacheName
+    end
+end
+else
+macro newinterp(InterpName)
+    InterpCacheName = esc(Symbol(string(InterpName, "Cache")))
+    InterpName = esc(InterpName)
+    C = Core
+    CC = Core.Compiler
     quote
-        struct $cachename
-            dict::IdDict{MethodInstance,CodeInstance}
+        struct $InterpCacheName
+            dict::IdDict{$C.MethodInstance,$C.CodeInstance}
+        end
+        $InterpCacheName() = $InterpCacheName(IdDict{$C.MethodInstance,$C.CodeInstance}())
+        struct $InterpName <: $CC.AbstractInterpreter
+            meta # additional information
+            world::UInt
+            inf_params::$CC.InferenceParams
+            opt_params::$CC.OptimizationParams
+            inf_cache::Vector{$CC.InferenceResult}
+            code_cache::$InterpCacheName
+            function $InterpName(meta = nothing;
+                                 world::UInt = Base.get_world_counter(),
+                                 inf_params::$CC.InferenceParams = $CC.InferenceParams(),
+                                 opt_params::$CC.OptimizationParams = $CC.OptimizationParams(),
+                                 inf_cache::Vector{$CC.InferenceResult} = $CC.InferenceResult[],
+                                 code_cache::$InterpCacheName = $InterpCacheName())
+                return new(meta, world, inf_params, opt_params, inf_cache, code_cache)
+            end
         end
-        struct $name <: CC.AbstractInterpreter
-            interp::CC.NativeInterpreter
-            cache::$cachename
-            $name(world = Base.get_world_counter();
-                interp = CC.NativeInterpreter(world),
-                cache = $cachename(IdDict{MethodInstance,CodeInstance}())
-                ) = new(interp, cache)
+        $CC.InferenceParams(interp::$InterpName) = interp.inf_params
+        $CC.OptimizationParams(interp::$InterpName) = interp.opt_params
+        @static if VERSION ≥ v"1.11.0-DEV.1498"
+        CC.get_inference_world(interp::$InterpName) = interp.world
+        else
+        CC.get_world_counter(interp::$InterpName) = interp.world
         end
-        CC.InferenceParams(interp::$name) = CC.InferenceParams(interp.interp)
-        CC.OptimizationParams(interp::$name) = CC.OptimizationParams(interp.interp)
-        CC.get_world_counter(interp::$name) = CC.get_world_counter(interp.interp)
-        CC.get_inference_cache(interp::$name) = CC.get_inference_cache(interp.interp)
-        CC.code_cache(interp::$name) = WorldView(interp.cache, WorldRange(CC.get_world_counter(interp)))
-        CC.get(wvc::WorldView{<:$cachename}, mi::MethodInstance, default) = get(wvc.cache.dict, mi, default)
-        CC.getindex(wvc::WorldView{<:$cachename}, mi::MethodInstance) = getindex(wvc.cache.dict, mi)
-        CC.haskey(wvc::WorldView{<:$cachename}, mi::MethodInstance) = haskey(wvc.cache.dict, mi)
-        CC.setindex!(wvc::WorldView{<:$cachename}, ci::CodeInstance, mi::MethodInstance) = setindex!(wvc.cache.dict, ci, mi)
+        $CC.get_inference_cache(interp::$InterpName) = interp.inf_cache
+        $CC.code_cache(interp::$InterpName) = $CC.WorldView(interp.code_cache, $CC.WorldRange(interp.world))
+        $CC.get(wvc::$CC.WorldView{$InterpCacheName}, mi::$C.MethodInstance, default) = get(wvc.cache.dict, mi, default)
+        $CC.getindex(wvc::$CC.WorldView{$InterpCacheName}, mi::$C.MethodInstance) = getindex(wvc.cache.dict, mi)
+        $CC.haskey(wvc::$CC.WorldView{$InterpCacheName}, mi::$C.MethodInstance) = haskey(wvc.cache.dict, mi)
+        $CC.setindex!(wvc::$CC.WorldView{$InterpCacheName}, ci::$C.CodeInstance, mi::$C.MethodInstance) = setindex!(wvc.cache.dict, ci, mi)
     end
 end
+end # if VERSION ≥ v"1.11.0-DEV.1552"
+
+@doc """
+@newinterp NewInterpreter
+
+Defines new `NewInterpreter <: AbstractInterpreter` whose cache is separated
+from the native code cache, satisfying the minimum interface requirements.
+""" var"@newinterp"
 
 # `OverlayMethodTable`
 # --------------------
 import Base.Experimental: @MethodTable, @overlay
 
 @newinterp MTOverlayInterp
-@MethodTable(OverlayedMT)
-CC.method_table(interp::MTOverlayInterp) = CC.OverlayMethodTable(CC.get_world_counter(interp), OverlayedMT)
+@MethodTable OverlayedMT
+CC.method_table(interp::MTOverlayInterp) =
+    CC.OverlayMethodTable(Cthulhu.get_inference_world(interp), OverlayedMT)
 @overlay OverlayedMT sin(x::Float64) = 1
 
 @testset "OverlayMethodTable integration" begin
diff --git a/test/test_Cthulhu.jl b/test/test_Cthulhu.jl
index 6eaccc2..f0b52db 100644
--- a/test/test_Cthulhu.jl
+++ b/test/test_Cthulhu.jl
@@ -52,7 +52,7 @@ end
     # Callsite handling in source-view mode: for kwarg functions, strip the body, and use "typed" callsites
     for m in (@which(anykwargs("animals")), @which(anykwargs("animals"; cat=1, dog=2)))
         mi = first_specialization(m)
-        src, rt = Cthulhu.TypedSyntax.getsrc(mi)
+        src, rt = Cthulhu.TypedSyntax.code_typed1_tsn(mi)
         tsn, mappings = Cthulhu.get_typed_sourcetext(mi, src, rt; warn=false)
         str = sprint(printstyled, tsn)
         @test occursin("anykwargs", str) && occursin("kwargs...", str) && !occursin("println", str)
@@ -61,7 +61,7 @@ end
     # Likewise for methods that fill in default positional arguments
     m = @which hasdefaultargs(1)
     mi = first_specialization(m)
-    src, rt = Cthulhu.TypedSyntax.getsrc(mi)
+    src, rt = Cthulhu.TypedSyntax.code_typed1_tsn(mi)
     tsn, mappings = Cthulhu.get_typed_sourcetext(mi, src, rt; warn=false)
     str = sprint(printstyled, tsn)
     @test occursin("hasdefaultargs(a, b=2)", str)
@@ -69,7 +69,7 @@ end
     @test isempty(mappings)
     m = @which hasdefaultargs(1, 5)
     mi = first_specialization(m)
-    src, rt = Cthulhu.TypedSyntax.getsrc(mi)
+    src, rt = Cthulhu.TypedSyntax.code_typed1_tsn(mi)
     tsn, mappings = Cthulhu.get_typed_sourcetext(mi, src, rt; warn=false)
     str = sprint(printstyled, tsn)
     @test occursin("hasdefaultargs(a, b=2)", str)
@@ -83,16 +83,46 @@ end
     calltwice(c) = twice(c[1])
 
     callsites = find_callsites_by_ftt(calltwice, Tuple{Vector{Float64}})
-    @test length(callsites) == 1 && callsites[1].head === :invoke
-    io = IOBuffer()
-    print(io, callsites[1])
-    @test occursin("invoke twice(::Float64)::Float64", String(take!(io)))
+    @static if VERSION ≥ v"1.11.0-DEV.753"
+        @test any(callsites) do callsite
+            callsite.head === :invoke || return false
+            io = IOBuffer()
+            print(io, callsite)
+            return occursin("invoke twice(::Float64)::Float64", String(take!(io)))
+        end
+        @test any(callsites) do callsite
+            callsite.head === :invoke || return false
+            io = IOBuffer()
+            print(io, callsite)
+            return occursin("invoke throw_boundserror", String(take!(io)))
+        end
+    else
+        @test length(callsites) == 1 && callsites[1].head === :invoke
+        io = IOBuffer()
+        print(io, callsites[1])
+        @test occursin("invoke twice(::Float64)::Float64", String(take!(io)))
+    end
 
     callsites = find_callsites_by_ftt(calltwice, Tuple{Vector{AbstractFloat}})
-    @test length(callsites) == 1 && callsites[1].head === :call
-    io = IOBuffer()
-    print(io, callsites[1])
-    @test occursin("call twice(::AbstractFloat)", String(take!(io)))
+    @static if VERSION ≥ v"1.11.0-DEV.753"
+        @test any(callsites) do callsite
+            callsite.head === :call || return false
+            io = IOBuffer()
+            print(io, callsite)
+            return occursin("call twice(::AbstractFloat)", String(take!(io)))
+        end
+        @test any(callsites) do callsite
+            callsite.head === :invoke || return false
+            io = IOBuffer()
+            print(io, callsite)
+            return occursin("invoke throw_boundserror", String(take!(io)))
+        end
+    else
+        @test length(callsites) == 1 && callsites[1].head === :call
+        io = IOBuffer()
+        print(io, callsites[1])
+        @test occursin("call twice(::AbstractFloat)", String(take!(io)))
+    end
 
     # Note the failure of `callinfo` to properly handle specialization
     @test_broken Cthulhu.callinfo(Tuple{typeof(twice), AbstractFloat}, AbstractFloat) isa Cthulhu.MultiCallInfo
@@ -130,28 +160,33 @@ let callsites = find_callsites_by_ftt(call_by_apply, Tuple{Tuple{Int}}; optimize
     @test length(callsites) == 1
 end
 
+Base.@propagate_inbounds _boundscheck_dce(x) = @boundscheck error()
+boundscheck_dce_inbounds(x) = @inbounds _boundscheck_dce(x)
+boundscheck_dce(x) = _boundscheck_dce(x)
+
 @testset "DCE & boundscheck" begin
-    M = Module()
-    @eval M begin
-        Base.@propagate_inbounds function f(x)
-            @boundscheck error()
+    @static if VERSION ≥ v"1.11.0-DEV.377"
+        # no boundscheck elimination on Julia-level compilation
+        for f in (boundscheck_dce_inbounds, boundscheck_dce)
+            let (; src) = cthulhu_info(f, Tuple{Vector{Float64}})
+                @test count(src.stmts.stmt) do stmt
+                    isexpr(stmt, :boundscheck)
+                end == 1
+            end
         end
-        g(x) = @inbounds f(x)
-        h(x) = f(x)
-    end
-
-    let (; src) = cthulhu_info(M.g, Tuple{Vector{Float64}})
-        stmts = @static VERSION < v"1.11.0-DEV.258" ? src.stmts.inst : src.stmts.stmt
-        @test all(stmts) do stmt
-            isa(stmt, Core.GotoNode) || (isa(stmt, Core.ReturnNode) && isdefined(stmt, :val))
+    else
+        let (; src) = cthulhu_info(boundscheck_dce_inbounds, Tuple{Vector{Float64}})
+            stmts = @static VERSION < v"1.11.0-DEV.258" ? src.stmts.inst : src.stmts.stmt
+            @test all(stmts) do stmt
+                isa(stmt, Core.GotoNode) || (isa(stmt, Core.ReturnNode) && isdefined(stmt, :val))
+            end
+        end
+        let (; src) = cthulhu_info(boundscheck_dce, Tuple{Vector{Float64}})
+            stmts = @static VERSION < v"1.11.0-DEV.258" ? src.stmts.inst : src.stmts.stmt
+            @test count(!isnothing, stmts) == 2
+            stmt = stmts[end]
+            @test isa(stmt, Core.ReturnNode) && !isdefined(stmt, :val)
         end
-    end
-
-    let (; src) = cthulhu_info(M.h, Tuple{Vector{Float64}})
-        stmts = @static VERSION < v"1.11.0-DEV.258" ? src.stmts.inst : src.stmts.stmt
-        @test count(!isnothing, stmts) == 2
-        stmt = stmts[end]
-        @test isa(stmt, Core.ReturnNode) && !isdefined(stmt, :val)
     end
 end
 
@@ -169,32 +204,30 @@ let callsites = find_callsites_by_ftt(f_matches, Tuple{Any, Any}; optimize=false
     @test occursin(r"→ g_matches\(::Any, ?::Any\)::Union{Float64, ?Int\d+}", String(take!(io)))
 end
 
-@testset "wrapped callinfo" begin
-    let
-        m = Module()
-        @eval m begin
-            # mutually recursive functions
-            f(a) = g(a)
-            g(a) = somecode::Bool ? h(a) : a
-            h(a) = f(Type{a})
-        end
+uncached_call1(a) = uncached_call2(a)
+uncached_call2(a) = somecode::Bool ? uncached_call3(a) : a
+uncached_call3(a) = uncached_call1(Type{a})
 
-        # make sure we form `UncachedCallInfo` so that we won't try to retrieve non-existing cache
-        callsites = @find_callsites_by_ftt m.f(Int)
-        @test length(callsites) == 1
-        ci = first(callsites).info
-        @test isa(ci, Cthulhu.UncachedCallInfo)
-        effects = Cthulhu.get_effects(ci)
-        @test !Core.Compiler.is_consistent(effects)
+@testset "wrapped callinfo" begin
+    # make sure we form `UncachedCallInfo` so that we won't try to retrieve non-existing cache
+    callsites = @find_callsites_by_ftt uncached_call1(Int)
+    @test length(callsites) == 1
+    ci = first(callsites).info
+    @test isa(ci, Cthulhu.UncachedCallInfo)
+    effects = Cthulhu.get_effects(ci)
+    @test !Core.Compiler.is_consistent(effects)
+    @static if VERSION ≥ v"1.11.0-DEV.392"
+        @test !Core.Compiler.is_effect_free(effects)
+    else
         @test Core.Compiler.is_effect_free(effects)
-        @test !Core.Compiler.is_nothrow(effects)
-        @test !Core.Compiler.is_terminates(effects)
-        @test Cthulhu.is_callsite(ci, ci.wrapped.mi)
-        io = IOBuffer()
-        show(io, first(callsites))
-        @test occursin("< uncached >", String(take!(io)))
-        # TODO do some test with `LimitedCallInfo`, but they happen at deeper callsites
     end
+    @test !Core.Compiler.is_nothrow(effects)
+    @test !Core.Compiler.is_terminates(effects)
+    @test Cthulhu.is_callsite(ci, ci.wrapped.mi)
+    io = IOBuffer()
+    show(io, first(callsites))
+    @test occursin("< uncached >", String(take!(io)))
+    # TODO do some test with `LimitedCallInfo`, but they happen at deeper callsites
 end
 
 @testset "ConstPropCallInfo" begin
@@ -235,12 +268,12 @@ end
                 end
                 t[1]
             end
-            @test length(callsites) == 1                                        # getindex(::Union{Vector{Any}, Const(tuple(1,nothing))}, ::Const(1))
+            @test length(callsites) == 1 # getindex(::Union{Vector{Any}, Const(tuple(1,nothing))}, ::Const(1))
             callinfo = callsites[1].info
             @test isa(callinfo, Cthulhu.MultiCallInfo)
             callinfos = callinfo.callinfos
             @test length(callinfos) == 2
-            @test count(ci->isa(ci, Cthulhu.MICallInfo), callinfos) == 1        # getindex(::Vector{Any}, ::Const(1))
+            @test count(ci->isa(ci, Cthulhu.MICallInfo), callinfos) == 1 # getindex(::Vector{Any}, ::Const(1))
             @test count(ci->isa(ci, Cthulhu.ConstPropCallInfo) || isa(ci, Cthulhu.SemiConcreteCallInfo), callinfos) == 1 # getindex(::Const(tuple(1,nothing)), ::Const(1))
         end
 
@@ -300,7 +333,7 @@ let # check the performance benefit of semi concrete evaluation
         out
     end
 end
-@static VERSION ≥ v"1.9-" && @testset "SemiConcreteResult" begin
+@testset "SemiConcreteResult" begin
     # constant prop' on all the splits
     let callsites = find_callsites_by_ftt((Int,); optimize = false) do x
             semi_concrete_eval(42, x)
@@ -319,16 +352,12 @@ function bar346(x::ComplexF64)
     x = ComplexF64(x.re, 1.0)
     return sin(x.im)
 end
-@static VERSION >= v"1.10-" && @testset "issue #346" begin
+@testset "issue #346" begin
     let (; interp, src, infos, mi, slottypes) = cthulhu_info(bar346, Tuple{ComplexF64}; optimize=false)
         callsites, _ = Cthulhu.find_callsites(interp, src, infos, mi, slottypes, false)
         @test isa(callsites[1].info, Cthulhu.SemiConcreteCallInfo)
         @test occursin("= < semi-concrete eval > getproperty(::ComplexF64,::Core.Const(:re))::Float64", string(callsites[1]))
-
         @test Cthulhu.get_rt(callsites[end].info) == Core.Const(sin(1.0))
-
-        @test Cthulhu.get_remarks(interp, callsites[1].info) == Cthulhu.PC2Remarks()
-        @test Cthulhu.get_effects(interp, callsites[1].info) == Cthulhu.PC2Effects()
     end
 end
 
@@ -344,8 +373,8 @@ struct SingletonPureCallable{N} end
     @test occursin("SingletonPureCallable{1}()(::Float64)::Float64", s)
 
 
-    @test Cthulhu.get_effects(c1) |> is_foldable_nothrow
-    @test Cthulhu.get_effects(c2) |> is_foldable_nothrow
+    @test Cthulhu.get_effects(c1) |> Core.Compiler.is_foldable_nothrow
+    @test Cthulhu.get_effects(c2) |> Core.Compiler.is_foldable_nothrow
 end
 
 @testset "ReturnTypeCallInfo" begin
@@ -377,8 +406,8 @@ end
     print(io, callsites[2])
     @test occursin("return_type < only_ints(::Float64)::Union{} >", String(take!(io)))
 
-    @test Cthulhu.get_effects(callinfo1) |> is_foldable_nothrow
-    @test Cthulhu.get_effects(callinfo2) |> is_foldable_nothrow
+    @test Cthulhu.get_effects(callinfo1) |> Core.Compiler.is_foldable_nothrow
+    @test Cthulhu.get_effects(callinfo2) |> Core.Compiler.is_foldable_nothrow
 end
 
 @testset "OCCallInfo" begin
@@ -389,7 +418,7 @@ end
         @test length(callsites) == 1
         callinfo = only(callsites).info
         @test callinfo isa Cthulhu.OCCallInfo
-        @test Cthulhu.get_effects(callinfo) |> !is_foldable_nothrow
+        @test Cthulhu.get_effects(callinfo) |> !Core.Compiler.is_foldable_nothrow
         # TODO not sure what these effects are (and neither is Base.infer_effects yet)
         @test callinfo.ci.rt === Base.return_types((Int,Int)) do a, b
             sin(a) + cos(b)
@@ -447,7 +476,7 @@ invoke_constcall(a::Number, c::Bool) = c ? Number : :number
         callsite = only(callsites)
         info = callsite.info
         @test isa(info, Cthulhu.InvokeCallInfo)
-        @test Cthulhu.get_effects(info) |> is_foldable_nothrow
+        @test Cthulhu.get_effects(info) |> Core.Compiler.is_foldable_nothrow
         rt = Core.Compiler.Const(:Integer)
         @test info.ci.rt === rt
         buf = IOBuffer()
@@ -460,7 +489,7 @@ invoke_constcall(a::Number, c::Bool) = c ? Number : :number
         callsite = only(callsites)
         info = callsite.info
         @test isa(info, Cthulhu.InvokeCallInfo)
-        @test Cthulhu.get_effects(info) |> is_foldable_nothrow
+        @test Cthulhu.get_effects(info) |> Core.Compiler.is_foldable_nothrow
         @test info.ci.rt === Core.Compiler.Const(:Int)
     end
 
@@ -471,13 +500,13 @@ invoke_constcall(a::Number, c::Bool) = c ? Number : :number
         callsite = only(callsites)
         info = callsite.info
         @test isa(info, Cthulhu.InvokeCallInfo)
-        @test Cthulhu.get_effects(info) |> is_foldable_nothrow
+        @test Cthulhu.get_effects(info) |> Core.Compiler.is_foldable_nothrow
         inner = info.ci
         rt = Core.Compiler.Const(Any)
         @test Cthulhu.get_rt(info) === rt
         buf = IOBuffer()
         show(buf, callsite)
-        @static VERSION ≥ v"1.9-" && @test isa(inner, Cthulhu.SemiConcreteCallInfo)
+        @test isa(inner, Cthulhu.SemiConcreteCallInfo)
         @test occursin("= invoke < invoke_constcall(::Any,::$(Core.Compiler.Const(true)))::$rt", String(take!(buf)))
     end
 end
@@ -644,9 +673,9 @@ end
         end
     end
     function doprint(f)
-        (; src, mi, rt, effects) = cthulhu_info(f)
+        (; src, mi, rt, exct, effects) = cthulhu_info(f)
         io = IOBuffer()
-        Cthulhu.cthulhu_typed(io, :none, src, rt, effects, mi; iswarn=false)
+        Cthulhu.cthulhu_typed(io, :none, src, rt, exct, effects, mi; iswarn=false)
         return String(take!(io))
     end
     @test occursin("invoke f1()::…\n", doprint(getfield(m, :f1)))
@@ -676,55 +705,55 @@ end
     @test isa(mi, Core.MethodInstance)
 end
 
-## Functions for "backedges & treelist"
-# The printing changes when the functions are defined inside the testset
-fbackedge1() = 1
-fbackedge2(x) = x > 0 ? fbackedge1() : -fbackedge1()
-fst1(x) = backtrace()
-@inline fst2(x) = fst1(x)
-@noinline fst3(x) = fst2(x)
-@inline fst4(x) = fst3(x)
-fst5(x) = fst4(x)
-
-@testset "backedges and treelist" begin
-    @test fbackedge2(0.2) == 1
-    @test fbackedge2(-0.2) == -1
-    mi = first_specialization(@which(fbackedge1()))
-    root = Cthulhu.treelist(mi)
-    @test Cthulhu.count_open_leaves(root) == 2
-    @test root.data.callstr == "fbackedge1()"
-    @test root.children[1].data.callstr == " fbackedge2(::Float64)"
-
-    # issue #114
-    unspecva(@nospecialize(i::Int...)) = 1
-    @test unspecva(1, 2) == 1
-    mi = first_specialization(only(methods(unspecva)))
-    root = Cthulhu.treelist(mi)
-    @test occursin("Vararg", root.data.callstr)
-
-    # Test highlighting and other printing
-    mi = Cthulhu.get_specialization(:, Tuple{T, T} where T<:Integer)
-    root = Cthulhu.treelist(mi)
-    @test occursin("\e[31m::T\e[39m", root.data.callstr)
-    mi = Cthulhu.get_specialization(Vector{Int}, Tuple{typeof(undef), Int})
-    io = IOBuffer()
-    @test Cthulhu.callstring(io, mi) == "Vector{$Int}(::UndefInitializer, ::$Int)"
-    mi = Cthulhu.get_specialization(similar, Tuple{Type{Vector{T}}, Dims{1}} where T)
-    @test occursin(r"31m::Type", Cthulhu.callstring(io, mi))
-
-    # treelist for stacktraces
-    tree = Cthulhu.treelist(fst5(1.0))
-    @test match(r"fst1 at .*:\d+ => fst2 at .*:\d+ => fst3\(::Float64\) at .*:\d+", tree.data.callstr) !== nothing
-    @test length(tree.children) == 1
-    child = tree.children[1]
-    @test match(r" fst4 at .*:\d+ => fst5\(::Float64\) at .*:\d+", child.data.callstr) !== nothing
-
-    # issue #184
-    tree = Cthulhu.treelist(similar(fst5(1.0), 0))
-    @test isempty(tree.data.callstr)
-    @test isempty(Cthulhu.callstring(io, similar(stacktrace(fst5(1.0)), 0)))
-    @test Cthulhu.instance(similar(stacktrace(fst5(1.0)), 0)) === Core.Compiler.Timings.ROOTmi
-end
+# ## Functions for "backedges & treelist"
+# # The printing changes when the functions are defined inside the testset
+# fbackedge1() = 1
+# fbackedge2(x) = x > 0 ? fbackedge1() : -fbackedge1()
+# fst1(x) = backtrace()
+# @inline fst2(x) = fst1(x)
+# @noinline fst3(x) = fst2(x)
+# @inline fst4(x) = fst3(x)
+# fst5(x) = fst4(x)
+
+# @testset "backedges and treelist" begin
+#     @test fbackedge2(0.2) == 1
+#     @test fbackedge2(-0.2) == -1
+#     mi = first_specialization(@which(fbackedge1()))
+#     root = Cthulhu.treelist(mi)
+#     @test Cthulhu.count_open_leaves(root) == 2
+#     @test root.data.callstr == "fbackedge1()"
+#     @test root.children[1].data.callstr == " fbackedge2(::Float64)"
+
+#     # issue #114
+#     unspecva(@nospecialize(i::Int...)) = 1
+#     @test unspecva(1, 2) == 1
+#     mi = first_specialization(only(methods(unspecva)))
+#     root = Cthulhu.treelist(mi)
+#     @test occursin("Vararg", root.data.callstr)
+
+#     # Test highlighting and other printing
+#     mi = Cthulhu.get_specialization(:, Tuple{T, T} where T<:Integer)
+#     root = Cthulhu.treelist(mi)
+#     @test occursin("\e[31m::T\e[39m", root.data.callstr)
+#     mi = Cthulhu.get_specialization(Vector{Int}, Tuple{typeof(undef), Int})
+#     io = IOBuffer()
+#     @test Cthulhu.callstring(io, mi) == "Vector{$Int}(::UndefInitializer, ::$Int)"
+#     mi = Cthulhu.get_specialization(similar, Tuple{Type{Vector{T}}, Dims{1}} where T)
+#     @test occursin(r"31m::Type", Cthulhu.callstring(io, mi))
+
+#     # treelist for stacktraces
+#     tree = Cthulhu.treelist(fst5(1.0))
+#     @test match(r"fst1 at .*:\d+ => fst2 at .*:\d+ => fst3\(::Float64\) at .*:\d+", tree.data.callstr) !== nothing
+#     @test length(tree.children) == 1
+#     child = tree.children[1]
+#     @test match(r" fst4 at .*:\d+ => fst5\(::Float64\) at .*:\d+", child.data.callstr) !== nothing
+
+#     # issue #184
+#     tree = Cthulhu.treelist(similar(fst5(1.0), 0))
+#     @test isempty(tree.data.callstr)
+#     @test isempty(Cthulhu.callstring(io, similar(stacktrace(fst5(1.0)), 0)))
+#     @test Cthulhu.instance(similar(stacktrace(fst5(1.0)), 0)) === Core.Compiler.Timings.ROOTmi
+# end
 
 @testset "ascend" begin
     # This tests only the non-interactive "look up the caller" portion
@@ -785,7 +814,6 @@ end
     @test lines == [line2]
 end
 
-
 @testset "ascend interface" begin
     m = Module()
     @eval m begin
@@ -943,7 +971,7 @@ function effects_dced(x)
     n = Core.arraysize(a, 1)
     return a, n
 end
-@static VERSION ≥ v"1.9-" && @testset "per-statement effects" begin
+@testset "per-statement effects" begin
     interp, mi = Cthulhu.mkinterp(effects_dced, (Int,));
     src = interp.unopt[mi].src
     i1 = only(findall(iscall((src, isa)), src.code))
@@ -958,7 +986,7 @@ end
     @test haskey(pc2effects, i4)
 end
 
-@static VERSION ≥ v"1.9-" && @testset "Bare-bones MIs" begin
+@testset "Bare-bones MIs" begin
     # Get IR for a function, wrap it in a minimal methodinstance
     (ir, rt) = only(Base.code_ircode(sqrt, (Float64,)))
     mi = ccall(:jl_new_method_instance_uninit, Ref{Core.MethodInstance}, ());
@@ -982,4 +1010,14 @@ end
     @test String(take!(io)) == ":toplevel(::Float64)::Float64"
 end
 
+@inline countvars50037(bitflags::Int, var::Int) = bitflags >> 0
+let (interp, mi) = Cthulhu.mkinterp((Int,)) do var::Int
+        countvars50037(1, var)
+    end
+    key = only(Base.specializations(only(methods(countvars50037))))
+    codeinst = interp.opt[key]
+    inferred = @atomic :monotonic codeinst.inferred
+    @test length(inferred.ir.cfg.blocks) == 1
+end
+
 end # module test_Cthulhu
diff --git a/test/test_codeview.jl b/test/test_codeview.jl
index 0995728..1c95b63 100644
--- a/test/test_codeview.jl
+++ b/test/test_codeview.jl
@@ -10,7 +10,7 @@ using .TestCodeViewSandbox
 Revise.track(TestCodeViewSandbox, normpath(@__DIR__, "TestCodeViewSandbox.jl"))
 
 @testset "printer test" begin
-    (; interp, src, infos, mi, rt, effects, slottypes) = cthulhu_info(testf_revise);
+    (; interp, src, infos, mi, rt, exct, effects, slottypes) = cthulhu_info(testf_revise);
     tf = (true, false)
 
     @testset "codeview: $codeview" for codeview in Cthulhu.CODEVIEWS
@@ -22,7 +22,7 @@ Revise.track(TestCodeViewSandbox, normpath(@__DIR__, "TestCodeViewSandbox.jl"))
                 config = Cthulhu.CONFIG
 
                 io = IOBuffer()
-                codeview(io, mi, optimize, debuginfo, interp, config)
+                codeview(io, mi, optimize, debuginfo, Cthulhu.get_inference_world(interp), config)
                 @test !isempty(String(take!(io))) # just check it works
             end
         end
@@ -35,7 +35,7 @@ Revise.track(TestCodeViewSandbox, normpath(@__DIR__, "TestCodeViewSandbox.jl"))
                     @testset "type_annotations: $type_annotations" for type_annotations in tf
                         io = IOBuffer()
                         Cthulhu.cthulhu_typed(io, debuginfo,
-                            src, rt, effects, mi;
+                            src, rt, exct, effects, mi;
                             iswarn, hide_type_stable, inline_cost, type_annotations)
                         @test !isempty(String(take!(io))) # just check it works
                     end
@@ -47,7 +47,7 @@ end
 
 @testset "hide type-stable statements" begin
     let # optimize code
-        (; src, infos, mi, rt, effects, slottypes) = @eval Module() begin
+        (; src, infos, mi, rt, exct, effects, slottypes) = @eval Module() begin
             const globalvar = Ref(42)
             $cthulhu_info() do
                 a = sin(globalvar[])
@@ -57,7 +57,7 @@ end
         end
         function prints(; kwargs...)
             io = IOBuffer()
-            Cthulhu.cthulhu_typed(io, :none, src, rt, effects, mi; kwargs...)
+            Cthulhu.cthulhu_typed(io, :none, src, rt, exct, effects, mi; kwargs...)
             return String(take!(io))
         end
 
@@ -74,7 +74,7 @@ end
     end
 
     let # unoptimize code
-        (; src, infos, mi, rt, effects, slottypes) = @eval Module() begin
+        (; src, infos, mi, rt, exct, effects, slottypes) = @eval Module() begin
             const globalvar = Ref(42)
             $cthulhu_info(; optimize=false) do
                 a = sin(globalvar[])
@@ -84,7 +84,7 @@ end
         end
         function prints(; kwargs...)
             io = IOBuffer()
-            Cthulhu.cthulhu_typed(io, :none, src, rt, effects, mi; kwargs...)
+            Cthulhu.cthulhu_typed(io, :none, src, rt, exct, effects, mi; kwargs...)
             return String(take!(io))
         end
 
diff --git a/test/test_codeview_vscode.jl b/test/test_codeview_vscode.jl
index c90033d..8ef8797 100644
--- a/test/test_codeview_vscode.jl
+++ b/test/test_codeview_vscode.jl
@@ -36,7 +36,7 @@ include("test_vscode_example_functions.jl")
 
             fake_terminal() do term, in, out, _
                 t = @async begin
-                    @test_nowarn descend(fib, (Int64,); terminal=term, iswarn, hide_type_stable, inlay_types_vscode, diagnostics_vscode)
+                    @test_nowarn descend(fib, (Int,); terminal=term, iswarn, hide_type_stable, inlay_types_vscode, diagnostics_vscode)
                 end
                 write(in, 'q')
                 wait(t)
@@ -60,18 +60,18 @@ include("test_vscode_example_functions.jl")
 
             if !hide_type_stable && inlay_types_vscode
                 @test equal_upto_ordering(first(values(VSCodeServer.inlay_hints[1])), [
-                    TypedSyntax.InlayHint(1, 14, "::Int64", 1)
-                    TypedSyntax.InlayHint(1, 15, "::Int64", 1)
+                    TypedSyntax.InlayHint(1, 14, "::$Int", 1)
+                    TypedSyntax.InlayHint(1, 15, "::$Int", 1)
                     TypedSyntax.InlayHint(3, 11, "(", 1)
                     TypedSyntax.InlayHint(3, 15, "(", 1)
-                    TypedSyntax.InlayHint(3, 16, "::Int64", 1)
-                    TypedSyntax.InlayHint(3, 20, ")::Int64", 1)
-                    TypedSyntax.InlayHint(3, 21, "::Int64", 1)
+                    TypedSyntax.InlayHint(3, 16, "::$Int", 1)
+                    TypedSyntax.InlayHint(3, 20, ")::$Int", 1)
+                    TypedSyntax.InlayHint(3, 21, "::$Int", 1)
                     TypedSyntax.InlayHint(3, 28, "(", 1)
-                    TypedSyntax.InlayHint(3, 29, "::Int64", 1)
-                    TypedSyntax.InlayHint(3, 33, ")::Int64", 1)
-                    TypedSyntax.InlayHint(3, 34, "::Int64", 1)
-                    TypedSyntax.InlayHint(3, 34, ")::Int64", 1)
+                    TypedSyntax.InlayHint(3, 29, "::$Int", 1)
+                    TypedSyntax.InlayHint(3, 33, ")::$Int", 1)
+                    TypedSyntax.InlayHint(3, 34, "::$Int", 1)
+                    TypedSyntax.InlayHint(3, 34, ")::$Int", 1)
                 ])
             elseif hide_type_stable && inlay_types_vscode
                 @test isempty(VSCodeServer.inlay_hints[1])
@@ -85,7 +85,7 @@ include("test_vscode_example_functions.jl")
 
             fake_terminal() do term, in, out, _
                 t = @async begin
-                    @test_nowarn descend(fVSCode, (Int64,); terminal=term, iswarn, hide_type_stable, inlay_types_vscode, diagnostics_vscode)
+                    @test_nowarn descend(fVSCode, (Int,); terminal=term, iswarn, hide_type_stable, inlay_types_vscode, diagnostics_vscode)
                 end
                 write(in, 'q')
                 wait(t)
@@ -114,60 +114,60 @@ include("test_vscode_example_functions.jl")
             if !hide_type_stable && inlay_types_vscode
                 if iswarn
                     @test equal_upto_ordering(first(values(VSCodeServer.inlay_hints[1])), [
-                        TypedSyntax.InlayHint(7, 18, "::Int64", 1)
-                        TypedSyntax.InlayHint(7, 19, "::Union{Float64, Int64}", nothing)
-                        TypedSyntax.InlayHint(8, 5, "::Int64", 1)
+                        TypedSyntax.InlayHint(7, 18, "::$Int", 1)
+                        TypedSyntax.InlayHint(7, 19, "::Union{Float64, $Int}", nothing)
+                        TypedSyntax.InlayHint(8, 5, "::$Int", 1)
                         TypedSyntax.InlayHint(8, 8, "(", 1)
-                        TypedSyntax.InlayHint(8, 9, "::Int64", 1)
-                        TypedSyntax.InlayHint(8, 13, ")::Int64", 1)
-                        TypedSyntax.InlayHint(9, 5, "::Int64", 1)
+                        TypedSyntax.InlayHint(8, 9, "::$Int", 1)
+                        TypedSyntax.InlayHint(8, 13, ")::$Int", 1)
+                        TypedSyntax.InlayHint(9, 5, "::$Int", 1)
                         TypedSyntax.InlayHint(9, 8, "(", 1)
-                        TypedSyntax.InlayHint(9, 13, "::Int64", 1)
-                        TypedSyntax.InlayHint(9, 13, ")::Int64", 1)
+                        TypedSyntax.InlayHint(9, 13, "::$Int", 1)
+                        TypedSyntax.InlayHint(9, 13, ")::$Int", 1)
                         TypedSyntax.InlayHint(10, 11, "(", 1)
-                        TypedSyntax.InlayHint(10, 12, "::Int64", 1)
+                        TypedSyntax.InlayHint(10, 12, "::$Int", 1)
                         TypedSyntax.InlayHint(10, 16, "(", 1)
-                        TypedSyntax.InlayHint(10, 17, "::Int64", 1)
+                        TypedSyntax.InlayHint(10, 17, "::$Int", 1)
                         TypedSyntax.InlayHint(10, 21, ")::Bool", 1)
-                        TypedSyntax.InlayHint(10, 26, "::Int64", 1)
+                        TypedSyntax.InlayHint(10, 26, "::$Int", 1)
                         TypedSyntax.InlayHint(10, 32, "::Float64", 1)
-                        TypedSyntax.InlayHint(10, 33, ")::Union{Float64, Int64}", nothing)
+                        TypedSyntax.InlayHint(10, 33, ")::Union{Float64, $Int}", nothing)
                     ])
 
                 else
                     @test equal_upto_ordering(first(values(VSCodeServer.inlay_hints[1])), [
-                        TypedSyntax.InlayHint(7, 18, "::Int64", 1)
-                        TypedSyntax.InlayHint(7, 19, "::Union{Float64, Int64}", 1)
-                        TypedSyntax.InlayHint(8, 5, "::Int64", 1)
+                        TypedSyntax.InlayHint(7, 18, "::$Int", 1)
+                        TypedSyntax.InlayHint(7, 19, "::Union{Float64, $Int}", 1)
+                        TypedSyntax.InlayHint(8, 5, "::$Int", 1)
                         TypedSyntax.InlayHint(8, 8, "(", 1)
-                        TypedSyntax.InlayHint(8, 9, "::Int64", 1)
-                        TypedSyntax.InlayHint(8, 13, ")::Int64", 1)
-                        TypedSyntax.InlayHint(9, 5, "::Int64", 1)
+                        TypedSyntax.InlayHint(8, 9, "::$Int", 1)
+                        TypedSyntax.InlayHint(8, 13, ")::$Int", 1)
+                        TypedSyntax.InlayHint(9, 5, "::$Int", 1)
                         TypedSyntax.InlayHint(9, 8, "(", 1)
-                        TypedSyntax.InlayHint(9, 13, "::Int64", 1)
-                        TypedSyntax.InlayHint(9, 13, ")::Int64", 1)
+                        TypedSyntax.InlayHint(9, 13, "::$Int", 1)
+                        TypedSyntax.InlayHint(9, 13, ")::$Int", 1)
                         TypedSyntax.InlayHint(10, 11, "(", 1)
-                        TypedSyntax.InlayHint(10, 12, "::Int64", 1)
+                        TypedSyntax.InlayHint(10, 12, "::$Int", 1)
                         TypedSyntax.InlayHint(10, 16, "(", 1)
-                        TypedSyntax.InlayHint(10, 17, "::Int64", 1)
+                        TypedSyntax.InlayHint(10, 17, "::$Int", 1)
                         TypedSyntax.InlayHint(10, 21, ")::Bool", 1)
-                        TypedSyntax.InlayHint(10, 26, "::Int64", 1)
+                        TypedSyntax.InlayHint(10, 26, "::$Int", 1)
                         TypedSyntax.InlayHint(10, 32, "::Float64", 1)
-                        TypedSyntax.InlayHint(10, 33, ")::Union{Float64, Int64}", 1)
+                        TypedSyntax.InlayHint(10, 33, ")::Union{Float64, $Int}", 1)
                     ])
                 end
             elseif hide_type_stable && inlay_types_vscode
                 if iswarn
                     @test equal_upto_ordering(first(values(VSCodeServer.inlay_hints[1])), [
-                        TypedSyntax.InlayHint(7, 19, "::Union{Float64, Int64}", nothing)
+                        TypedSyntax.InlayHint(7, 19, "::Union{Float64, $Int}", nothing)
                         TypedSyntax.InlayHint(10, 11, "(", 1)
-                        TypedSyntax.InlayHint(10, 33, ")::Union{Float64, Int64}", nothing)
+                        TypedSyntax.InlayHint(10, 33, ")::Union{Float64, $Int}", nothing)
                     ])
                 else
                     @test equal_upto_ordering(first(values(VSCodeServer.inlay_hints[1])), [
-                        TypedSyntax.InlayHint(7, 19, "::Union{Float64, Int64}", 1)
+                        TypedSyntax.InlayHint(7, 19, "::Union{Float64, $Int}", 1)
                         TypedSyntax.InlayHint(10, 11, "(", 1)
-                        TypedSyntax.InlayHint(10, 33, ")::Union{Float64, Int64}", 1)
+                        TypedSyntax.InlayHint(10, 33, ")::Union{Float64, $Int}", 1)
                     ])
                 end
             end
@@ -206,12 +206,20 @@ include("test_vscode_example_functions.jl")
             if !hide_type_stable && inlay_types_vscode
                 @test equal_upto_ordering(first(values(VSCodeServer.inlay_hints[1])), [
                     TypedSyntax.InlayHint(14, 18, "::Float64", 1)
-                    TypedSyntax.InlayHint(14, 19, "::Int64", 1)
-                    TypedSyntax.InlayHint(15, 15, "::Float64", 1)
-                    TypedSyntax.InlayHint(15, 16, "::Int64", 1)
+                    TypedSyntax.InlayHint(14, 19, "::$Int", 1)
+                    TypedSyntax.InlayHint(15, 11, "::Type{$Int}", 1)
+                    TypedSyntax.InlayHint(15, 13, "::Float64", 1)
+                    TypedSyntax.InlayHint(15, 14, "::$Int", 1)
                     TypedSyntax.InlayHint(16, 9, "::Float64", 1)
-                    TypedSyntax.InlayHint(16, 10, "::Int64", 1)
-                ])
+                    TypedSyntax.InlayHint(16, 10, "::$Int", 1)
+                ]) || equal_upto_ordering(first(values(VSCodeServer.inlay_hints[1])), [
+                    TypedSyntax.InlayHint(14, 18, "::Float64", 1)
+                    TypedSyntax.InlayHint(14, 19, "::$Int", 1)
+                    TypedSyntax.InlayHint(15, 13, "::Float64", 1)
+                    TypedSyntax.InlayHint(15, 14, "::$Int", 1)
+                    TypedSyntax.InlayHint(16, 9, "::Float64", 1)
+                    TypedSyntax.InlayHint(16, 10, "::$Int", 1)
+                ]) # Workaround for #530
             elseif hide_type_stable && inlay_types_vscode
                 @test isempty(VSCodeServer.inlay_hints[1])
             end
@@ -219,12 +227,12 @@ include("test_vscode_example_functions.jl")
     end
 
     for inlay_types_vscode in (true, false), diagnostics_vscode in (true, false), iswarn in (true, false), hide_type_stable in (true, false)
-        @testset "fibcall Int64 inlay_types_vscode=$inlay_types_vscode, diagnostics_vscode=$diagnostics_vscode, iswarn=$iswarn, hide_type_stable=$hide_type_stable" begin
+        @testset "fibcall Int inlay_types_vscode=$inlay_types_vscode, diagnostics_vscode=$diagnostics_vscode, iswarn=$iswarn, hide_type_stable=$hide_type_stable" begin
             VSCodeServer.reset_test_containers()
 
             fake_terminal() do term, in, out, _
                 t = @async begin
-                    @test_nowarn descend(fibcall, (Int64,); terminal=term, iswarn, hide_type_stable, inlay_types_vscode, diagnostics_vscode)
+                    @test_nowarn descend(fibcall, (Int,); terminal=term, iswarn, hide_type_stable, inlay_types_vscode, diagnostics_vscode)
                 end
                 write(in, 'q')
                 wait(t)
@@ -248,25 +256,45 @@ include("test_vscode_example_functions.jl")
 
             if !hide_type_stable && inlay_types_vscode
                 @test equal_upto_ordering(first(values(VSCodeServer.inlay_hints[1])), [
-                    TypedSyntax.InlayHint(14, 18, "::Int64", 1)
-                    TypedSyntax.InlayHint(14, 19, "::Int64", 1)
-                    TypedSyntax.InlayHint(15, 15, "::Int64", 1)
-                    TypedSyntax.InlayHint(15, 16, "::Int64", 1)
-                    TypedSyntax.InlayHint(16, 9, "::Int64", 1)
-                    TypedSyntax.InlayHint(16, 10, "::Int64", 1)
-                    TypedSyntax.InlayHint(1, 14, "::Int64", 1)
-                    TypedSyntax.InlayHint(1, 15, "::Int64", 1)
+                    TypedSyntax.InlayHint(14, 18, "::$Int", 1)
+                    TypedSyntax.InlayHint(14, 19, "::$Int", 1)
+                    TypedSyntax.InlayHint(15, 11, "::Type{$Int}", 1)
+                    TypedSyntax.InlayHint(15, 13, "::$Int", 1)
+                    TypedSyntax.InlayHint(15, 14, "::$Int", 1)
+                    TypedSyntax.InlayHint(16, 9, "::$Int", 1)
+                    TypedSyntax.InlayHint(16, 10, "::$Int", 1)
+                    TypedSyntax.InlayHint(1, 14, "::$Int", 1)
+                    TypedSyntax.InlayHint(1, 15, "::$Int", 1)
                     TypedSyntax.InlayHint(3, 11, "(", 1)
                     TypedSyntax.InlayHint(3, 15, "(", 1)
-                    TypedSyntax.InlayHint(3, 16, "::Int64", 1)
-                    TypedSyntax.InlayHint(3, 20, ")::Int64", 1)
-                    TypedSyntax.InlayHint(3, 21, "::Int64", 1)
+                    TypedSyntax.InlayHint(3, 16, "::$Int", 1)
+                    TypedSyntax.InlayHint(3, 20, ")::$Int", 1)
+                    TypedSyntax.InlayHint(3, 21, "::$Int", 1)
                     TypedSyntax.InlayHint(3, 28, "(", 1)
-                    TypedSyntax.InlayHint(3, 29, "::Int64", 1)
-                    TypedSyntax.InlayHint(3, 33, ")::Int64", 1)
-                    TypedSyntax.InlayHint(3, 34, "::Int64", 1)
-                    TypedSyntax.InlayHint(3, 34, ")::Int64", 1)
-                ])
+                    TypedSyntax.InlayHint(3, 29, "::$Int", 1)
+                    TypedSyntax.InlayHint(3, 33, ")::$Int", 1)
+                    TypedSyntax.InlayHint(3, 34, "::$Int", 1)
+                    TypedSyntax.InlayHint(3, 34, ")::$Int", 1)
+                ]) || equal_upto_ordering(first(values(VSCodeServer.inlay_hints[1])), [
+                    TypedSyntax.InlayHint(14, 18, "::$Int", 1)
+                    TypedSyntax.InlayHint(14, 19, "::$Int", 1)
+                    TypedSyntax.InlayHint(15, 13, "::$Int", 1)
+                    TypedSyntax.InlayHint(15, 14, "::$Int", 1)
+                    TypedSyntax.InlayHint(16, 9, "::$Int", 1)
+                    TypedSyntax.InlayHint(16, 10, "::$Int", 1)
+                    TypedSyntax.InlayHint(1, 14, "::$Int", 1)
+                    TypedSyntax.InlayHint(1, 15, "::$Int", 1)
+                    TypedSyntax.InlayHint(3, 11, "(", 1)
+                    TypedSyntax.InlayHint(3, 15, "(", 1)
+                    TypedSyntax.InlayHint(3, 16, "::$Int", 1)
+                    TypedSyntax.InlayHint(3, 20, ")::$Int", 1)
+                    TypedSyntax.InlayHint(3, 21, "::$Int", 1)
+                    TypedSyntax.InlayHint(3, 28, "(", 1)
+                    TypedSyntax.InlayHint(3, 29, "::$Int", 1)
+                    TypedSyntax.InlayHint(3, 33, ")::$Int", 1)
+                    TypedSyntax.InlayHint(3, 34, "::$Int", 1)
+                    TypedSyntax.InlayHint(3, 34, ")::$Int", 1)
+                ]) # Workaround for #530
             elseif hide_type_stable && inlay_types_vscode
                 @test isempty(VSCodeServer.inlay_hints[1])
             end
@@ -274,4 +302,4 @@ include("test_vscode_example_functions.jl")
     end
 end
 
-end
\ No newline at end of file
+end
diff --git a/test/test_irshow.jl b/test/test_irshow.jl
index 22e2870..b9418a1 100644
--- a/test/test_irshow.jl
+++ b/test/test_irshow.jl
@@ -10,7 +10,7 @@ include("IRShowSandbox.jl")
     tf = (true, false)
 
     @testset "optimize: $optimize" for optimize in tf
-        (; src, infos, mi, rt, effects, slottypes) = cthulhu_info(IRShowSandbox.foo, (Int, Int); optimize);
+        (; src, infos, mi, rt, exct, effects, slottypes) = cthulhu_info(IRShowSandbox.foo, (Int, Int); optimize);
 
         @testset "debuginfo: $debuginfo" for debuginfo in instances(Cthulhu.DInfo.DebugInfo)
             @testset "iswarn: $iswarn" for iswarn in tf
@@ -22,7 +22,7 @@ include("IRShowSandbox.jl")
 
                             s = sprint(; context=:color=>true) do io
                                 Cthulhu.cthulhu_typed(io, debuginfo,
-                                                      src, rt, effects, mi;
+                                                      src, rt, exct, effects, mi;
                                                       iswarn, hide_type_stable, inline_cost, type_annotations)
                             end
                             s = strip_base_linenums(s)
diff --git a/test/test_terminal.jl b/test/test_terminal.jl
index 60bef4a..954e041 100644
--- a/test/test_terminal.jl
+++ b/test/test_terminal.jl
@@ -207,14 +207,9 @@ end
                 Base.text_colors[Base.error_color()]
             end
             @test occursin("$(warncolor)%\e[39m2 = call → fmulti(::Any)::Union{Float32, Int64}", lines)
-            if isdefined(Core.Compiler, :NoCallInfo)
-                write(in, keydict[:down])
-                write(in, keydict[:enter])
-                lines = cread(out)
-            else
-                write(in, keydict[:enter])
-                lines = cread1(out)
-            end
+            write(in, keydict[:down])
+            write(in, keydict[:enter])
+            lines = cread(out)
             @test occursin("%2 = fmulti(::Int32)::Union{Float32, $Int}", lines)
             @test occursin("%2 = fmulti(::Float32)::Union{Float32, $Int}", lines)
             @test occursin("%2 = fmulti(::Char)::Union{Float32, $Int}", lines)
diff --git a/test/test_vscode_example_functions.jl b/test/test_vscode_example_functions.jl
index 8de2228..8728ed8 100644
--- a/test/test_vscode_example_functions.jl
+++ b/test/test_vscode_example_functions.jl
@@ -13,6 +13,6 @@ end
 
 # exact location in file matters
 function fibcall(n)
-    fib(Int64(n))
+    fib(Int(n))
     fib(n)
 end
\ No newline at end of file