@@ -126,23 +126,54 @@ extension Parser {
126126
127127 let someOrAny = self . consume ( if: . keyword( . some) , . keyword( . any) )
128128
129- var base = self . parseSimpleType ( )
130- guard self . atContextualPunctuator ( " & " ) else {
131- if let someOrAny {
132- return RawTypeSyntax (
133- RawSomeOrAnyTypeSyntax (
134- someOrAnySpecifier: someOrAny,
135- constraint: base,
129+ // Consumes all trailing optional sugar (`?` or `!`), wrapping the base type
130+ // in the correct optional or IUO syntax node types.
131+ func wrapWhileConsumingOptionalSugar( _ base: RawTypeSyntax ) -> RawTypeSyntax {
132+ var base = base
133+ var loopProgress = LoopProgressCondition ( )
134+
135+ func tagWithDiagnosticIfAmbiguous( _ token: RawTokenSyntax ) -> RawTokenSyntax {
136+ guard base. as ( RawSomeOrAnyTypeSyntax . self) ? . constraint. is ( RawCompositionTypeSyntax . self) == true else {
137+ return token
138+ }
139+ return token. tokenView. withTokenDiagnostic (
140+ tokenDiagnostic: TokenDiagnostic ( . ambiguousOptionalAfterSomeOrAnyComposition, byteOffset: 0 ) ,
141+ arena: self . arena
142+ )
143+ }
144+
145+ while self . hasProgressed ( & loopProgress) {
146+ if self . at ( . postfixQuestionMark) {
147+ var optional = self . parseOptionalType ( base)
148+ optional = RawOptionalTypeSyntax (
149+ wrappedType: optional. wrappedType,
150+ questionMark: tagWithDiagnosticIfAmbiguous ( optional. questionMark) ,
136151 arena: self . arena
137152 )
138- )
139- } else {
140- return base
153+ base = RawTypeSyntax ( optional)
154+ } else if self . at ( . exclamationMark) {
155+ var iuo = self . parseImplicitlyUnwrappedOptionalType ( base)
156+ iuo = RawImplicitlyUnwrappedOptionalTypeSyntax (
157+ wrappedType: iuo. wrappedType,
158+ exclamationMark: tagWithDiagnosticIfAmbiguous ( iuo. exclamationMark) ,
159+ arena: self . arena
160+ )
161+ base = RawTypeSyntax ( iuo)
162+ } else {
163+ break
164+ }
141165 }
166+ return base
142167 }
143168
144- var elements = [ RawCompositionTypeElementSyntax] ( )
169+ var base = self . parseSimpleType ( allowTrailingOptional: someOrAny == nil )
170+
171+ // This is the happy path for compositions, such as `P & Q` or `some P & Q`.
172+ // If we have a composition that is interrupted by trailing optional sugar
173+ // (e.g., `some P? & Q`), this check fails and we fall through to the
174+ // recovery path below.
145175 if let firstAmpersand = self . consumeIfContextualPunctuator ( " & " ) {
176+ var elements = [ RawCompositionTypeElementSyntax] ( )
146177 elements. append (
147178 RawCompositionTypeElementSyntax (
148179 type: base,
@@ -154,7 +185,32 @@ extension Parser {
154185 var keepGoing : RawTokenSyntax ? = nil
155186 var loopProgress = LoopProgressCondition ( )
156187 repeat {
157- let elementType = self . parseSimpleType ( )
188+ var elementType : RawTypeSyntax
189+ if someOrAny == nil {
190+ elementType = self . parseSimpleType ( )
191+ } else {
192+ // If trailing `?` or `!` is present, determine whether it should bind
193+ // to the element type instead of the entire `some`/`any` type. This
194+ // happens in two cases:
195+ //
196+ // 1. The optional is the base of a member type construction, like
197+ // `some P?.Q`.
198+ // 2. The user wrote the invalid type construction `some P? & Q`,
199+ // which we still want to parse syntactically as a composition for
200+ // recovery purposes; the type checker will reject it.
201+ elementType = self . parseSimpleType ( allowTrailingOptional: false )
202+ let shouldBindOptionalToElement = self . withLookahead {
203+ var hasOptional = false
204+ while $0. consume ( if: . postfixQuestionMark, . exclamationMark) != nil {
205+ hasOptional = true
206+ }
207+ return hasOptional && ( $0. atContextualPunctuator ( " & " ) || $0. at ( . period) )
208+ }
209+ if shouldBindOptionalToElement {
210+ elementType = wrapWhileConsumingOptionalSugar ( elementType)
211+ }
212+ }
213+
158214 keepGoing = self . consumeIfContextualPunctuator ( " & " )
159215 elements. append (
160216 RawCompositionTypeElementSyntax (
@@ -173,33 +229,72 @@ extension Parser {
173229 )
174230 }
175231
232+ // If `some`/`any` was present, apply it to the base, and *then* apply any
233+ // trailing optionals.
176234 if let someOrAny {
177- return RawTypeSyntax (
235+ base = RawTypeSyntax (
178236 RawSomeOrAnyTypeSyntax (
179237 someOrAnySpecifier: someOrAny,
180238 constraint: base,
181239 arena: self . arena
182240 )
183241 )
184- } else {
185- return base
242+ base = wrapWhileConsumingOptionalSugar ( base)
243+ }
244+
245+ // Recovery path for broken compositions, like `some P? & Q ...`: consume
246+ // the rest of the composition with the `some` already applied to the first
247+ // element (`(some P)? & Q ...`). This ensures we produce a reasonable AST,
248+ // and the type checker will reject it.
249+ if let firstAmpersand = self . consumeIfContextualPunctuator ( " & " ) {
250+ var elements = [ RawCompositionTypeElementSyntax] ( )
251+ elements. append (
252+ RawCompositionTypeElementSyntax (
253+ type: base,
254+ ampersand: firstAmpersand,
255+ arena: self . arena
256+ )
257+ )
258+
259+ var keepGoing : RawTokenSyntax ? = nil
260+ var loopProgress = LoopProgressCondition ( )
261+ repeat {
262+ let elementType = self . parseSimpleType ( )
263+ keepGoing = self . consumeIfContextualPunctuator ( " & " )
264+ elements. append (
265+ RawCompositionTypeElementSyntax (
266+ type: elementType,
267+ ampersand: keepGoing,
268+ arena: self . arena
269+ )
270+ )
271+ } while keepGoing != nil && self . hasProgressed ( & loopProgress)
272+
273+ base = RawTypeSyntax (
274+ RawCompositionTypeSyntax (
275+ elements: RawCompositionTypeElementListSyntax ( elements: elements, arena: self . arena) ,
276+ arena: self . arena
277+ )
278+ )
186279 }
280+
281+ return base
187282 }
188283
189284 /// Parse the subset of types that we allow in attribute names.
190285 mutating func parseAttributeName( ) -> RawTypeSyntax {
191- return parseSimpleType ( forAttributeName : true )
286+ return parseSimpleType ( allowTrailingOptional : false )
192287 }
193288
194289 mutating func parseSimpleType(
195290 allowMemberTypes: Bool = true ,
196- forAttributeName : Bool = false
291+ allowTrailingOptional : Bool = true
197292 ) -> RawTypeSyntax {
198293 let tilde = self . consumeIfContextualPunctuator ( " ~ " , remapping: . prefixOperator)
199294
200295 let baseType = self . parseUnsuppressedSimpleType (
201296 allowMemberTypes: allowMemberTypes,
202- forAttributeName : forAttributeName
297+ allowTrailingOptional : allowTrailingOptional
203298 )
204299
205300 guard let tilde else {
@@ -213,7 +308,7 @@ extension Parser {
213308
214309 mutating func parseUnsuppressedSimpleType(
215310 allowMemberTypes: Bool = true ,
216- forAttributeName : Bool = false
311+ allowTrailingOptional : Bool = true
217312 ) -> RawTypeSyntax {
218313 enum TypeBaseStart : TokenSpecSet {
219314 case `Self`
@@ -319,18 +414,25 @@ extension Parser {
319414 continue
320415 }
321416
322- // Do not allow ? or ! suffixes when parsing attribute names.
323- if forAttributeName {
324- break
325- }
326-
327- if self . at ( TokenSpec ( . postfixQuestionMark, allowAtStartOfLine: false ) ) {
328- base = RawTypeSyntax ( self . parseOptionalType ( base) )
329- continue
330- }
331- if self . at ( TokenSpec ( . exclamationMark, allowAtStartOfLine: false ) ) {
332- base = RawTypeSyntax ( self . parseImplicitlyUnwrappedOptionalType ( base) )
333- continue
417+ if self . at ( . postfixQuestionMark, . exclamationMark) {
418+ // Even if we're not generally allowing trailing optional sugar (e.g.,
419+ // at the end of a `some` type), we still need to consume them if they
420+ // are followed by a member access (`.`), because we need to support
421+ // valid types like `some P?.Q`.
422+ let shouldConsume =
423+ allowTrailingOptional
424+ || self . withLookahead {
425+ $0. consumeAnyToken ( )
426+ return $0. at ( . period)
427+ }
428+ if shouldConsume {
429+ if self . at ( . postfixQuestionMark) {
430+ base = RawTypeSyntax ( self . parseOptionalType ( base) )
431+ } else {
432+ base = RawTypeSyntax ( self . parseImplicitlyUnwrappedOptionalType ( base) )
433+ }
434+ continue
435+ }
334436 }
335437
336438 break
0 commit comments