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
Swift Xcode 7 Beta 5 Type Cannot Refer to Itself as a Requirement
Arkit/Scenekit on iOS 14 Throws New Warning (Metal)
Problem with Swiftui and Foreach on Xcode Playground
Why Swift Disallows Weak Reference for Non-Optional Type
Bool.Hashvalue Valid to Convert to Int
Adding Constraints Programmatically in UIview with UItextview
#If Canimport(Coreimage) Not Working in Swift Package Manager
iOS-Charts Error: Thread1: Exc_Bad_Access (Code=2, Address=0X2A0C220)
3 Component Dynamic Multi UIpickerview Swift
I Opened My App in Xcode 10 and Now I Have Errors in 9.4.1: Sdkapplicationdelegate (Facebookcore)
Saving And/Or Querying User Display Names in Firebase Using Caseinsensitive
Swift: Mkannotation Long Title Text
Weird Toolbar with Nested Conditionals Behavior
How to Create Generic Closures in Swift