-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add support for unqualified selectors #23801
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
base: main
Are you sure you want to change the base?
Conversation
The CI fails as we cannot bootstrap. The following snippet doesn't represent chained applications at the moment, but rather two expressions. a.foo(0) // first expression
.bar(1) // second expression (unqualified selection) |
I have thought some more about it, and tried to nail down my unease with the proposal. I think it is this:
Also, there is the problem as you noted that a.b
.c is valid code today and we need to keep it. So, as a counter-proposal maybe we should restrict it to enums and drop the leading When resolving an unqualified identifier, if all other schemes have failed, check whether the target type is an That would let us write simply Why single out enumerations? They define a lot of names that are usually easily recognizable and where the nesting is forced on us. An On the other hand, if we allowed this everywhere it would essentially allow full scope injection which is very powerful and can quickly lead to incomprehensible code. |
The use of elided qualification goes way beyond just enum cases in Swift and I do believe the feature makes sense in Scala too, pushing in the idea of promoting concision. The general rule in Swift is that if there's an expression of the form
The constructors of an enum in Swift satisfy these requirements because this code: enum Color {
case rgb(Int, Int, Int)
case hsv(Int, Int, Int)
} is roughly equivalent to this: sealed trait Color
object Color:
case class RGB(r: Int, g: Int: b: Int) extends Color
def rgb(r: Int, g: Int: b: Int): Color = RGB(r, g, b)
case class HSV(h: Int, s: Int: v: Int) extends Color
def hsv(h: Int, s: Int: v: Int): Color = HSV(h, s, v) So when we write It is easy to see how this technique generalizes to other use cases. For example, any constructor defined in a companion object can be applied that way. In Swift, that can prove particularly useful in code that sets up many instances. // Library code
struct Vector2 {
let x, y: Double
static let zero = Vector2(x: 0, y: 0)
static let unitX = Vector2(x: 1, y: 0)
static func + (lhs: Self, rhs: Self) -> Self { ... }
}
struct Circle {
let origin: Vector2
let radius: Double
static func enclosing(_ cs: [Circle]) -> Circle { ... }
}
typealias VennDiagram = Array<(Circle, String)>
// User code
let c = Circle(origin: .zero, radius: .pi)
let d = Circle(origin: . unitX, radius: .pi * 2)
let xs: VennDiagram = [
(c, "A"),
(.enclosing([c, d]), "B")
] Another advantage is that companion objects become a very convenient "natural" namespace, which can be leveraged to create more meaningful helper types. It is not rare in Swift to write things like One can certainly write very similar code in Scala using clever combinations of imports and exports but
Clearly in the above example no one wants to import Similarly,
This observation is rather subjective and somewhat disproved by empirical evidence in Swift.
Doing so would discourage certain case names when those could clash with other similarly named types or enum cases. For example: enum TypedTree:
case App(lhs: TypedTree, rhs: TypedTree)
case Abs(x: String, t: Type, e: TypedTree)
case Var(x: String)
enum Tree:
case App(lhs: Tree, rhs: Tree)
case Abs(x: String, e: Tree)
case Var(x: String) |
TODO:
case .some(v) => ???
,case _: .InnerClass
, ...