Type inference not working when assigning to a type alias

Sorry I couldn't figure out a more descriptive title for this. The integer generic parameters proposal motivated me to try some rudimentary metaprogramming.

I tried to implement something similar to C++'s std::conditional like so (compiler explorer link).

// the static member `value` is inconsequential for now
protocol IntegerValue {
    static var value: Int { get }
}
struct ZeroValue : IntegerValue {
    static let value = 0
}
struct NonZeroValue : IntegerValue {
    static let value = 1
}
// With Integer parameters, first parameter will let B: Int
struct If<B, Consequent, Alternative> {
}
extension If where B == NonZeroValue {
    typealias T = Consequent
}
extension If where B == ZeroValue {
    typealias T = Alternative
}

typealias cond = ZeroValue 
typealias type1 = If<cond, Int, Float>.T

The last line results in an ambiguous type name error

<source>:22:40: error: ambiguous type name 'T' in 'If<cond, Int, Float>' (aka 'If<ZeroValue, Int, Float>')
typealias type1 = If<cond, Int, Float>.T
                  ~~~~~~~~~~~~~~~~~~~~ ^

and includes a note

<source>:15:15: note: found candidate with type 'If<cond, Int, Float>.T' (aka 'Int')
    typealias T = Consequent
              ^

But, this is the extension for NonZeroValue, not ZeroValue.

And, for some reason when I try to get a value directly instead of assigning to a typealias, e.g. let value = If<cond, Int, Float>.T(), this compiles just fine (compiler explorer link).

I have the following questions

  1. Why is the extension with the formal parameter constraint B == NonZeroValue a candidate in the first place? Shouldn't it be discarded since the actual parameter B is ZeroValue?
  2. Why does this only happen when assigning the type to a typealias?
1 Like

I don't know all the ins and outs here but IIRC there are some cases where name lookup is deliberately limited in the information it's able to take into account in order to prevent circularity/layering issues. It's possible that's what you're butting up against? Name lookup in expression type checking happens later so it's not surprising to me that the If<cond, Int, Float>.T() form works fine.

cc @Slava_Pestov who is much more knowledgable than myself: is this sort of construction something that's expected to fail, or is this a bug?

1 Like

Type resolution picks the declaration first, and then checks generic requirements, if any. So using requirements to resolve ambiguity in this way is not possible.

In expression context, references to types go through the usual overload resolution mechanism, so your construction works.

There’s no inherent reason for this to be the case, it’s just how it is. A long time ago the compiler didn’t check requirements in type resolution at all, and then I added the check in the most convenient spot, which happens to be after we already pick a declaration.

This could be made to work but it would need some refactoring.

1 Like