Hi Jacob

Apologies for the delay in answering…

> On Dec 9, 2015, at 2:00 AM, Jacob Bandes-Storch via swift-dev 
> <swift-dev@swift.org> wrote:
> 
> I'm looking into allowing extensions like "extension Array where Element == 
> Int" — relaxing the restriction that prevents generic function/type 
> definitions from having concrete types specified. (Slava mentioned that this 
> is a favorable language change without need for the evolution process: 
> https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/000865.html
>  
> <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/000865.html>)
> 
> I'd like to run my thoughts by some people who know what they're talking 
> about, before diving in too far :-)
> 
> The diagnostic in question is
>     diag::requires_generic_param_made_equal_to_concrete
>     "same-type requirement makes generic parameter %0 non-generic"
> which is emitted from ArchetypeBuilder::addSameTypeRequirementToConcrete().
> 
> Multiple code paths reach checkGenericParamList() which adds the requirements:
> 
>     - DeclChecker::visitClassDecl
>        -> TC::validateDecl case for struct/class/enum
>        -> TC::validateGenericTypeSignature
>        -> TC::validateGenericSignature
>        -> TC::checkGenericParamList
> 
>     - DeclChecker::visitFuncDecl
>        -> TC::validateGenericFuncSignature
>        -> TC::checkGenericFuncSignature
>        -> TC::checkGenericParamList
> 
>     - DeclChecker::visitExtensionDecl
>        -> TC::validateExtension
>        -> TC::checkExtensionGenericParams
>        -> TC::validateGenericSignature
>        -> TC::checkGenericParamList

There’s a path through TypeChecker::handleSILGenericParams() that you need to 
consider, which is triggered when parsing SIL. It’s an odd case because you get 
all of the generic parameter lists up front. I suspect you would just always 
allow type parameters to be equated with concrete types from here.

> (Mildly confusing to have both "validate" and "check" variants, but only in 
> some of the cases…?)

Having the “check” in the middle/bottom is the exceptional part here. The rough 
intent at one point was that “validate” was basic validation of a declaration 
so that it could be useful from elsewhere, while “check” checks all of the 
semantic constraints to see if the declaration was well-formed. But, it wasn’t 
followed that carefully, and this turns out to be a not-terribly-useful 
distinction. Rather, we should be handling more fine-grained type checking 
requests iteratively, per

        
https://github.com/apple/swift/blob/master/docs/proposals/DeclarationTypeChecker.rst

But that’s off on the horizon. Back to your actual question…

> 
> It's only in the 3rd case (extensions) that we want to allow the requirements 
> to make the types fully bound/concrete.

Okay, so this means that your example would be accepted, but something like:

        class X<T where T == Int> {
        }

would remain ill-formed, as would

        class X<T> {
          func f<U where T == Int>() { … }
        }

> So here's what I propose doing:
> 
>   1. The ArchetypeBuilder needs to know whether this is allowed. So add a 
> boolean field, called e.g. AllowConcreteRequirements.
> 
>   2a. Pass false to the ArchetypeBuilder created in 
> validateGenericFuncSignature.
>   2b. Pass the boolean through as a param to validateGenericSignature, where 
> the ArchetypeBuilder is created. (validateGenericSignature is used in both 
> class & extension cases). In particular, pass true from 
> checkExtensionGenericParams and false from validateGenericTypeSignature.
> 
>   3. Skip the error if AllowConcreteRequirements was true. Instead allow the 
> requirement to be added (and fix any fallout issues from this change, add 
> tests, yadda yadda).
> 
> How does that sound?

You may need to make the AllowConcreteRequirements flag indicate the depth at 
which generic parameters are allowed to be made equivalent to concrete types. 
Consider, for example:

        class X<T> { }

        extension X<T> {
          func f<U where T == Int>() { }
        }

Presumably that should be ill-formed still, and that one would properly have to 
write

        extension X where T == Int {
          func f() { }
        }

(which is much clearer anyway, of course!).

And although it’s broken today for other reasons, the depth of the generic 
parameters that are allowed to be made equivalent to concrete types could be > 
0 if you had a generic type nested within a generic type, e.g.,

        class X<T> {
                class Y<U> {
                }
        }

        extension X.Y where T == Int, U == String { … } // okay; both depths 0 
and 1 are okay to bind to concrete types


> Also, is there any desire to remove this restriction?
>     diag::extension_specialization
>     "constrained extension must be declared on the unspecialized generic type 
> %0 with constraints specified by a 'where' clause"
> It seems natural to want to allow "extension Array<Int>", but I'm afraid it 
> may complicate things significantly, especially if we only wanted to allow 
> this syntax in the case of .

I’d want this bit of syntax to go through swift-evolution, even though I 
suspect it would be very well-received. There’s a syntactic symmetry question 
it invokes, because

        struct X<T> { … }

introduces T as a type parameter while

        extension X<T> { … }

looks up T in the enclosing scope. That potentially becomes somewhat ambiguous 
if we want to be able to introduce new type parameters in a specific extension, 
which comes up if we implement something akin to C++’s partial specialization. 
For example, extending an array of optional values:

        extension X<T?> { } // no, looks up T in the enclosing scope

There are syntactic ways around this. For example, putting the type parameters 
after “extension”:

        extension<T> Array<T?> { }

or even spell it with the syntactic sugar:

        extension<T> [T?] {
                func nonnilValues() -> [T] {
                }
        }

to open the flood gates yet further.

I don’t see any rush to push the syntax through swift-evolution: you can work 
on the implementation using the “Element == Int” syntax (which should work 
regardless of whether there’s a more concise way to spell the same intent) and 
discuss/add the syntactic sugar later.

        - Doug

_______________________________________________
swift-dev mailing list
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev

Reply via email to