How Does Swift Disambiguate Type Arguments in Expression Contexts

How does Swift disambiguate Type Arguments in Expression Contexts?

Thanks to @Martin R, I found the relevant part of the compiler source code, which contains a comment that explains how it resolves the ambiguity.

swift/ParseExpr.cpp, line 1533:

///   The generic-args case is ambiguous with an expression involving '<'
/// and '>' operators. The operator expression is favored unless a generic
/// argument list can be successfully parsed, and the closing bracket is
/// followed by one of these tokens:
/// lparen_following rparen lsquare_following rsquare lbrace rbrace
/// period_following comma semicolon

Basically, the compiler attempts to parse a list of types and then checks the token after the closing angle bracket. If that token is

  • a closing parenthesis, bracket or brace,
  • an opening parenthesis, bracket or period without whitespace between itself and the closing angle bracket (>(, >[, but not > (, > [),
  • an opening brace or
  • a comma or semicolon

It parses the expression as a generic call, otherwise it parses it as one or more relational expressions.

As described in the book Annotated C#, the problem is solved in a similar way in C#.

How do I disambiguate this call to a generic function with a function parameter in Swift?

The issue here is not the generic parameters. Your first and second attempt would tell the compiler what type T should be.

The issue is the value you pass as callback, which has the following signature:

(Parser) -> () throws -> T

You are passing in self.parseType which has the following signature:

() throws -> String

What would work is using Self.parseType (notice the capital S) or Parser.parseType as value for callback.

Alternatively, you could define maybe like this:

public func maybe<T>(_ callback: (Parser) throws -> T) -> T? {
do {
return try callback(self)
} catch {
return nil
}
}

And then call it like this:

let type = self.maybe { try $0.parseType() }

How can I disambiguate a type and a module with the same name?

The type can be disambiguated using the little-known import (class|struct|func|protocol|enum) Module.Symbol syntax.

import struct BTree.OrderedSet

From this point on, OrderedSet unambiguously refers to the one in BTree.

If this would still be ambiguous or sub-optimal in some files, you can create a Swift file to rename imports using typealiases:

// a.swift
import struct BTree.OrderedSet
typealias BTreeOrderedSet<T> = BTree.OrderedSet<T>

 

// b.swift
let foo = OrderedSet<Int>() // from Foundation
let bar = BTreeOrderedSet<Int>() // from BTree

There was a new syntax discussed for Swift 3, but it fell through.

Adding inline explicit type annotations to a closure with a return value but no input parameters in Swift?

The empty tuple () indicates an empty argument list:

let f = { () -> Double in
1 + 1
}

print(f()) // 2.0

() can be both a type (identical to Void) and an instance of that type:

let a: Void = ()
let b: () = ()
print(type(of: a) == type(of: b)) // true

Swift 3 needs more info, to infer a parameter?

That is a name collision between the existing next method of UIResponder, and your extension method. self.next inside
your method refers to the (generic) method itself.

Renaming the extension makes it compile:

public extension UIResponder {
public func nextOfType<T>() -> T? {
guard let responder = self.next
else { return nil }
return (responder as? T) ?? responder.nextOfType()
}
}

And even if you didn't ask for it, here is an iterative instead of
recursive version :)

public extension UIResponder {
public func nextOfType<T>() -> T? {
var current = self
while let responder = current.next {
if let match = responder as? T {
return match
}
current = responder
}
return nil
}
}

Using init() in map()

Simplified repro:

String.init as Character -> String
// error: type of expression is ambiguous without more context

This is because String has two initializers that accept one Character:

init(_ c: Character)
init(stringInterpolationSegment expr: Character)

As far as I know, there is no way to disambiguate them when using the initializer as a value.

As for (1...100).map(String.init), String.init is referred as Int -> String. Although there are two initializers that accept one Int:

init(stringInterpolationSegment expr: Int)
init<T : _SignedIntegerType>(_ v: T)

Generic type is weaker than explicit type. So the compiler choose stringInterpolationSegment: one in this case. You can confirm that by command + click on .init.

What's the difference between an argument and a parameter?

A parameter is a variable in a method definition. When a method is called, the arguments are the data you pass into the method's parameters.

public void MyMethod(string myParam) { }

...

string myArg1 = "this is my argument";
myClass.MyMethod(myArg1);


Related Topics



Leave a reply



Submit