mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
279 lines
11 KiB
ReStructuredText
279 lines
11 KiB
ReStructuredText
:orphan:
|
|
|
|
.. warning:: This proposal was rejected. We explored a number of alternate
|
|
syntaxes for method names that both allowed describing arguments and were
|
|
compatible with Objective-C selectors.
|
|
|
|
Summary
|
|
-------
|
|
|
|
We make the following incremental language changes:
|
|
|
|
- We make it so that selector-style declaration syntax declares a
|
|
**selector name** for a method. ``func foo(_ x:T) bar(y:U)`` declares a method
|
|
named with a new selector reference syntax, ``self.foo:bar:``. This method
|
|
cannot be referenced as ``foo``; it can only be referenced as
|
|
``self.foo:bar:``, or with keyword application syntax,
|
|
``self.foo(x, bar: y)``.
|
|
|
|
- We change keywords in parens, as in ``(a: x, b: y)`` to be a feature of
|
|
apply syntax, instead of a feature of tuple literals. Name lookup changes to
|
|
resolve keywords according to the function declaration rather than to the
|
|
context tuple type. For tuple-style declarations ``func foo(_ a: Int, b: Int)``,
|
|
keywords are optional and can be reordered. For selector-style declarations
|
|
``func foo(_ a: Int) bar(b: Int)``, the keywords are required and must appear
|
|
in order.
|
|
|
|
- With keyword arguments no longer reliant on tuple types, we simplify the
|
|
tuple type system by removing keywords from tuple types.
|
|
|
|
Keyword Apply Syntax
|
|
--------------------
|
|
::
|
|
|
|
expr-apply ::= expr-postfix '(' (kw-arg (',' kw-arg)*)? ')'
|
|
kw-arg ::= (identifier ':')? expr
|
|
|
|
// Examples
|
|
foo()
|
|
foo(1, 2, 3)
|
|
foo(1, bar: 2, bas: 3)
|
|
foo!(1, bar: 2, bas: 3)
|
|
a.foo?(1, bar: 2, bas: 3)
|
|
|
|
Keyword syntax becomes a feature of apply expressions. When a named
|
|
function or method is applied, a declaration is looked up using the name and any
|
|
keyword arguments that are provided. An arbitrary expression that produces
|
|
a function result may be applied, but cannot be used with keyword arguments.
|
|
|
|
Named Application Lookup
|
|
------------------------
|
|
|
|
A named application is matched to a declaration using both the base name, which
|
|
appears to the left of the open parens, and the keyword arguments that are
|
|
provided. The matching rules are different for "tuple-style" and
|
|
"keyword-style" declarations:
|
|
|
|
Finding the base name
|
|
`````````````````````
|
|
|
|
The callee of an apply is an arbitrary expression. If the expression is a
|
|
standalone declaration reference or a member reference, then the apply is a
|
|
**named application**, and the name is used as the **base name** during function
|
|
name lookup, as described below. Certain expression productions are looked
|
|
through when looking for a base name:
|
|
|
|
- The ``Optional`` postfix operators ``!`` and ``?``.
|
|
``foo.bar!()`` and ``bas?()`` are named applications, as is ``foo?!?()``.
|
|
- Parens. ``(foo.bar)()``` is a named application.
|
|
|
|
These productions are looked through to arbitrary depth, so ``((foo!?)?!)()``
|
|
is also a named application.
|
|
|
|
Unnamed applications cannot use keyword arguments.
|
|
|
|
Tuple-Style Declarations
|
|
````````````````````````
|
|
|
|
A tuple-style declaration matches a named application if:
|
|
|
|
- The base name matches the name of the declaration::
|
|
|
|
func foo(_ x: Int, y: Int) {}
|
|
foo(1, 2) // matches
|
|
bar(1, 2) // doesn't match
|
|
|
|
- Positional arguments, that is, arguments without keywords, must be convertible
|
|
to the type of the corresponding positional parameter of the declaration::
|
|
|
|
func foo(_ x: Int, y: Int) {}
|
|
foo(1, 2) // matches
|
|
foo("one", 2) // doesn't match
|
|
foo(1, "two") // doesn't match
|
|
foo("one", "two") // doesn't match
|
|
|
|
- Keyword names, if present, must match the declared name of a parameter in the
|
|
declaration, and the corresponding argument must be convertible to the type
|
|
of the parameter in the declaration. The same keyword name may not be used
|
|
multiple times, and the same argument cannot be provided positionally and
|
|
by keyword. All keyword arguments must follow all positional arguments::
|
|
|
|
func foo(_ x: Int, y: String, z: UnicodeScalar) {}
|
|
foo(1, "two", '3') // matches
|
|
foo(1, "two", z: '3') // matches
|
|
foo(1, y: "two", '3') // invalid, positional arg after keyword arg
|
|
foo(1, z: '3', y: "two") // matches
|
|
foo(z: '3', x: 1, y: "two") // matches
|
|
foo(z: '3', q: 1, y: "two") // doesn't match; no keyword 'q'
|
|
foo(1, "two", '3', x: 4) // doesn't match; 'x' already given positionally
|
|
foo(1, "two", z: '3', z: '4') // doesn't match; multiple 'z'
|
|
|
|
As an exception, a trailing closure argument always positionally matches
|
|
the last declared parameter, and can appear after keyword arguments::
|
|
|
|
func foo(_ x: Int, y: String, f: () -> ()
|
|
foo(y: "two", x: 1) { } // matches
|
|
|
|
- If the final declared keyword parameter takes a variadic argument, the keyword
|
|
in the argument list may be followed by multiple
|
|
non-keyworded arguments. All arguments up to either the next keyword or
|
|
the end of the argument list become that keyword's argument::
|
|
|
|
func foo(_ x: Int, y: String, z: UnicodeScalar...) {}
|
|
foo(1, "two", '3', '4', '5') // matches, z = ['3', '4', '5']
|
|
foo(1, "two", z: '3', '4', '5') // same
|
|
foo(1, z: '3', '4', '5', y: "two") // same
|
|
|
|
- If the base name is the name of a non-function definition of function type,
|
|
the input types match the input type of the referenced function value, and
|
|
there are no keyword arguments::
|
|
|
|
var foo: (Int, Int) -> ()
|
|
foo(1, 2) // matches
|
|
foo(x: 1, y: 2) // doesn't match
|
|
|
|
Selector-Style Declarations
|
|
```````````````````````````
|
|
|
|
A selector-style declaration matches a named application if:
|
|
|
|
- The expression must provide keywords for all of its arguments but the first.
|
|
It must *not* provide a keyword for the first argument::
|
|
|
|
func foo(_ x: Int) bar(y: String) bas(z: UnicodeScalar) {}
|
|
foo(1, "two", '3') // doesn't match; no keywords
|
|
foo(x: 1, bar: "two", bas: '3') // doesn't match; first keyword provided
|
|
foo(1, bar: "two", bas: '3') // matches
|
|
|
|
- The base name of the apply expression must match the first declared selector
|
|
piece. The subsequent argument keyword names must match the remaining selector
|
|
pieces in order. The same keyword name may be used multiple times, to refer
|
|
to selector pieces with the same name. The argument values must be convertible
|
|
to the declared types of each selector piece's parameter::
|
|
|
|
func foo(_ x: Int) bar(y: String) bas(z: UnicodeScalar) {}
|
|
foo(1, bar: "two", bas: '3') // matches
|
|
foo(1, bas: '3', bar: "two") // doesn't match; wrong selector piece order
|
|
foo(1, bar: '2', bas: "three") // doesn't match; wrong types
|
|
|
|
func foo(_ x: Int) foo(y: String) foo(z: UnicodeScalar) {}
|
|
foo(1, foo: "two", foo: '3') // matches
|
|
|
|
- If the final selector piece declares a variadic parameter, then the keyword
|
|
in the call expression may be followed by multiple arguments. All arguments
|
|
up to the end of the argument list become the keyword parameter's value.
|
|
(Because of strict keyword ordering, additional keywords may not follow.)
|
|
For example::
|
|
|
|
func foo(_ x: Int) bar(y: String...) {}
|
|
|
|
foo(1, bar: "two", "three", "four") // matches, y = ["two", "three", "four"]
|
|
|
|
- If the final selector piece declares a function parameter, then the function
|
|
can be called using trailing closure syntax omitting the keyword. The keyword
|
|
is still required when trailing closure syntax is not used. For example::
|
|
|
|
func foo(_ x: Int) withBlock(f: () -> ())
|
|
|
|
foo(1, withBlock: { }) // matches
|
|
foo(1, { }) // doesn't match
|
|
foo(1) { } // matches
|
|
|
|
Trailing closure syntax can introduce ambiguities when selector-style
|
|
functions differ only in their final closure selector piece::
|
|
|
|
func foo(_ x: Int) onCompletion(f: () -> ())
|
|
func foo(_ x: Int) onError(f: () -> ())
|
|
|
|
foo(1) { } // error: ambiguous
|
|
|
|
Duplicate Definitions
|
|
---------------------
|
|
|
|
Tuple-Style Declarations
|
|
````````````````````````
|
|
|
|
Keyword names are part of a tuple-style declaration, but they are not part
|
|
of the declaration's name, they are not part of the declaration's type, and
|
|
they are not part of the declaration's ABI. Two tuple-style declarations that
|
|
differ only in keyword names are considered duplicates::
|
|
|
|
// Error: Duplicate definition of foo(Int, Int) -> ()
|
|
func foo(_ a: Int, b: Int) {}
|
|
func foo(_ x: Int, y: Int) {}
|
|
|
|
Selector-Style Declarations
|
|
```````````````````````````
|
|
|
|
The name of a selector-style declaration comprises all of its selector pieces in
|
|
declaration order. Selector-style declarations can be overloaded by selector
|
|
name, by selector order, and by type::
|
|
|
|
// OK, no duplicates
|
|
func foo(_ x: Int) bar(y: Int) bas(z: Int)
|
|
func foo(_ x: Int) bar(y: Int) zim(z: Int)
|
|
func foo(_ x: Int) bas(y: Int) bar(z: Int)
|
|
func foo(_ x: Int) bar(y: Int) bas(z: Float)
|
|
|
|
Tuple- and selector-style declarations are not considered duplicates, even if
|
|
they can match the same keywords with the same types::
|
|
|
|
// OK, not duplicates
|
|
func foo(_ x: Int, bar: Int)
|
|
func foo(_ x: Int) bar(x: Int)
|
|
|
|
Unapplied Name Lookup
|
|
---------------------
|
|
|
|
An unapplied declaration reference ``identifier`` or member reference
|
|
``obj.identifier`` finds any tuple-style declaration whose name matches the
|
|
referenced name. It never finds selector-style declarations::
|
|
|
|
func foo(_ a: Int, b: Int) {}
|
|
func foo(_ a: Int) bar(b: Int) {}
|
|
|
|
var f = foo // Finds foo(Int, Int) -> (), not foo:bar:
|
|
|
|
Selector Name Lookup
|
|
--------------------
|
|
::
|
|
|
|
expr-selector-member-ref ::= expr-postfix '.' identifier ':' (identifier ':')+
|
|
|
|
Unapplied selector-style declarations can be referenced as a member of their
|
|
enclosing context using selector member reference expressions. The name must
|
|
consist of at least two selector pieces, each followed by a colon. (A single
|
|
identifier followed by a colon, such as ``foo.bar:``, is parsed as a normal
|
|
member reference ``foo.bar`` followed by a colon.) A selector member reference
|
|
expression finds any selector-style declarations whose selector pieces match the
|
|
named selector pieces in order::
|
|
|
|
class C {
|
|
func foo(_ a: Int) bar(b: Int) bas(c: Int)
|
|
func foo(_ a: Int) bas(b: Int) bar(c: Int)
|
|
|
|
func foo(_ a: Int, bar: Int, bas: Int)
|
|
}
|
|
|
|
var c: C
|
|
|
|
c.foo:bar:bas: // Finds c.foo:bar:bas: (not c.foo or c.foo:bas:bar:)
|
|
c.foo:bas:bar: // Finds c.foo:bas:bar:
|
|
c.foo // Finds c.foo
|
|
|
|
QoI Issues
|
|
----------
|
|
|
|
Under this proposal, keyword resolution relies on being able to find a named
|
|
function declaration. This means that keywords cannot be used with arbitrary
|
|
expressions of function type.
|
|
We however still need to parse keywords in nameless applications for recovery.
|
|
There are also functional operators like ``!`` and ``?`` that we need to
|
|
forward keyword arguments through. Are there others? What about parens?
|
|
``(foo)(bar: x)`` should probably work.
|
|
|
|
This proposal also prevents a single-element name from being referenced with
|
|
selector syntax as ``foo.bar:``. For QoI, we should recognize attempts to
|
|
reference a member in this way, such as ``if var f = foo.bar: {}`` or
|
|
``[foo.bar:: bas]``, and fixit away the trailing colon.
|