Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No longer possible to define a function String #57290

Open
lgoettgens opened this issue Feb 6, 2025 · 7 comments
Open

No longer possible to define a function String #57290

lgoettgens opened this issue Feb 6, 2025 · 7 comments
Assignees
Labels
types and dispatch Types, subtyping and method dispatch

Comments

@lgoettgens
Copy link
Contributor

The following code snippet currently behaves differently between 1.11 and nightly:

julia> module Foo
         String(i::Int) = i
       end
Main.Foo

julia> Foo.String === String
false   # 1.11
true    # nightly

A real-world example of this is oscar-system/GAP.jl#1146 (comment). In the submodule GAP.Wrappers of GAP.jl, we create julia wrapper functions around GAP functions using a @wrap macro, and we keep the function names from GAP.
GAP has a function named String, so we create a function GAP.Wrappers.String, which worked for the last years, but no longer in nightly. There, this instead creates a new method for Core.String.

I bisected this to #57253 by @Keno.

@Keno
Copy link
Member

Keno commented Feb 6, 2025

This behavior used to be underspecified because it depends on the resolvedness of the binding

julia> module Foo
            @isdefined(String)
            String(i::Int) = i
       end

julia> Foo.String === String
true

As described in the PR, that notion has been removed in 1.12 and the behavior you want now needs to be selected explicitly:

julia> module Foo
       global String
       String(i::Int) = i
       end
Main.Foo

julia> Foo.String === String
false

@lgoettgens
Copy link
Contributor Author

Thanks for the pointer, that's something I can work with, see oscar-system/GAP.jl#1147.

Could you please make sure that this is documented somewhere for people with similar problems, e.g. in History.md? Once that is done, this issue can be closed.

@vtjnash
Copy link
Member

vtjnash commented Feb 6, 2025

@Keno this seems like a bug though, since extending a function which isn't already imported (merely exported) is supposed to force a new binding (act as if global String was called to soft-resolve it) to prevent accidental merging of unrelated functions

@Keno
Copy link
Member

Keno commented Feb 6, 2025

The issue is that String is a type, which has special logic of not needing to be explicitly or implicitly imported to be extended (#25744). We could drop that or restrict it to only cases where the import was not explicit.

@nsajko nsajko added the types and dispatch Types, subtyping and method dispatch label Feb 6, 2025
@Keno Keno self-assigned this Feb 7, 2025
@fingolfin
Copy link
Member

We tripped over this special logic recently: in a package we already had function Float64(x::OurType). A student added function ComplexF64(x::OurType) next to it. The former worked the latter not. We eventually figured out that this is because Float64 is a type and gets special treatment, while ComplexF64 is merely a "type alias" and apparently does not. I wish this wart was not there.

Alas changing this most likely would break a lot of packages.

But I wonder if the change discussed here doesn't also break a lot of packages? It certainly broke GAP.jl. Maybe we just had bad luck -- but still, doesn't NanoSoldier exist to trst such things? It wasn't tun on #57253

@Keno
Copy link
Member

Keno commented Feb 7, 2025

We decided not to run pkgeval on the individual components of that change, since some amount of breakage was expected and we wanted to get everything in for the 1.12 alpha, which is getting pkgeval runs anyway.

Keno added a commit that referenced this issue Feb 7, 2025
I was playing with strengthening the semantics around #57290.
However, the particular change I was trying turned out too breaking
(I may try a weaker version of it). Still, there were a number of
good changes found in two categories:

1. We should explicitly import types when defining constructors.
   This has been allowed for a long time, but we may want to consider
   removing it, especially given the new binding semantics which make
   it more confusing as in #57290.

2. There were a couple of places where I don't think it was intended
   for generic functions in question not to extend Base.
@Keno
Copy link
Member

Keno commented Feb 7, 2025

I played with the semantics here some more, but the nasty thing is that existing code is relying on this in both directions, although more commonly existing code expects this to extend the constructor (since types are much more common at top level and thus resolve the binding). I do agree though that there's something problematic here, so my current thought is to keep the existing behavior but give a warning telling people to disambiguate explicitly.

Keno added a commit that referenced this issue Feb 7, 2025
I was playing with strengthening the semantics around #57290. However,
the particular change I was trying turned out too breaking (I may try a
weaker version of it). Still, there were a number of good changes found
in two categories:

1. We should explicitly import types when defining constructors. This
has been allowed for a long time, but we may want to consider removing
it, especially given the new binding semantics which make it more
confusing as in #57290.

2. There were a couple of places where I don't think it was intended for
generic functions in question not to extend Base.
Keno added a commit that referenced this issue Feb 7, 2025
This adds a warning for the auto-import of types cases (#25744) that
we have long considered a bit of a bug, but didn't want to change because
it is too breaking.

The reason to do it now is that the binding rework has made this case
more problematic (see #57290). To summarize, the question is what
happens when the compiler sees `f(x) = ...` and `f` is currently
and implicitly imported binding. There are two options:

1. We add a method to the generic function referred to by `f`, or
2. We create a new generic function `f` in the current module.

Historically, case 1 has the additional complication that this
error'd unless `f` is a type. It is my impression that a lot
of existing code did not have a particularly good understanding
of the resolved-ness dependence of this behavior.

However, because case 1 errors for generic functions, it appears
that existing code generally expects case 2. On the other hand,
for types, there is existing code in both directions (#57290 is
an example of case 2; see #57302 for examples of case 1). That
said, case 1 is more common (because types tend to be resolved
because they're used in signatures at toplevel).

Thus, to retain compatibility, the current behavior on master (where
resolvedness is no longer available) is that we always choose
case 2 for functions and case 1 for types. This inconsistency is
unfortunate, but I tried resolving this in either way (making
all situations case 1 or all case 2) and the result was too breaking.

Nevertheless, it is problematic that there is existing code that
expects case 2 beavior for types and we should help users to know
what the correct way to fix it is. The proposed resolution is thus:
1. Retain case 1 behavior for types
2. Make it a warning to use, encouraging people to explicitly import,
   since we generally consider the #25744 case a bug.

Example:
```
julia> module Foo
         String(i::Int) = i
       end
WARNING: Type Core.String was auto-`import`ed in `Foo`.
NOTE: This behavior is deprecated and may change in future Julia versions.
NOTE: This behavior may have differed in Julia versions prior to 1.12 depending on binding resolution.
Hint: To retain the current behavior, add an explicit `import Core: String` in Foo.
Hint: To create a new generic function of the same name use `function String end`.
Main.Foo
```
Keno added a commit that referenced this issue Feb 7, 2025
This adds a warning for the auto-import of types cases (#25744) that
we have long considered a bit of a bug, but didn't want to change because
it is too breaking.

The reason to do it now is that the binding rework has made this case
more problematic (see #57290). To summarize, the question is what
happens when the compiler sees `f(x) = ...` and `f` is currently
and implicitly imported binding. There are two options:

1. We add a method to the generic function referred to by `f`, or
2. We create a new generic function `f` in the current module.

Historically, case 1 has the additional complication that this
error'd unless `f` is a type. It is my impression that a lot
of existing code did not have a particularly good understanding
of the resolved-ness dependence of this behavior.

However, because case 1 errors for generic functions, it appears
that existing code generally expects case 2. On the other hand,
for types, there is existing code in both directions (#57290 is
an example of case 2; see #57302 for examples of case 1). That
said, case 1 is more common (because types tend to be resolved
because they're used in signatures at toplevel).

Thus, to retain compatibility, the current behavior on master (where
resolvedness is no longer available) is that we always choose
case 2 for functions and case 1 for types. This inconsistency is
unfortunate, but I tried resolving this in either way (making
all situations case 1 or all case 2) and the result was too breaking.

Nevertheless, it is problematic that there is existing code that
expects case 2 beavior for types and we should help users to know
what the correct way to fix it is. The proposed resolution is thus:
1. Retain case 1 behavior for types
2. Make it a warning to use, encouraging people to explicitly import,
   since we generally consider the #25744 case a bug.

Example:
```
julia> module Foo
         String(i::Int) = i
       end
WARNING: Type Core.String was auto-`import`ed in `Foo`.
NOTE: This behavior is deprecated and may change in future Julia versions.
NOTE: This behavior may have differed in Julia versions prior to 1.12 depending on binding resolution.
Hint: To retain the current behavior, add an explicit `import Core: String` in Foo.
Hint: To create a new generic function of the same name use `function String end`.
Main.Foo
```
Keno added a commit that referenced this issue Feb 8, 2025
This adds a warning for the auto-import of types cases (#25744) that
we have long considered a bit of a bug, but didn't want to change because
it is too breaking.

The reason to do it now is that the binding rework has made this case
more problematic (see #57290). To summarize, the question is what
happens when the compiler sees `f(x) = ...` and `f` is currently
and implicitly imported binding. There are two options:

1. We add a method to the generic function referred to by `f`, or
2. We create a new generic function `f` in the current module.

Historically, case 1 has the additional complication that this
error'd unless `f` is a type. It is my impression that a lot
of existing code did not have a particularly good understanding
of the resolved-ness dependence of this behavior.

However, because case 1 errors for generic functions, it appears
that existing code generally expects case 2. On the other hand,
for types, there is existing code in both directions (#57290 is
an example of case 2; see #57302 for examples of case 1). That
said, case 1 is more common (because types tend to be resolved
because they're used in signatures at toplevel).

Thus, to retain compatibility, the current behavior on master (where
resolvedness is no longer available) is that we always choose
case 2 for functions and case 1 for types. This inconsistency is
unfortunate, but I tried resolving this in either way (making
all situations case 1 or all case 2) and the result was too breaking.

Nevertheless, it is problematic that there is existing code that
expects case 2 beavior for types and we should help users to know
what the correct way to fix it is. The proposed resolution is thus:
1. Retain case 1 behavior for types
2. Make it a warning to use, encouraging people to explicitly import,
   since we generally consider the #25744 case a bug.

Example:
```
julia> module Foo
         String(i::Int) = i
       end
WARNING: Type Core.String was auto-`import`ed in `Foo`.
NOTE: This behavior is deprecated and may change in future Julia versions.
NOTE: This behavior may have differed in Julia versions prior to 1.12 depending on binding resolution.
Hint: To retain the current behavior, add an explicit `import Core: String` in Foo.
Hint: To create a new generic function of the same name use `function String end`.
Main.Foo
```
Keno added a commit that referenced this issue Feb 8, 2025
This adds a warning for the auto-import of types cases (#25744) that
we have long considered a bit of a bug, but didn't want to change because
it is too breaking.

The reason to do it now is that the binding rework has made this case
more problematic (see #57290). To summarize, the question is what
happens when the compiler sees `f(x) = ...` and `f` is currently
and implicitly imported binding. There are two options:

1. We add a method to the generic function referred to by `f`, or
2. We create a new generic function `f` in the current module.

Historically, case 1 has the additional complication that this
error'd unless `f` is a type. It is my impression that a lot
of existing code did not have a particularly good understanding
of the resolved-ness dependence of this behavior.

However, because case 1 errors for generic functions, it appears
that existing code generally expects case 2. On the other hand,
for types, there is existing code in both directions (#57290 is
an example of case 2; see #57302 for examples of case 1). That
said, case 1 is more common (because types tend to be resolved
because they're used in signatures at toplevel).

Thus, to retain compatibility, the current behavior on master (where
resolvedness is no longer available) is that we always choose
case 2 for functions and case 1 for types. This inconsistency is
unfortunate, but I tried resolving this in either way (making
all situations case 1 or all case 2) and the result was too breaking.

Nevertheless, it is problematic that there is existing code that
expects case 2 beavior for types and we should help users to know
what the correct way to fix it is. The proposed resolution is thus:
1. Retain case 1 behavior for types
2. Make it a warning to use, encouraging people to explicitly import,
   since we generally consider the #25744 case a bug.

Example:
```
julia> module Foo
         String(i::Int) = i
       end
WARNING: Type Core.String was auto-`import`ed in `Foo`.
NOTE: This behavior is deprecated and may change in future Julia versions.
NOTE: This behavior may have differed in Julia versions prior to 1.12 depending on binding resolution.
Hint: To retain the current behavior, add an explicit `import Core: String` in Foo.
Hint: To create a new generic function of the same name use `function String end`.
Main.Foo
```
Keno added a commit that referenced this issue Feb 8, 2025
This adds a warning for the auto-import of types cases (#25744) that
we have long considered a bit of a bug, but didn't want to change because
it is too breaking.

The reason to do it now is that the binding rework has made this case
more problematic (see #57290). To summarize, the question is what
happens when the compiler sees `f(x) = ...` and `f` is currently
and implicitly imported binding. There are two options:

1. We add a method to the generic function referred to by `f`, or
2. We create a new generic function `f` in the current module.

Historically, case 1 has the additional complication that this
error'd unless `f` is a type. It is my impression that a lot
of existing code did not have a particularly good understanding
of the resolved-ness dependence of this behavior.

However, because case 1 errors for generic functions, it appears
that existing code generally expects case 2. On the other hand,
for types, there is existing code in both directions (#57290 is
an example of case 2; see #57302 for examples of case 1). That
said, case 1 is more common (because types tend to be resolved
because they're used in signatures at toplevel).

Thus, to retain compatibility, the current behavior on master (where
resolvedness is no longer available) is that we always choose
case 2 for functions and case 1 for types. This inconsistency is
unfortunate, but I tried resolving this in either way (making
all situations case 1 or all case 2) and the result was too breaking.

Nevertheless, it is problematic that there is existing code that
expects case 2 beavior for types and we should help users to know
what the correct way to fix it is. The proposed resolution is thus:
1. Retain case 1 behavior for types
2. Make it a warning to use, encouraging people to explicitly import,
   since we generally consider the #25744 case a bug.

Example:
```
julia> module Foo
         String(i::Int) = i
       end
WARNING: Type Core.String was auto-`import`ed in `Foo`.
NOTE: This behavior is deprecated and may change in future Julia versions.
NOTE: This behavior may have differed in Julia versions prior to 1.12 depending on binding resolution.
Hint: To retain the current behavior, add an explicit `import Core: String` in Foo.
Hint: To create a new generic function of the same name use `function String end`.
Main.Foo
```
Keno added a commit that referenced this issue Feb 8, 2025
This adds a warning for the auto-import of types cases (#25744) that
we have long considered a bit of a bug, but didn't want to change because
it is too breaking.

The reason to do it now is that the binding rework has made this case
more problematic (see #57290). To summarize, the question is what
happens when the compiler sees `f(x) = ...` and `f` is currently
and implicitly imported binding. There are two options:

1. We add a method to the generic function referred to by `f`, or
2. We create a new generic function `f` in the current module.

Historically, case 1 has the additional complication that this
error'd unless `f` is a type. It is my impression that a lot
of existing code did not have a particularly good understanding
of the resolved-ness dependence of this behavior.

However, because case 1 errors for generic functions, it appears
that existing code generally expects case 2. On the other hand,
for types, there is existing code in both directions (#57290 is
an example of case 2; see #57302 for examples of case 1). That
said, case 1 is more common (because types tend to be resolved
because they're used in signatures at toplevel).

Thus, to retain compatibility, the current behavior on master (where
resolvedness is no longer available) is that we always choose
case 2 for functions and case 1 for types. This inconsistency is
unfortunate, but I tried resolving this in either way (making
all situations case 1 or all case 2) and the result was too breaking.

Nevertheless, it is problematic that there is existing code that
expects case 2 beavior for types and we should help users to know
what the correct way to fix it is. The proposed resolution is thus:
1. Retain case 1 behavior for types
2. Make it a warning to use, encouraging people to explicitly import,
   since we generally consider the #25744 case a bug.

Example:
```
julia> module Foo
         String(i::Int) = i
       end
WARNING: Type Core.String was auto-`import`ed in `Foo`.
NOTE: This behavior is deprecated and may change in future Julia versions.
NOTE: This behavior may have differed in Julia versions prior to 1.12 depending on binding resolution.
Hint: To retain the current behavior, add an explicit `import Core: String` in Foo.
Hint: To create a new generic function of the same name use `function String end`.
Main.Foo
```
Keno added a commit that referenced this issue Feb 10, 2025
This adds a warning for the auto-import of types cases (#25744) that
we have long considered a bit of a bug, but didn't want to change because
it is too breaking.

The reason to do it now is that the binding rework has made this case
more problematic (see #57290). To summarize, the question is what
happens when the compiler sees `f(x) = ...` and `f` is currently
and implicitly imported binding. There are two options:

1. We add a method to the generic function referred to by `f`, or
2. We create a new generic function `f` in the current module.

Historically, case 1 has the additional complication that this
error'd unless `f` is a type. It is my impression that a lot
of existing code did not have a particularly good understanding
of the resolved-ness dependence of this behavior.

However, because case 1 errors for generic functions, it appears
that existing code generally expects case 2. On the other hand,
for types, there is existing code in both directions (#57290 is
an example of case 2; see #57302 for examples of case 1). That
said, case 1 is more common (because types tend to be resolved
because they're used in signatures at toplevel).

Thus, to retain compatibility, the current behavior on master (where
resolvedness is no longer available) is that we always choose
case 2 for functions and case 1 for types. This inconsistency is
unfortunate, but I tried resolving this in either way (making
all situations case 1 or all case 2) and the result was too breaking.

Nevertheless, it is problematic that there is existing code that
expects case 2 beavior for types and we should help users to know
what the correct way to fix it is. The proposed resolution is thus:
1. Retain case 1 behavior for types
2. Make it a warning to use, encouraging people to explicitly import,
   since we generally consider the #25744 case a bug.

Example:
```
julia> module Foo
         String(i::Int) = i
       end
WARNING: Type Core.String was auto-`import`ed in `Foo`.
NOTE: This behavior is deprecated and may change in future Julia versions.
NOTE: This behavior may have differed in Julia versions prior to 1.12 depending on binding resolution.
Hint: To retain the current behavior, add an explicit `import Core: String` in Foo.
Hint: To create a new generic function of the same name use `function String end`.
Main.Foo
```
Keno added a commit that referenced this issue Feb 10, 2025
This adds a warning for the auto-import of types cases (#25744) that
we have long considered a bit of a bug, but didn't want to change because
it is too breaking.

The reason to do it now is that the binding rework has made this case
more problematic (see #57290). To summarize, the question is what
happens when the compiler sees `f(x) = ...` and `f` is currently
and implicitly imported binding. There are two options:

1. We add a method to the generic function referred to by `f`, or
2. We create a new generic function `f` in the current module.

Historically, case 1 has the additional complication that this
error'd unless `f` is a type. It is my impression that a lot
of existing code did not have a particularly good understanding
of the resolved-ness dependence of this behavior.

However, because case 1 errors for generic functions, it appears
that existing code generally expects case 2. On the other hand,
for types, there is existing code in both directions (#57290 is
an example of case 2; see #57302 for examples of case 1). That
said, case 1 is more common (because types tend to be resolved
because they're used in signatures at toplevel).

Thus, to retain compatibility, the current behavior on master (where
resolvedness is no longer available) is that we always choose
case 2 for functions and case 1 for types. This inconsistency is
unfortunate, but I tried resolving this in either way (making
all situations case 1 or all case 2) and the result was too breaking.

Nevertheless, it is problematic that there is existing code that
expects case 2 beavior for types and we should help users to know
what the correct way to fix it is. The proposed resolution is thus:
1. Retain case 1 behavior for types
2. Make it a warning to use, encouraging people to explicitly import,
   since we generally consider the #25744 case a bug.

Example:
```
julia> module Foo
         String(i::Int) = i
       end
WARNING: Type Core.String was auto-`import`ed in `Foo`.
NOTE: This behavior is deprecated and may change in future Julia versions.
NOTE: This behavior may have differed in Julia versions prior to 1.12 depending on binding resolution.
Hint: To retain the current behavior, add an explicit `import Core: String` in Foo.
Hint: To create a new generic function of the same name use `function String end`.
Main.Foo
```
Keno added a commit that referenced this issue Feb 10, 2025
This adds a warning for the auto-import of types cases (#25744) that we
have long considered a bit of a bug, but didn't want to change because
it is too breaking.

The reason to do it now is that the binding rework has made this case
more problematic (see #57290). To summarize, the question is what
happens when the compiler sees `f(x) = ...` and `f` is currently and
implicitly imported binding. There are two options:

1. We add a method to the generic function referred to by `f`, or
2. We create a new generic function `f` in the current module.

Historically, case 1 has the additional complication that this error'd
unless `f` is a type. It is my impression that a lot of existing code
did not have a particularly good understanding of the resolved-ness
dependence of this behavior.

However, because case 1 errors for generic functions, it appears that
existing code generally expects case 2. On the other hand, for types,
there is existing code in both directions (#57290 is an example of case
2; see #57302 for examples of case 1). That said, case 1 is more common
(because types tend to be resolved because they're used in signatures at
toplevel).

Thus, to retain compatibility, the current behavior on master (where
resolvedness is no longer available) is that we always choose case 2 for
functions and case 1 for types. This inconsistency is unfortunate, but I
tried resolving this in either way (making all situations case 1 or all
case 2) and the result was too breaking.

Nevertheless, it is problematic that there is existing code that expects
case 2 beavior for types and we should help users to know what the
correct way to fix it is. The proposed resolution is thus:
1. Retain case 1 behavior for types
2. Make it a warning to use, encouraging people to explicitly import,
since we generally consider the #25744 case a bug.

Example:
```
julia> module Foo
         String(i::Int) = i
       end
WARNING: Type Core.String was auto-`import`ed in `Foo`.
NOTE: This behavior is deprecated and may change in future Julia versions.
NOTE: This behavior may have differed in Julia versions prior to 1.12 depending on binding resolution.
Hint: To retain the current behavior, add an explicit `import Core: String` in Foo.
Hint: To create a new generic function of the same name use `function String end`.
Main.Foo
```
KristofferC pushed a commit that referenced this issue Feb 11, 2025
This adds a warning for the auto-import of types cases (#25744) that we
have long considered a bit of a bug, but didn't want to change because
it is too breaking.

The reason to do it now is that the binding rework has made this case
more problematic (see #57290). To summarize, the question is what
happens when the compiler sees `f(x) = ...` and `f` is currently and
implicitly imported binding. There are two options:

1. We add a method to the generic function referred to by `f`, or
2. We create a new generic function `f` in the current module.

Historically, case 1 has the additional complication that this error'd
unless `f` is a type. It is my impression that a lot of existing code
did not have a particularly good understanding of the resolved-ness
dependence of this behavior.

However, because case 1 errors for generic functions, it appears that
existing code generally expects case 2. On the other hand, for types,
there is existing code in both directions (#57290 is an example of case
2; see #57302 for examples of case 1). That said, case 1 is more common
(because types tend to be resolved because they're used in signatures at
toplevel).

Thus, to retain compatibility, the current behavior on master (where
resolvedness is no longer available) is that we always choose case 2 for
functions and case 1 for types. This inconsistency is unfortunate, but I
tried resolving this in either way (making all situations case 1 or all
case 2) and the result was too breaking.

Nevertheless, it is problematic that there is existing code that expects
case 2 beavior for types and we should help users to know what the
correct way to fix it is. The proposed resolution is thus:
1. Retain case 1 behavior for types
2. Make it a warning to use, encouraging people to explicitly import,
since we generally consider the #25744 case a bug.

Example:
```
julia> module Foo
         String(i::Int) = i
       end
WARNING: Type Core.String was auto-`import`ed in `Foo`.
NOTE: This behavior is deprecated and may change in future Julia versions.
NOTE: This behavior may have differed in Julia versions prior to 1.12 depending on binding resolution.
Hint: To retain the current behavior, add an explicit `import Core: String` in Foo.
Hint: To create a new generic function of the same name use `function String end`.
Main.Foo
```

(cherry picked from commit 8c62f42)
dlfivefifty pushed a commit to JuliaArrays/LazyArrays.jl that referenced this issue Feb 15, 2025
* Explicitly import `ArrayLayouts.MemoryLayout`

Prevent name ambiguity, adapt to changes in nightly Julia, preventing a
warning during precompilation.

See:
* JuliaLang/julia#25744
* JuliaLang/julia#57290
* JuliaLang/julia#57311

* apply suggestion
KristofferC pushed a commit that referenced this issue Feb 21, 2025
I was playing with strengthening the semantics around #57290. However,
the particular change I was trying turned out too breaking (I may try a
weaker version of it). Still, there were a number of good changes found
in two categories:

1. We should explicitly import types when defining constructors. This
has been allowed for a long time, but we may want to consider removing
it, especially given the new binding semantics which make it more
confusing as in #57290.

2. There were a couple of places where I don't think it was intended for
generic functions in question not to extend Base.

(cherry picked from commit 778e079)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
types and dispatch Types, subtyping and method dispatch
Projects
None yet
Development

No branches or pull requests

5 participants