Skip to content

Commit af587cb

Browse files
authored
Merge pull request #1171 from AnthonyLatsis/sugared-member-types
SwiftParser: A lookahead fix and test coverage for member types with non-identifier qualifiers
2 parents cfec25e + 4827e48 commit af587cb

File tree

15 files changed

+785
-425
lines changed

15 files changed

+785
-425
lines changed

CodeGeneration/Sources/SyntaxSupport/gyb_generated/AttributeNodes.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public let ATTRIBUTE_NODES: [Node] = [
6161
nameForDiagnostics: "attribute",
6262
description: "An `@` attribute.",
6363
kind: "Syntax",
64+
parserFunction: "parseAttribute",
6465
children: [
6566
Child(name: "AtSignToken",
6667
kind: "AtSignToken",

Sources/SwiftParser/Types.swift

Lines changed: 110 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,11 @@ extension Parser {
1818
/// Grammar
1919
/// =======
2020
///
21+
/// type → simple-type
2122
/// type → function-type
22-
/// type → array-type
23-
/// type → dictionary-type
24-
/// type → type-identifier
25-
/// type → tuple-type
26-
/// type → optional-type
27-
/// type → implicitly-unwrapped-optional-type
2823
/// type → protocol-composition-type
24+
/// type → constrained-sugar-type
2925
/// type → opaque-type
30-
/// type → metatype-type
31-
/// type → any-type
32-
/// type → self-type
33-
/// type → '(' type ')'
3426
@_spi(RawSyntax)
3527
public mutating func parseType(misplacedSpecifiers: [RawTokenSyntax] = []) -> RawTypeSyntax {
3628
let type = self.parseTypeScalar(misplacedSpecifiers: misplacedSpecifiers)
@@ -133,11 +125,13 @@ extension Parser {
133125
/// Grammar
134126
/// =======
135127
///
136-
/// type-identifiertype-name generic-argument-clause? | type-name generic-argument-clause? '.' type-identifier
137-
/// type-nameidentifier
128+
/// protocol-composition-typesimple-type '&' protocol-composition-continuation
129+
/// protocol-composition-continuationsimple-type | protocol-composition-type
138130
///
139-
/// protocol-composition-type → type-identifier '&' protocol-composition-continuation
140-
/// protocol-composition-continuation → type-identifier | protocol-composition-type
131+
/// constrained-sugar-type → constrained-sugar-type-specifier constrained-sugar-type-constraint
132+
/// constrained-sugar-type-specifier → 'any' | 'some'
133+
/// constrained-sugar-type-constraint → protocol-composition-type
134+
/// constrained-sugar-type-constraint → type-simple
141135
@_spi(RawSyntax)
142136
public mutating func parseSimpleOrCompositionType() -> RawTypeSyntax {
143137
// 'each' is a contextual keyword for a pack reference.
@@ -219,13 +213,21 @@ extension Parser {
219213
/// Grammar
220214
/// =======
221215
///
222-
/// type → type-identifier
223-
/// type → tuple-type
224-
/// type → array-type
225-
/// type → dictionary-type
226-
/// type → metatype-type
216+
/// simple-type → type-identifier
217+
/// simple-type → any-type
218+
/// simple-type → paren-type
219+
/// simple-type → tuple-type
220+
/// simple-type → array-type
221+
/// simple-type → dictionary-type
222+
/// simple-type → optional-type
223+
/// simple-type → implicitly-unwrapped-optional-type
224+
/// simple-type → metatype-type
225+
/// simple-type → member-type-identifier
227226
///
228-
/// metatype-type → type '.' 'Type' | type '.' 'Protocol'
227+
/// metatype-type → simple-type '.' 'Type' | simple-type '.' 'Protocol'
228+
///
229+
/// member-type-identifier → member-type-identifier-base '.' type-identifier
230+
/// member-type-identifier-base → simple-type | member-type-identifier
229231
@_spi(RawSyntax)
230232
public mutating func parseSimpleType(
231233
stopAtFirstPeriod: Bool = false
@@ -349,6 +351,12 @@ extension Parser {
349351
return (name, nil)
350352
}
351353

354+
/// Parse a type identifier.
355+
///
356+
/// Grammar
357+
/// =======
358+
///
359+
/// type-identifier → identifier generic-argument-clause?
352360
mutating func parseTypeIdentifier() -> RawTypeSyntax {
353361
if self.at(.anyKeyword) {
354362
return RawTypeSyntax(self.parseAnyType())
@@ -369,7 +377,7 @@ extension Parser {
369377
/// Grammar
370378
/// =======
371379
///
372-
/// any-type → Any
380+
/// any-type → 'Any'
373381
@_spi(RawSyntax)
374382
public mutating func parseAnyType() -> RawSimpleTypeIdentifierSyntax {
375383
let (unexpectedBeforeName, name) = self.expect(.anyKeyword)
@@ -461,6 +469,8 @@ extension Parser {
461469
/// Grammar
462470
/// =======
463471
///
472+
/// paren-type → '(' type ')'
473+
///
464474
/// tuple-type → '(' ')' | '(' tuple-type-element ',' tuple-type-element-list ')'
465475
/// tuple-type-element-list → tuple-type-element | tuple-type-element ',' tuple-type-element-list
466476
/// tuple-type-element → element-name type-annotation | type
@@ -639,14 +649,87 @@ extension Parser {
639649

640650
extension Parser.Lookahead {
641651
mutating func canParseType() -> Bool {
652+
guard self.canParseTypeScalar() else {
653+
return false
654+
}
655+
656+
if self.currentToken.isEllipsis {
657+
self.consumeAnyToken()
658+
}
659+
660+
return true
661+
}
662+
663+
mutating func skipTypeAttributeList() {
664+
var specifierProgress = LoopProgressCondition()
665+
// TODO: Can we model isolated/_const so that they're specified in both canParse* and parse*?
666+
while self.at(anyIn: TypeSpecifier.self) != nil || self.atContextualKeyword("isolated") || self.atContextualKeyword("_const"),
667+
specifierProgress.evaluate(currentToken)
668+
{
669+
self.consumeAnyToken()
670+
}
671+
672+
var attributeProgress = LoopProgressCondition()
673+
while self.at(.atSign), attributeProgress.evaluate(currentToken) {
674+
self.consumeAnyToken()
675+
self.skipTypeAttribute()
676+
}
677+
}
678+
679+
mutating func canParseTypeScalar() -> Bool {
642680
self.skipTypeAttributeList()
643681

682+
guard self.canParseSimpleOrCompositionType() else {
683+
return false
684+
}
685+
686+
if self.isAtFunctionTypeArrow() {
687+
// Handle type-function if we have an '->' with optional
688+
// 'async' and/or 'throws'.
689+
var loopProgress = LoopProgressCondition()
690+
while let (_, handle) = self.at(anyIn: EffectsSpecifier.self), loopProgress.evaluate(currentToken) {
691+
self.eat(handle)
692+
}
693+
694+
guard self.consume(if: .arrow) != nil else {
695+
return false
696+
}
697+
698+
return self.canParseType()
699+
}
700+
701+
return true
702+
}
703+
704+
mutating func canParseSimpleOrCompositionType() -> Bool {
705+
if self.atContextualKeyword("each") {
706+
return self.canParseSimpleType();
707+
}
708+
644709
if self.atContextualKeyword("some") || self.atContextualKeyword("any") {
645710
self.consumeAnyToken()
646711
}
647712

713+
guard self.canParseSimpleType() else {
714+
return false
715+
}
716+
717+
var loopCondition = LoopProgressCondition()
718+
while self.atContextualPunctuator("&") && loopCondition.evaluate(self.currentToken) {
719+
self.consumeAnyToken()
720+
guard self.canParseSimpleType() else {
721+
return false
722+
}
723+
}
724+
725+
return true
726+
}
727+
728+
mutating func canParseSimpleType() -> Bool {
648729
switch self.currentToken.tokenKind {
649-
case .capitalSelfKeyword, .anyKeyword, .identifier:
730+
case .anyKeyword:
731+
self.consumeAnyToken()
732+
case .capitalSelfKeyword, .identifier:
650733
guard self.canParseTypeIdentifier() else {
651734
return false
652735
}
@@ -700,44 +783,9 @@ extension Parser.Lookahead {
700783
break
701784
}
702785

703-
if self.isAtFunctionTypeArrow() {
704-
// Handle type-function if we have an '->' with optional
705-
// 'async' and/or 'throws'.
706-
var loopProgress = LoopProgressCondition()
707-
while let (_, handle) = self.at(anyIn: EffectsSpecifier.self), loopProgress.evaluate(currentToken) {
708-
self.eat(handle)
709-
}
710-
711-
guard self.consume(if: .arrow) != nil else {
712-
return false
713-
}
714-
715-
return self.canParseType()
716-
}
717-
718-
if self.currentToken.isEllipsis {
719-
self.consumeAnyToken()
720-
}
721-
722786
return true
723787
}
724788

725-
mutating func skipTypeAttributeList() {
726-
var specifierProgress = LoopProgressCondition()
727-
// TODO: Can we model isolated/_const so that they're specified in both canParse* and parse*?
728-
while self.at(anyIn: TypeSpecifier.self) != nil || self.atContextualKeyword("isolated") || self.atContextualKeyword("_const"),
729-
specifierProgress.evaluate(currentToken)
730-
{
731-
self.consumeAnyToken()
732-
}
733-
734-
var attributeProgress = LoopProgressCondition()
735-
while self.at(.atSign), attributeProgress.evaluate(currentToken) {
736-
self.consumeAnyToken()
737-
self.skipTypeAttribute()
738-
}
739-
}
740-
741789
mutating func canParseTupleBodyType() -> Bool {
742790
guard
743791
!self.at(any: [.rightParen, .rightBrace]) && !self.atContextualPunctuator("...") && !self.atStartOfDeclaration()
@@ -793,30 +841,6 @@ extension Parser.Lookahead {
793841
return self.consume(if: .rightParen) != nil
794842
}
795843

796-
mutating func canParseSimpleType() -> Bool {
797-
var allowKeyword = false
798-
var loopCondition = LoopProgressCondition()
799-
while loopCondition.evaluate(currentToken) {
800-
if !self.canParseTypeIdentifier() {
801-
// Allow Foo.<keyword> but not <keyword> as the initial identifier
802-
if allowKeyword && self.currentToken.isKeyword {
803-
self.consumeAnyToken()
804-
} else {
805-
return false
806-
}
807-
}
808-
809-
// Treat 'Foo.<anything>' as an attempt to write a dotted type
810-
if self.at(.period) {
811-
self.consumeAnyToken()
812-
allowKeyword = true
813-
} else {
814-
return true
815-
}
816-
}
817-
preconditionFailure("Should return from inside the loop")
818-
}
819-
820844
func isAtFunctionTypeArrow() -> Bool {
821845
if self.at(.arrow) {
822846
return true
@@ -840,26 +864,14 @@ extension Parser.Lookahead {
840864
return false
841865
}
842866

843-
mutating func canParseSimpleOrCompositionType() -> Bool {
844-
var loopCondition = LoopProgressCondition()
845-
while loopCondition.evaluate(currentToken) {
846-
guard self.canParseSimpleType() else {
847-
return false
848-
}
849-
850-
if self.atContextualPunctuator("&") {
851-
self.consumeAnyToken()
852-
continue
853-
} else {
854-
return true
855-
}
867+
mutating func canParseTypeIdentifier(allowKeyword: Bool = false) -> Bool {
868+
if self.at(.anyKeyword) {
869+
self.consumeAnyToken()
870+
return true
856871
}
857-
preconditionFailure("Should return from inside the loop")
858-
}
859872

860-
mutating func canParseTypeIdentifier(allowKeyword: Bool = false) -> Bool {
861873
// Parse an identifier.
862-
guard self.at(.identifier) || self.at(.capitalSelfKeyword) || self.at(.anyKeyword) || (allowKeyword && self.currentToken.isKeyword) else {
874+
guard self.at(.identifier) || self.at(.capitalSelfKeyword) || (allowKeyword && self.currentToken.isKeyword) else {
863875
return false
864876
}
865877
self.consumeAnyToken()

Sources/SwiftParser/generated/Parser+Entry.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ public protocol SyntaxParseable: SyntaxProtocol {
4343
static func parse(from parser: inout Parser) -> Self
4444
}
4545

46+
extension AttributeSyntax: SyntaxParseable {
47+
public static func parse(from parser: inout Parser) -> Self {
48+
let node = parser.parseAttribute()
49+
let raw = RawSyntax(parser.parseRemainder(into: node))
50+
return Syntax(raw: raw).cast(Self.self)
51+
}
52+
}
53+
4654
extension CatchClauseSyntax: SyntaxParseable {
4755
public static func parse(from parser: inout Parser) -> Self {
4856
let node = parser.parseCatchClause()

Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ extension AssignmentExprSyntax: SyntaxExpressibleByStringInterpolation {
5353
extension AssociatedtypeDeclSyntax: SyntaxExpressibleByStringInterpolation {
5454
}
5555

56+
extension AttributeSyntax: SyntaxExpressibleByStringInterpolation {
57+
public init(stringInterpolationOrThrow stringInterpolation: SyntaxStringInterpolation) throws {
58+
self = try performParse(source: stringInterpolation.sourceText, parse: { parser in
59+
return Self.parse(from: &parser)
60+
})
61+
}
62+
}
63+
5664
extension AttributedTypeSyntax: SyntaxExpressibleByStringInterpolation {
5765
}
5866

Tests/SwiftParserTest/AttributeTests.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,55 @@ final class AttributeTests: XCTestCase {
234234
)
235235
}
236236

237+
func testImplementsAttributeBaseType() {
238+
let cases: [UInt: String] = [
239+
// Identifiers and member types
240+
#line: "X<T>",
241+
#line: "X.Y",
242+
#line: "X.Y<T>",
243+
244+
// Metatypes
245+
#line: "X.Type",
246+
#line: "X.Protocol",
247+
248+
// Sugared optionals
249+
#line: "X?",
250+
#line: "X!",
251+
252+
// Sugared collections
253+
#line: "[X]",
254+
#line: "[X : Y]",
255+
256+
// Tuples and paren type
257+
#line: "()",
258+
#line: "(X)",
259+
#line: "(X, X)",
260+
261+
// Keywords
262+
#line: "Any",
263+
#line: "Self",
264+
265+
// Protocol compositions
266+
#line: "X & Y",
267+
#line: "any X & Y",
268+
269+
// Functions
270+
#line: "(X) -> Y",
271+
]
272+
273+
for (line, baseType) in cases {
274+
var parser = Parser(baseType)
275+
276+
AssertParse(
277+
"@_implements(1️⃣\(baseType), f())",
278+
AttributeSyntax.parse,
279+
substructure: Syntax(TypeSyntax.parse(from: &parser)),
280+
substructureAfterMarker: "1️⃣",
281+
line: line
282+
)
283+
}
284+
}
285+
237286
func testSemanticsAttribute() {
238287
AssertParse(
239288
"""

0 commit comments

Comments
 (0)