Skip to content

Conversation

@nsajko
Copy link
Member

@nsajko nsajko commented Aug 30, 2025

Introduce functions to construct and typeassert several concrete types:

  • Bool

  • other primitive integers

  • Char

  • String

The new functions are applied internally instead of the original constructors to deduplicate code, as typeasserts that were already present inline are eliminated.

The new functions are somewhat of a workaround for issue #42372 local to the implementation here.

Motivation: adding more typeasserts seems necessary to fix the test suite for existing PRs that try to decrease max_methods, so this PR tries to allow for doing that in the future in a principled manner.

Additional motivation: in some cases typeasserts are necessary to prevent stack overflows given user-defined constructor methods with unexpected return types, such as here:

julia/base/file.jl

Lines 373 to 374 in 3396562

cptree(src::AbstractString, dst::AbstractString; kwargs...) =
cptree(String(src)::String, String(dst)::String; kwargs...)

@nsajko
Copy link
Member Author

nsajko commented Aug 30, 2025

adding more typeasserts seems necessary to fix the test suite for existing PRs that try to decrease max_methods

In particular, for PR #59377, if no typeasserts are added, these tests fail:

@test all(T -> T <: Union{Union{}, Int}, Base.return_types(write, (IO, AbstractString)))

@test Int === Base.infer_return_type(nextind, Tuple{AbstractString, Vararg})

@nsajko nsajko force-pushed the partially_work_around_issue_42372 branch from 9eeaa3b to 7536d40 Compare August 30, 2025 13:02
@nsajko nsajko marked this pull request as ready for review August 30, 2025 14:31
Copy link
Member

@LilithHafner LilithHafner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed 1 of the hundreds of changes. The argument to _UInt8 in base/sort.jl seems extremely unlikely to not be concretely typed. It this change hurts readability slightly but shouldn't hurt inference or runtime performance.

This type of wide-ranging grep-based PR is slightly risky in the absence of 100% code coverage, though this particular PR's methodology is reasonable and unlikely to cause bugs that are not caught by release PkgEval IMO.

Please demonstrate at least a few examples of where this PR helps inference. I wouldn't want to make hundreds of changes that don't actually help anything.

Whenever possible I like it for Base code to look like ordinary package code to make it more understandable via introspection and easier to contribute to. This is a secondary concern, but this PR does hurt that value.

I'm curious what Triage has to say about this PR.

@LilithHafner LilithHafner added the triage This should be discussed on a triage call label Aug 31, 2025
@nsajko
Copy link
Member Author

nsajko commented Sep 1, 2025

Please demonstrate at least a few examples of where this PR helps inference. I wouldn't want to make hundreds of changes that don't actually help anything.

My comment above mentions two inference tests that fail when decreasing max_methods without this change (the changes under base/strings/, to be specific).

However, it just crossed my mind it might make sense to separate this PR into multiple PRs:

  • First, I would change this PR so it doesn't add any additional typeassert. Instead it would just do code deduplication, for example changing String(expression)::String for _String(expression). EDIT: done, see latest commit.

  • Then, potential followup PRs can:

    • Introduce typeasserts to prevent stack overflows.

    • Introduce typeasserts to help inference.

Does that sound like a better plan?

Introduce functions to construct and typeassert several concrete types:

* `Bool`

* other primitive integers

* `Char`

* `String`

The new functions are meant to be used internally instead of the
original constructors to help inference.

Cross-reference issue JuliaLang#42372.
@nsajko nsajko force-pushed the partially_work_around_issue_42372 branch from fa84d91 to b0ede0a Compare September 1, 2025 04:26
@nsajko nsajko force-pushed the partially_work_around_issue_42372 branch from b0ede0a to 59a28fa Compare September 1, 2025 04:28
@KristofferC
Copy link
Member

The new functions are applied internally instead of the original constructors to deduplicate code

I personally don't see any improvements to readability or functionality in this PR. "Deduplicating" code is not an end goal.

@jakobnissen
Copy link
Member

IMO improvements to inference is good, but this is an ugly code pattern. It seems un-idiomatic, and it's not great to see it proliferate in Base.
I understand how annoying it is that Julia can't infer T(::Any)::T, as discussed in the original issue linked in this.

I think the least bad solution - which does not require solving #42372 more generally - is to spam ::T, as in s = String(x)::String, and I would prefer if this PR did that. Compared to that, this PR introduces a layer of indirection using private functions.

@LilithHafner
Copy link
Member

I agree with @jakobnissen on all points. We can remove all the annotations in this pattern T(x)::T once we have a more general solution that obviates them.

@nsajko
Copy link
Member Author

nsajko commented Sep 2, 2025

OK. I think this would have been a nice solution but I guess I'm the only one.

@nsajko nsajko closed this Sep 2, 2025
@nsajko
Copy link
Member Author

nsajko commented Sep 2, 2025

I'll open other, more localized, PRs to prevent stack overflows and fix inference. EDIT: PR #59506

@nsajko nsajko deleted the partially_work_around_issue_42372 branch September 2, 2025 10:53
Comment on lines +11 to +18
struct ConstructorStrict{F} <: Function end
# Keyword arguments are not available at this point in bootstrap, so they're not
# supported.
function (::ConstructorStrict{F})(args::Vararg{Any, N}) where {F, N}
@inline
r = F(args...)
r::F
end
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a historical note, soon after closing this PR I realized there's a nicer way of doing this, based on preexisting functionality and more generic: Base.Fix2(typeassert, F) ∘ F. Registered package based on this idea:

@nsajko nsajko removed the triage This should be discussed on a triage call label Sep 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants