/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
test/typegroup.jl
967 строк
33 KB
Keno Fischer
Use typegroup mechanism for ordinary structs (#61306)
29 июл 2026, 02:21
Не верифицирован
29 июл 2026, 02:21
fd4f64f
Код
Авторство
О чём код?
# Tests for typegroup blocks (mutually recursive type definitions) # See https://github.com/JuliaLang/julia/issues/269 using Test @testset "typegroup blocks" begin @testset "basic mutual recursion" begin # Classic graph example: nodes and edges reference each other typegroup struct TG_Node edges::Vector{TG_Edge} end struct TG_Edge from::TG_Node to::TG_Node end end @test fieldtype(TG_Node, :edges) == Vector{TG_Edge} @test fieldtype(TG_Edge, :from) == TG_Node @test fieldtype(TG_Edge, :to) == TG_Node # Can construct instances n1 = TG_Node(TG_Edge[]) n2 = TG_Node(TG_Edge[]) e = TG_Edge(n1, n2) push!(n1.edges, e) @test n1.edges[1].to === n2 end @testset "parametric types" begin # Parametric mutual recursion typegroup struct TG_PNode{T} data::T edges::Vector{TG_PEdge{T}} end struct TG_PEdge{T} from::TG_PNode{T} to::TG_PNode{T} end end @test fieldtype(TG_PNode{Int}, :edges) == Vector{TG_PEdge{Int}} @test fieldtype(TG_PEdge{String}, :from) == TG_PNode{String} # Can construct parametric instances n1 = TG_PNode(42, TG_PEdge{Int}[]) n2 = TG_PNode(99, TG_PEdge{Int}[]) e = TG_PEdge(n1, n2) @test e.from.data == 42 @test e.to.data == 99 end @testset "self-referential types" begin # Single type referencing itself (degenerate case) typegroup struct TG_SelfRef next::Union{Nothing, TG_SelfRef} end end @test fieldtype(TG_SelfRef, :next) == Union{Nothing, TG_SelfRef} node3 = TG_SelfRef(nothing) node2 = TG_SelfRef(node3) node1 = TG_SelfRef(node2) @test node1.next.next === node3 end @testset "mutable structs" begin typegroup mutable struct TG_MutNode edges::Vector{TG_MutEdge} end mutable struct TG_MutEdge from::TG_MutNode to::TG_MutNode end end @test ismutabletype(TG_MutNode) @test ismutabletype(TG_MutEdge) n1 = TG_MutNode(TG_MutEdge[]) n2 = TG_MutNode(TG_MutEdge[]) e = TG_MutEdge(n1, n2) push!(n1.edges, e) # Can mutate e.to = n1 @test e.to === n1 end @testset "return value" begin # typegroup returns nothing (types are defined as side effect) result = typegroup struct TG_ReturnTest x::Int end end @test result === nothing end @testset "where clause in field types" begin # Field types with where clauses (UnionAll) typegroup struct TG_Container # Field type uses where clause with reference to TG_Item items::Vector{TG_Item{T} where T} end struct TG_Item{T} value::T parent::TG_Container end end @test fieldtype(TG_Container, :items) == Vector{TG_Item{T} where T} @test fieldtype(TG_Item{Int}, :parent) == TG_Container # Can construct and use c = TG_Container(TG_Item[]) item = TG_Item(42, c) push!(c.items, item) @test c.items[1].value == 42 @test c.items[1].parent === c end @testset "parametric mutual recursion with Union" begin # Issue: parametric types with Union{Nothing, OtherType{T}} fields # This tests cycle detection during type instantiation typegroup struct TG_UnionA{T} value::T other::Union{Nothing, TG_UnionB{T}} end struct TG_UnionB{T} value::T other::Union{Nothing, TG_UnionA{T}} end end @test fieldtype(TG_UnionA{Int}, :other) == Union{Nothing, TG_UnionB{Int}} @test fieldtype(TG_UnionB{Int}, :other) == Union{Nothing, TG_UnionA{Int}} # Construct instances a = TG_UnionA{Int}(1, nothing) b = TG_UnionB{Int}(2, nothing) a2 = TG_UnionA{Int}(3, b) b2 = TG_UnionB{Int}(4, a) @test a.other === nothing @test a2.other.value == 2 @test b2.other.value == 1 end @testset "parametric direct mutual reference" begin # Issue: direct reference (not through Union) caused stack overflow # This tests mayinlinealloc cycle detection typegroup struct TG_DirectA{T} value::T other::Union{Nothing, TG_DirectB{T}} end struct TG_DirectB{T} target::TG_DirectA{T} # Direct reference, not Union weight::Float64 end end @test fieldtype(TG_DirectA{Int}, :other) == Union{Nothing, TG_DirectB{Int}} @test fieldtype(TG_DirectB{Int}, :target) == TG_DirectA{Int} # Construct instances a = TG_DirectA{Int}(42, nothing) b = TG_DirectB{Int}(a, 1.5) a2 = TG_DirectA{Int}(99, b) @test a2.other.target.value == 42 @test a2.other.weight == 1.5 end @testset "parametric with Vector wrapping" begin # Issue: Vector{OtherType{T}} caused stack overflow during layout computation typegroup struct TG_VecNode{T} value::T edges::Vector{TG_VecEdge{T}} end struct TG_VecEdge{T} target::TG_VecNode{T} weight::Float64 end end @test fieldtype(TG_VecNode{Int}, :edges) == Vector{TG_VecEdge{Int}} @test fieldtype(TG_VecEdge{String}, :target) == TG_VecNode{String} # Construct graph n1 = TG_VecNode{Int}(1, TG_VecEdge{Int}[]) n2 = TG_VecNode{Int}(2, TG_VecEdge{Int}[]) e1 = TG_VecEdge{Int}(n2, 1.0) e2 = TG_VecEdge{Int}(n1, 2.0) n3 = TG_VecNode{Int}(3, [e1, e2]) @test length(n3.edges) == 2 @test n3.edges[1].target.value == 2 @test n3.edges[2].target.value == 1 end @testset "three-way parametric mutual recursion" begin # Test cycle detection with more than two types typegroup struct TG_ThreeA{T} value::T b::Union{Nothing, TG_ThreeB{T}} end struct TG_ThreeB{T} value::T c::Union{Nothing, TG_ThreeC{T}} end struct TG_ThreeC{T} value::T a::Union{Nothing, TG_ThreeA{T}} end end @test fieldtype(TG_ThreeA{Int}, :b) == Union{Nothing, TG_ThreeB{Int}} @test fieldtype(TG_ThreeB{Int}, :c) == Union{Nothing, TG_ThreeC{Int}} @test fieldtype(TG_ThreeC{Int}, :a) == Union{Nothing, TG_ThreeA{Int}} # Construct chain a = TG_ThreeA{Int}(1, nothing) c = TG_ThreeC{Int}(3, a) b = TG_ThreeB{Int}(2, c) a2 = TG_ThreeA{Int}(4, b) @test a2.b.c.a.value == 1 end @testset "multiple type parameters" begin # Mutual recursion with multiple type parameters typegroup struct TG_MultiA{K,V} key::K value::V other::Union{Nothing, TG_MultiB{K,V}} end struct TG_MultiB{K,V} key::K value::V other::Union{Nothing, TG_MultiA{K,V}} end end @test fieldtype(TG_MultiA{String,Int}, :other) == Union{Nothing, TG_MultiB{String,Int}} a = TG_MultiA{String,Int}("a", 1, nothing) b = TG_MultiB{String,Int}("b", 2, a) @test b.other.key == "a" @test b.other.value == 1 end @testset "four-way mutual recursion" begin typegroup struct TG_FourA{T} b::Union{Nothing, TG_FourB{T}} d::Union{Nothing, TG_FourD{T}} end struct TG_FourB{T} c::Union{Nothing, TG_FourC{T}} a::Union{Nothing, TG_FourA{T}} end struct TG_FourC{T} d::Union{Nothing, TG_FourD{T}} b::Union{Nothing, TG_FourB{T}} end struct TG_FourD{T} a::Union{Nothing, TG_FourA{T}} c::Union{Nothing, TG_FourC{T}} end end a = TG_FourA{Int}(nothing, nothing) b = TG_FourB{Int}(nothing, a) c = TG_FourC{Int}(nothing, b) d = TG_FourD{Int}(a, c) @test d.a === a @test d.c.b.a === a end @testset "graph with typed edges" begin # Pattern from Rust's petgraph typegroup struct TG_Graph{N, E} nodes::Vector{TG_GraphNode{N, E}} end struct TG_GraphNode{N, E} data::N edges::Vector{TG_GraphEdge{N, E}} end struct TG_GraphEdge{N, E} weight::E target::TG_GraphNode{N, E} end end n1 = TG_GraphNode{String, Float64}("A", TG_GraphEdge{String,Float64}[]) n2 = TG_GraphNode{String, Float64}("B", TG_GraphEdge{String,Float64}[]) e = TG_GraphEdge{String, Float64}(1.5, n2) push!(n1.edges, e) g = TG_Graph{String, Float64}([n1, n2]) @test g.nodes[1].edges[1].target.data == "B" end @testset "JSON-like recursive structure" begin typegroup struct TG_JSONValue data::Union{Nothing, Bool, Int, Float64, String, TG_JSONArray, TG_JSONObject} end struct TG_JSONArray elements::Vector{TG_JSONValue} end struct TG_JSONObject pairs::Vector{Pair{String, TG_JSONValue}} end end arr = TG_JSONArray([TG_JSONValue(42), TG_JSONValue("hello")]) obj = TG_JSONObject([Pair("array", TG_JSONValue(arr))]) @test obj.pairs[1].second.data.elements[1].data == 42 end @testset "doubly-linked list" begin typegroup mutable struct TG_DLNode{T} value::T prev::Union{Nothing, TG_DLNode{T}} next::Union{Nothing, TG_DLNode{T}} end end n1 = TG_DLNode(1, nothing, nothing) n2 = TG_DLNode(2, n1, nothing) n1.next = n2 @test n1.next.value == 2 @test n2.prev.value == 1 end @testset "binary tree with parent pointer" begin typegroup mutable struct TG_BinTree{T} value::T parent::Union{Nothing, TG_BinTree{T}} left::Union{Nothing, TG_BinTree{T}} right::Union{Nothing, TG_BinTree{T}} end end root = TG_BinTree(10, nothing, nothing, nothing) left = TG_BinTree(5, root, nothing, nothing) right = TG_BinTree(15, root, nothing, nothing) root.left = left root.right = right @test root.left.parent === root @test root.right.value == 15 end @testset "lambda calculus AST" begin typegroup struct TG_LamVar name::Symbol end struct TG_LamAbs param::Symbol body::Union{TG_LamVar, TG_LamAbs, TG_LamApp} end struct TG_LamApp func::Union{TG_LamVar, TG_LamAbs, TG_LamApp} arg::Union{TG_LamVar, TG_LamAbs, TG_LamApp} end end v = TG_LamVar(:x) abs = TG_LamAbs(:x, v) app = TG_LamApp(abs, v) @test app.func.param == :x end @testset "entity-component pattern" begin typegroup struct TG_Entity id::Int components::Dict{Symbol, TG_Component} end struct TG_Component owner::TG_Entity data::Any end end e = TG_Entity(1, Dict{Symbol,TG_Component}()) c = TG_Component(e, "health") e.components[:health] = c @test e.components[:health].owner === e end @testset "NamedTuple fields" begin typegroup struct TG_NTNode data::@NamedTuple{value::Int, edge::Union{Nothing, TG_NTEdge}} end struct TG_NTEdge info::@NamedTuple{from::TG_NTNode, to::TG_NTNode, weight::Float64} end end n1 = TG_NTNode((value=1, edge=nothing)) n2 = TG_NTNode((value=2, edge=nothing)) e = TG_NTEdge((from=n1, to=n2, weight=1.0)) @test e.info.from.data.value == 1 end @testset "bounded type parameters" begin typegroup struct TG_BoundedA{T <: Number} b::Union{Nothing, TG_BoundedB{T}} end struct TG_BoundedB{T <: Number} a::Union{Nothing, TG_BoundedA{T}} end end a = TG_BoundedA{Int}(nothing) b = TG_BoundedB{Float64}(nothing) @test fieldtype(TG_BoundedA{Int}, :b) == Union{Nothing, TG_BoundedB{Int}} end @testset "deeply nested Union" begin typegroup struct TG_DeepUnionA x::Union{Nothing, Union{Int, Union{String, Union{Float64, TG_DeepUnionB}}}} end struct TG_DeepUnionB y::Union{Nothing, TG_DeepUnionA} end end a = TG_DeepUnionA(nothing) b = TG_DeepUnionB(a) @test b.y === a end @testset "supertype referencing incomplete type" begin typegroup struct TG_SuperRefA <: AbstractVector{TG_SuperRefB} data::Vector{TG_SuperRefB} end struct TG_SuperRefB a::TG_SuperRefA end end @test TG_SuperRefA <: AbstractVector{TG_SuperRefB} end @testset "self-referential supertype parameter" begin # Node{T} <: AbstractVector{Node{T}} -- type references itself in supertype params typegroup struct TG_SelfSuperNode{T} <: AbstractVector{TG_SelfSuperNode{T}} data::T end end @test TG_SelfSuperNode{Int} <: AbstractVector{TG_SelfSuperNode{Int}} @test supertype(TG_SelfSuperNode{Int}) == AbstractVector{TG_SelfSuperNode{Int}} n = TG_SelfSuperNode{Int}(42) @test n.data == 42 # Two types where one references itself in supertype typegroup struct TG_SelfSuperA{T} <: AbstractVector{TG_SelfSuperA{T}} b::Union{Nothing, TG_SelfSuperB{T}} end struct TG_SelfSuperB{T} a::TG_SelfSuperA{T} end end @test TG_SelfSuperA{Int} <: AbstractVector{TG_SelfSuperA{Int}} @test fieldtype(TG_SelfSuperB{Int}, :a) == TG_SelfSuperA{Int} end @testset "red/black list with AbstractArray{T,0} supertype" begin # Mutually recursive supertypes: each list node subtypes a 0-dimensional # AbstractArray whose element type is the opposite-color node typegroup struct TG_RedNode <: AbstractArray{TG_BlackNode, 0} child::Union{Nothing, TG_BlackNode} end struct TG_BlackNode <: AbstractArray{TG_RedNode, 0} child::Union{Nothing, TG_RedNode} end end @test TG_RedNode <: AbstractArray{TG_BlackNode, 0} @test TG_BlackNode <: AbstractArray{TG_RedNode, 0} @test eltype(TG_RedNode) == TG_BlackNode @test eltype(TG_BlackNode) == TG_RedNode # Construct alternating red/black chain r1 = TG_RedNode(nothing) b1 = TG_BlackNode(r1) r2 = TG_RedNode(b1) b2 = TG_BlackNode(r2) @test b2.child.child.child === r1 @test b2.child.child.child.child === nothing end @testset "Tuple fields with incomplete types" begin # Self-referential Tuple field typegroup struct TG_TupleSelf data::Tuple{TG_TupleSelf} end end @test fieldtype(TG_TupleSelf, :data) == Tuple{TG_TupleSelf} # Tuple with two types from typegroup typegroup struct TG_TupleA data::Tuple{Int, TG_TupleB} end struct TG_TupleB x::Int end end @test fieldtype(TG_TupleA, :data) == Tuple{Int, TG_TupleB} a = TG_TupleA((42, TG_TupleB(99))) @test a.data[1] == 42 @test a.data[2].x == 99 # NTuple with self-reference through Union typegroup struct TG_NTupleNode neighbors::NTuple{3, Union{Nothing, TG_NTupleNode}} end end @test fieldtype(TG_NTupleNode, :neighbors) == NTuple{3, Union{Nothing, TG_NTupleNode}} n = TG_NTupleNode((nothing, nothing, nothing)) @test n.neighbors[1] === nothing # Tuple with Union containing incomplete type typegroup struct TG_TupleUnion data::Tuple{Int, Union{Nothing, TG_TupleUnion}} end end t = TG_TupleUnion((42, nothing)) @test t.data[1] == 42 @test t.data[2] === nothing t2 = TG_TupleUnion((99, t)) @test t2.data[2].data[1] == 42 end @testset "TypeApp reflection" begin ta = Core.TypeApp(Vector, Int) @test ta isa Core.TypeApp @test ta.head === Vector @test ta.param === Int @test !ismutabletype(Core.TypeApp) @test fieldcount(Core.TypeApp) == 2 @test fieldnames(Core.TypeApp) == (:head, :param) # Nested chain: T{P1, P2} == TypeApp(TypeApp(T, P1), P2) ta2 = Core.TypeApp(Core.TypeApp(Dict, String), Int) @test ta2.head isa Core.TypeApp @test ta2.head.head === Dict @test ta2.head.param === String @test ta2.param === Int # TypeVar head (the typegroup placeholder pattern) tv = TypeVar(:T) ta3 = Core.TypeApp(tv, Int) @test ta3.head === tv # _contains_typeapp traverses types @test Core._contains_typeapp(ta) @test !Core._contains_typeapp(Int) @test !Core._contains_typeapp(Vector{Int}) @test !Core._contains_typeapp(Union{Int, String}) # Union{Int, TypeApp} can't be constructed directly (Union requires Types), # but _contains_typeapp handles it if encountered during resolution. # Test via DataType wrapping instead: @test Core._contains_typeapp(Core.TypeApp(Core.TypeApp(Vector, ta), Int)) # apply_type_or_typeapp: concrete args → real apply_type @test Core.apply_type_or_typeapp(Vector, Int) === Vector{Int} @test Core.apply_type_or_typeapp(Dict, String, Int) === Dict{String, Int} # apply_type_or_typeapp: TypeVar head → nested TypeApp chain r = Core.apply_type_or_typeapp(tv, Int, String) @test r isa Core.TypeApp @test r.head isa Core.TypeApp @test r.head.head === tv @test r.head.param === Int @test r.param === String # apply_type_or_typeapp: TypeApp head → extends the chain r2 = Core.apply_type_or_typeapp(ta, Float64) @test r2 isa Core.TypeApp @test r2.head === ta @test r2.param === Float64 # apply_type_or_typeapp: TypeApp buried in a param → defers r3 = Core.apply_type_or_typeapp(Vector, ta) @test r3 isa Core.TypeApp @test r3.head === Vector @test r3.param === ta # TypeApp is not a Type — reflection that requires Type arguments errors @test_throws TypeError ta <: Any @test_throws TypeError Any <: ta @test_throws TypeError typeintersect(ta, Any) @test_throws TypeError 1 isa ta @test_throws TypeError fieldcount(ta) @test_throws MethodError fieldnames(ta) @test_throws MethodError fieldtypes(ta) @test_throws MethodError supertype(ta) @test_throws TypeError hasmethod(+, Tuple{ta, ta}) # resolve_typegroup with empty inputs @test Core.resolve_typegroup(@__MODULE__, Core.svec(), Core.svec(), Core.svec()) === () end # Issue #60919: accessing incomplete types during struct definition should error, not segfault # These tests exercise the incomplete type safety checks added for ordinary struct lowering. @testset "incomplete type errors (#60919)" begin # With typegroup lowering, struct names are TypeVars during definition. # Accessing incomplete types now errors safely instead of segfaulting. # fieldtype on incomplete type (TypeVar during definition) — gets TypeError @test_throws TypeError eval(:(struct TG_60919_A <: AbstractVector{fieldtype(TG_60919_A, :x)} end)) # fieldtype on incomplete type where field exists but types aren't set yet @test_throws TypeError eval(:(struct TG_60919_B <: AbstractVector{fieldtype(TG_60919_B, :x)} x::Int end)) # sizeof on outer incomplete type from nested struct — errors because # the inner struct constructor has free type variables (the outer TypeVar) @test_throws ArgumentError eval(:(struct TG_60919_C x::(struct TG_60919_C_Inner; y::TG_60919_C; end; Core.sizeof(TG_60919_C); TG_60919_C_Inner) end)) # nested struct referencing incomplete outer type — errors because # the inner struct constructor has free type variables @test_throws ArgumentError eval(:(struct TG_60919_D x::(struct TG_60919_D_Inner; y::TG_60919_D; end; TG_60919_D_Inner) end)) end # Constructing a typegroup type while types are still being defined should error, not crash @testset "method call on incomplete typegroup type" begin @test_throws MethodError eval(:(typegroup struct TG_EarlyCall_A x::Int b::Union{Nothing, TG_EarlyCall_B} end struct TG_EarlyCall_B a::(TG_EarlyCall_A(1, nothing); TG_EarlyCall_A) end end)) end # Defining methods on incomplete types during type construction should error @testset "method definition on incomplete type during super expression" begin # Normal struct case @test_throws ArgumentError eval(:(struct TG_SideEffect_S <: (global _tg_se_f; _tg_se_f(::TG_SideEffect_S) = 1; Any) x::Int end)) # Typegroup case @test_throws ArgumentError eval(:(typegroup struct TG_SideEffect_A <: (global _tg_se_g; _tg_se_g(::TG_SideEffect_A) = 1; Any) b::Union{Nothing, TG_SideEffect_B} end struct TG_SideEffect_B a::Union{Nothing, TG_SideEffect_A} end end)) # Subtype check on incomplete type (TypeVar during definition) @test_throws TypeError eval(:(struct TG_SideEffect_Sub <: (TG_SideEffect_Sub <: Real ? Any : Real) end)) end # Precompilation should fail for modules containing incomplete type errors. @testset "precompilation rejects incomplete types" begin mktempdir() do dir pushfirst!(LOAD_PATH, dir) depot = mktempdir() pushfirst!(DEPOT_PATH, depot) try write(joinpath(dir, "TG_PrecompIncomplete.jl"), """ module TG_PrecompIncomplete # Nested struct referencing incomplete outer type struct Outer x::(struct Inner; y::Outer; end; Inner) end end """) @test_throws Exception Base.require(Main, :TG_PrecompIncomplete) finally filter!((≠)(dir), LOAD_PATH) filter!((≠)(depot), DEPOT_PATH) end end end @testset "invalid supertype errors" begin # Cannot subtype a tuple type @test_throws ErrorException eval(:(typegroup struct TG_BadTuple <: Tuple{Int} x::Int end end)) # Cannot subtype a named tuple type @test_throws ErrorException eval(:(typegroup struct TG_BadNT <: @NamedTuple{x::Int} x::Int end end)) # Cannot add subtypes to Type @test_throws ErrorException eval(:(typegroup struct TG_BadType <: Type{Int} x::Int end end)) # Can only subtype abstract types @test_throws ErrorException eval(:(typegroup struct TG_BadConcrete <: Int x::Int end end)) # A type cannot subtype itself @test_throws "a type cannot subtype itself" eval(:(typegroup struct TG_SelfSub <: TG_SelfSub x::Int end end)) end @testset "inner constructors" begin # Basic inner constructor with no arguments typegroup struct TG_InnerBasicA x::Int TG_InnerBasicA() = new(0) end struct TG_InnerBasicB a::TG_InnerBasicA end end @test TG_InnerBasicA().x == 0 @test TG_InnerBasicB(TG_InnerBasicA()).a.x == 0 # Inner constructor with arguments typegroup struct TG_InnerArgsA x::Int y::Float64 TG_InnerArgsA(x::Int) = new(x, float(x)) end struct TG_InnerArgsB a::TG_InnerArgsA end end @test TG_InnerArgsA(3).y == 3.0 @test TG_InnerArgsB(TG_InnerArgsA(5)).a.x == 5 # Inner constructor with new{T}(...) for parametric types typegroup struct TG_InnerParamA{T} x::T TG_InnerParamA{T}(x) where {T} = new{T}(x) TG_InnerParamA(x::T) where {T} = new{T}(x) end struct TG_InnerParamB{T} a::TG_InnerParamA{T} end end @test TG_InnerParamA{Int}(42).x == 42 @test TG_InnerParamA(3.14).x == 3.14 @test TG_InnerParamB{Int}(TG_InnerParamA(1)).a.x == 1 # Inner constructor in one type referencing the other typegroup type typegroup struct TG_InnerCrossA x::Int b::Union{Nothing, TG_InnerCrossB} TG_InnerCrossA(x::Int) = new(x, nothing) end struct TG_InnerCrossB a::TG_InnerCrossA TG_InnerCrossB(x::Int) = new(TG_InnerCrossA(x)) end end @test TG_InnerCrossA(1).b === nothing @test TG_InnerCrossB(42).a.x == 42 # Multiple inner constructors typegroup struct TG_InnerMultiA x::Int y::Int TG_InnerMultiA() = new(0, 0) TG_InnerMultiA(x::Int) = new(x, x) TG_InnerMultiA(x::Int, y::Int) = new(x, y) end struct TG_InnerMultiB a::TG_InnerMultiA end end @test TG_InnerMultiA().x == 0 @test TG_InnerMultiA(3).y == 3 @test TG_InnerMultiA(1, 2).y == 2 end @testset "docstrings on typegroup types" begin # Docstrings on individual types within a typegroup typegroup "TG_DocA: a documented node type" struct TG_DocA edges::Vector{TG_DocB} end "TG_DocB: a documented edge type" struct TG_DocB from::TG_DocA to::TG_DocA end end @test fieldtype(TG_DocA, :edges) == Vector{TG_DocB} @test fieldtype(TG_DocB, :from) == TG_DocA meta = Base.Docs.meta(@__MODULE__) bind_a = Base.Docs.Binding(@__MODULE__, :TG_DocA) bind_b = Base.Docs.Binding(@__MODULE__, :TG_DocB) @test haskey(meta, bind_a) @test haskey(meta, bind_b) @test contains(string(meta[bind_a].docs[Union{}]), "TG_DocA: a documented node type") @test contains(string(meta[bind_b].docs[Union{}]), "TG_DocB: a documented edge type") # Mix of documented and undocumented types typegroup "TG_DocC: only this one has a docstring" struct TG_DocC other::TG_DocD end struct TG_DocD other::TG_DocC end end @test fieldtype(TG_DocC, :other) == TG_DocD bind_c = Base.Docs.Binding(@__MODULE__, :TG_DocC) bind_d = Base.Docs.Binding(@__MODULE__, :TG_DocD) @test haskey(meta, bind_c) @test contains(string(meta[bind_c].docs[Union{}]), "TG_DocC: only this one has a docstring") @test !haskey(meta, bind_d) end @testset "group type reference inside TypeVar bounds" begin # The placeholder TypeVar for a group member must be substituted even # when it only appears inside another TypeVar's bounds (e.g. `<:` in # covariant position, which lowers to `Vector{S} where S<:Name`). struct TG_BoundSelf y::Vector{<:TG_BoundSelf} TG_BoundSelf(y) = new(y) end ft = fieldtype(TG_BoundSelf, :y) @test ft == Vector{<:TG_BoundSelf} @test !Base.has_free_typevars(ft) @test (ft::UnionAll).var.ub === TG_BoundSelf struct TG_BoundUnion x::Union{Nothing, Vector{<:TG_BoundUnion}} TG_BoundUnion(x) = new(x) end @test !Base.has_free_typevars(fieldtype(TG_BoundUnion, :x)) struct TG_BoundWhere x::Ref{S} where S<:TG_BoundWhere TG_BoundWhere(x) = new(x) end @test !Base.has_free_typevars(fieldtype(TG_BoundWhere, :x)) # Parametric self-reference through a bound; also checks that the # default constructors can be created (they reject free typevars). struct TG_BoundParam{T} x::T y::Dict{Symbol, <:TG_BoundParam} end @test !Base.has_free_typevars(fieldtype(TG_BoundParam, :y)) v = TG_BoundParam{Int}(1, Dict{Symbol, TG_BoundParam{Int}}()) @test v.x == 1 # Sibling group member referenced from inside a bound typegroup struct TG_BoundSibA items::Vector{<:TG_BoundSibB} TG_BoundSibA(items) = new(items) end struct TG_BoundSibB owner::Union{Nothing, TG_BoundSibA} end end ftA = fieldtype(TG_BoundSibA, :items) @test !Base.has_free_typevars(ftA) @test (ftA::UnionAll).var.ub === TG_BoundSibB b = TG_BoundSibB(nothing) a = TG_BoundSibA([b]) @test a.items[1] === b # Group references in a type *parameter* bound cannot be substituted # (the bound is baked into the wrapper UnionAll) and must be rejected # cleanly instead of escaping as a type with free placeholder vars. # On the old lowering this was an UndefVarError. @test_throws ErrorException @eval struct TG_BoundParamRef{T<:Union{Nothing,Vector{TG_BoundParamRef}}} x::T TG_BoundParamRef{T}(x) where T = new{T}(x) end @test !isdefined(@__MODULE__, :TG_BoundParamRef) # The same rebuilt TypeVar must be used consistently when the var # occurs in the body, so the UnionAll stays well-formed. struct TG_BoundBody x::Pair{S, Vector{S}} where S<:TG_BoundBody TG_BoundBody(x) = new(x) end ftB = fieldtype(TG_BoundBody, :x) @test !Base.has_free_typevars(ftB) @test ftB == Pair{S, Vector{S}} where S<:TG_BoundBody end @testset "type cache hygiene" begin # Count global Tuple-cache entries whose parameters reference a type # with the given name that is NOT the currently bound one (i.e. # discarded duplicates from redefinition, or types from a failed # definition). Field types in these tests keep the reference as a # direct Tuple parameter, so a shallow scan suffices. function stale_tuple_cache_refs(name::Symbol, keep = nothing) stale = 0 for cache in (Tuple.name.cache, Tuple.name.linearcache) for e in cache e isa DataType || continue for p in e.parameters if p isa DataType && p.name.name === name && p !== keep stale += 1 end end end end return stale end # Identical redefinition keeps the old type; the tuples instantiated # while constructing the discarded duplicate must not remain in the # global type cache. for _ in 1:3 @eval struct TG_CacheRedef t::Tuple{TG_CacheRedef, Int} TG_CacheRedef(t) = new(t) end end @test stale_tuple_cache_refs(:TG_CacheRedef, TG_CacheRedef) == 0 # the kept type's field tuple is published and usable @test fieldtype(TG_CacheRedef, :t) === Tuple{TG_CacheRedef, Int} # A failed group definition must not leave cache entries referencing # the never-published types. @test_throws Exception @eval typegroup struct TG_CacheFail t::Tuple{TG_CacheFail, Int} TG_CacheFail(t) = new(t) end struct TG_CacheFailBad x::1 end end @test !isdefined(@__MODULE__, :TG_CacheFail) @test stale_tuple_cache_refs(:TG_CacheFail) == 0 end end