# Why don't \`Any\` constraints check that the parameter is a type

**URL:** <https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920>\
**Category:** Internals & Design\
**Created:** [February 2, 2023, 12:21pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920 "2023-02-02T12:21:36Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [February 2, 2023, 12:21pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/1 "2023-02-02T12:21:36Z")

</div>

Why is a `<:Any` constraint equivalent to there being no constraint?

For abstract types:

```julia
julia> abstract type A{T <: Any} end

julia> A{2} # Succeeds even though 2 doesn't subtype Any
A{2}

```

For concrete types:

```julia
julia> struct S{T <: Any} end

julia> S{2} # Succeeds even though 2 doesn't subtype Any
S{2}

```

For methods:

```julia
julia> f(::Val{T}) where {T <: Any} = nothing
f (generic function with 1 method)

julia> f(Val{2}()) # Succeeds even though 2 doesn't subtype Any

```

The above behavior seems to be inconsistent with how subtyping constraints work for all other types except for `Any`, a constraint `A <: B` is only valid if `A` subtypes `B`, _except_ if `B` is `Any`. For example:

```julia
julia> struct R{T <: Number} end

julia> R{2}
ERROR: TypeError: in R, in T, expected T<:Number, got a value of type Int64

```

The operator `<:`, in expressions, doesn’t exhibit this inconsistency: `2 <: Any` fails just the same as `2 <: Number`:

```julia
julia> 2 <: Any
ERROR: TypeError: in <:, expected Type, got a value of type Int64

```

```julia
julia> 2 <: Number
ERROR: TypeError: in <:, expected Type, got a value of type Int64

```

EDIT: fixed source code formatting

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [February 2, 2023, 2:21pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/2 "2023-02-02T14:21:55Z")

</div>

It’s actually the other way around - only specifiying `abstract type A{T} end` is equivalent to `T <: Any`, and it is the `2` and `Val(2)` that are special cased to be allowed here. The `<: Any` “check” is never done at all, if the effective result is “allow anything”. See [here](https://docs.julialang.org/en/v1/manual/types/#man-types):

> - Both abstract and concrete types can be parameterized by other types. They can also be parameterized by symbols, by values of any type for which [`isbits`](https://docs.julialang.org/en/v1/base/base/#Base.isbits) returns true (essentially, things like numbers and bools that are stored like C types or `struct`s with no pointers to other objects), and also by tuples thereof. Type parameters may be omitted when they do not need to be referenced or restricted.

---

<div class="post-metadata">

**Author:** ![sijo](https://avatars.discourse-cdn.com/v4/letter/s/da6949/32.png) [@sijo](https://discourse.julialang.org/u/sijo)\
**Post date:** [February 2, 2023, 2:50pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/3 "2023-02-02T14:50:11Z")

</div>

> [@Sukera](#):
>
> The `<: Any` “check” is never done at all, if the effective result is “allow anything”.

That’s the question though: why is the check not done, since the effective result if it was done would not be to allow anything…

The doc says that `<:` checks for a subtype-supertype relationship, so it would make sense for `{T}` to accept any isbits value and for `{T <: Any}` to accept only type values.

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [February 2, 2023, 3:50pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/4 "2023-02-02T15:50:56Z")

</div>

That’s the answer, though: we don’t have a separate syntax _or internal representation_ for `T{X::Any}`.

The only way value parameters work right now is because the `<: Any` constraint is not enforced. I could’ve sworn there was an issue tracking the `isa` constraint for type parameters, but I cannot find it.

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [February 2, 2023, 4:44pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/5 "2023-02-02T16:44:02Z")

</div>

Put another way - since `Any` is the supertype of all types (even itself), there’s nothing to check - anything in the type system will fulfill the requirement of being a subtype of `Any`, by definition. Additionally, we also allow objects that are `isbits` to match such a clause. That doesn’t mean that the objects themselves are a subtype of `Any` though and that’s not what the `<:` is doing here. It’s not really a check for conformance, but more communication about the requirements of the type - in that interpretation, `<: Any` means “I don’t have any particular requirements”.

Alas, this shows a hole in our type system - we can’t write `A{T isa Int}` after all (though we probably could make it so, the utility is a bit questionable).

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [February 2, 2023, 5:11pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/6 "2023-02-02T17:11:31Z")

</div>

I wouldn’t work so hard to justify the status quo; it’s just a necessary practicality do make Julia act the way we want.

> <https://github.com/JuliaLang/julia/issues/9580>
>
> When a type parameter is specified to be \`T \<: Top\`, it actually accept non-type… as parameters as well.
> 
> \`\`\` julia
> julia\> type A{T\<:Top}
> end
> 
> julia\> A{1}
> A{1}
> 
> julia\> A{1.2}
> A{1.2}
> \`\`\`
> 
> Although \`1\` and \`1.2\` are clearly not subtypes of \`Top\`
> 
> \`\`\` julia
> julia\> 1 \<: Top
> ERROR: type: subtype: expected Type{T\<:Top}, got Int64
> 
> julia\> 1.2 \<: Top
> ERROR: type: subtype: expected Type{T\<:Top}, got Float64
> \`\`\`
> 
> This is also the case for the builtin \`Type\` (some other types like \`Array\` doesn't even have this constraint specified)
> 
> \`\`\` julia
> julia\> Type
> Type{T\<:Top}
> 
> julia\> Type{1}
> Type{1}
> 
> julia\> Type{1.2}
> Type{1.2}
> \`\`\`
> 
> No error is raised even if this parameter is used as a type of a field
> 
> \`\`\` julia
> julia\> type A{T\<:Top}
> a::T
> end
> 
> julia\> A{1}
> A{1}
> 
> julia\> A{1}.names
> (:a,)
> 
> julia\> A{1}.types
> (1,)
> \`\`\`
> 
> The same thing happens for \`T \<: Any\` although this time it correctly reject \`Core.Unref\`
> 
> \`\`\` julia
> julia\> type A{T \<: Any}
> end
> 
> julia\> A{1}
> A{1}
> 
> julia\> A{1.2}
> A{1.2}
> 
> julia\> A{Core.Undef}
> ERROR: type: A: in T, expected T, got Type{Undef}
> 
> julia\> Core.Undef \<: Any
> false
> \`\`\`
> 
> Specifying other types seems fine
> 
> \`\`\` julia
> julia\> type C{T \<: Integer}
> end
> 
> julia\> C{1}
> ERROR: type: C: in T, expected T\<:Integer, got Int64
> 
> julia\> C{Int}
> C{Int64}
> \`\`\`
> 
> Another related issue is that \`Vararg\` doesn't specify any constraint on the parameter and therefore the following syntax is allowed.
> 
> \`\`\` julia
> julia\> (1...)\[1\]
> 1...
> 
> julia\> typeof((1...)\[1\])
> DataType
> \`\`\`

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [February 2, 2023, 5:21pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/7 "2023-02-02T17:21:49Z")

</div>

But this time I’m not asking for constraints on non-type as type parameters (I can already enforce such constraints by wrapping the non-type in a singleton type). What I’m instead asking for in this post is for the `<:Any` constraint to have a different effect than no constraint. It would be an improvement for the safety of Julia code if the `<:Any` constraint was actually effectful, i.e., if it checked that the parameter is an actual type. For example, if I define two types like:

```julia
# the parameter can be a non-type
struct S{s} end

# the parameter must be a type
struct R{T <: Any} end

```

I would expect and like it if `R{3}` was caught as an error because of that `<:Any` constraint. Instead it seems that the constraint is just ignored as a consequence of the current implementation.

EDIT: I guess that an alternative proposal would be to introduce a new abstract type that subtypes `Any`, with all _other_ types being subtypes of the new type. Then I could use the constraint with the new type, and it would work as-is.

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [February 2, 2023, 5:25pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/8 "2023-02-02T17:25:32Z")

</div>

> [@nsajko](#):
>
> It would be an improvement for the safety of Julia code if the `<:Any` constraint was actually effectful

I’m not sure I follow - what is the tangible increase in safety (from what)? Or is it that you’d like to get an error when calling the constructor, rather than inside of it?

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [February 2, 2023, 5:43pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/9 "2023-02-02T17:43:45Z")

</div>

While looking for a workaround, I found out that even constraints using UnionAll, like `<:(AnyType where {AnyType})`, have no effect. For example:

```julia
# Sadly behaves as if there's no constraint
struct S{T <: (AnyType where {AnyType})} end

# Likewise
struct R{T <: (Union{AnyType} where {AnyType})} end

```

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [February 2, 2023, 5:46pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/10 "2023-02-02T17:46:13Z")

</div>

> [@Sukera](#):
>
> Or is it that you’d like to get an error when calling the constructor, rather than inside of it?

Sorry, but what does “inside of it” mean?

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [February 2, 2023, 5:53pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/11 "2023-02-02T17:53:23Z")

</div>

> [@Sukera](#):
>
> I’m not sure I follow - what is the tangible increase in safety (from what)? Or is it that you’d like to get an error when calling the constructor, rather than inside of it?

I’d like to constrain my type parameters to be types, i.e., disallow non-type values for certain (most) parameters. The error should happen independently of constructing the object, i.e., something like `S{3}` should fail.

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [February 2, 2023, 6:01pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/12 "2023-02-02T18:01:30Z")

</div>

> [@nsajko](#):
>
> Sorry, but what does “inside of it” mean?

Currently, if your type expects to be passed a type in its constructor (but only has `<: Any` or untyped for that parameter), but is passed something else instead, the error happens inside of the constructor, i.e. when it is called. What I was referring to is that if you could restrict `T` to be an instance of an `isbitstype` (or conversely, require `T` to be a type and disallow instances of types), you’d get a `MethodError` instead, i.e. an error outside of the constructor, since there is no matching method/constructor to be called.

> [@nsajko](#):
>
> The error should happen independently of constructing the object, i.e., something like `S{3}` should fail.

Yes, that cannot be expressed right now. What `S{3}` is doing is constructing an instance of `DataType`, by filling the type parameters:

```julia-auto
julia> struct S{T} end

julia> S{3} |> dump
S{3} <: Any

julia> S{3} |> typeof
DataType

julia> Meta.@lower S{3}
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = Core.apply_type(S, 3)
└── return %1
))))

```

If I understand correctly, you want to introduce a distinction between instances of `isbitstype`s and instances of `DataType`, to be matched when constructing the `S{3}` instance of `DataType`?

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [February 2, 2023, 6:22pm UTC](https://discourse.julialang.org/t/why-dont-any-constraints-check-that-the-parameter-is-a-type/93920/13 "2023-02-02T18:22:58Z")

</div>

> [@Sukera](#):
>
> If I understand correctly, you want to introduce a distinction between instances of `isbitstype`s and instances of `DataType`, to be matched when constructing the `S{3}` instance of `DataType`?

Yeah, but you’re making it sound like I’m asking for an exception. It’s the opposite, I’m just asking for consistency, for the root of the type system, `Any`, to behave like other types. Like I’ve shown in the original post above, any other type _except_ `Any` will behave as expected.
