Skip to content

Commit 3957561

Browse files
committed
add support for protocol composition types in the type signature disambiguation syntax
1 parent 3316887 commit 3957561

File tree

5 files changed

+59
-23
lines changed

5 files changed

+59
-23
lines changed

Sources/UCF/Codelinks/Grammar/UCF.NominalPatternRule.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,27 @@ import Grammar
22

33
extension UCF
44
{
5-
/// NominalPattern ::= PathComponent ( '.' PathComponent ) *
5+
/// NominalPattern ::= PathComponents ( '&' PathComponents ) *
6+
/// PathComponents ::= PathComponent ( '.' PathComponent ) *
67
enum NominalPatternRule:ParsingRule
78
{
89
typealias Location = String.Index
910
typealias Terminal = Unicode.Scalar
1011

11-
typealias Construction = [(Range<Location>, [UCF.TypePattern])]
12+
typealias Construction = [[(Range<Location>, [UCF.TypePattern])]]
1213

1314
static func parse<Diagnostics>(
1415
_ input:inout ParsingInput<Diagnostics>) throws -> Construction where
1516
Diagnostics:ParsingDiagnostics,
1617
Diagnostics.Source.Element == Terminal,
1718
Diagnostics.Source.Index == Location
1819
{
19-
try input.parse(as: Pattern.Join<PathComponent,
20-
UnicodeEncoding<Location, Terminal>.Period,
20+
try input.parse(as: Pattern.Join<Pattern.Join<PathComponent,
21+
UnicodeEncoding<Location, Terminal>.Period,
22+
Construction.Element>,
23+
Pattern.Pad<
24+
UnicodeEncoding<Location, Terminal>.Ampersand,
25+
UnicodeEncoding<Location, Terminal>.Space>,
2126
Construction>.self)
2227
}
2328
}

Sources/UCF/Codelinks/Grammar/UCF.TypeOperand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ extension UCF
44
{
55
case bracket(TypePattern, TypePattern?)
66
case closure([TypePattern], TypePattern)
7-
case nominal([(Range<String.Index>, [TypePattern])])
7+
case nominal([[(Range<String.Index>, [TypePattern])]])
88
/// A single parenthesized type, or nil if the type is a placeholder (`_`).
99
case single(TypePattern?)
1010
/// A tuple containing either zero or two or more types.

Sources/UCF/Codelinks/Grammar/UCF.TypePattern.swift

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -90,42 +90,55 @@ extension UCF.TypePattern
9090
string.append(")->")
9191
output.format(source: source, into: &string)
9292

93-
case .nominal(let path):
93+
case .nominal(let composition):
9494
var first:Bool = true
95-
for (component, generics):(Range<String.Index>, [UCF.TypePattern]) in path
95+
for path:[(Range<String.Index>, [UCF.TypePattern])] in composition
9696
{
9797
if first
9898
{
9999
first = false
100100
}
101101
else
102102
{
103-
string.append(".")
103+
string.append("&")
104104
}
105105

106-
string += source[component]
107-
108-
if generics.isEmpty
109-
{
110-
continue
111-
}
112-
113-
string.append("<")
114106
var first:Bool = true
115-
for type:UCF.TypePattern in generics
107+
for (component, generics):(Range<String.Index>, [UCF.TypePattern]) in path
116108
{
117109
if first
118110
{
119111
first = false
120112
}
121113
else
122114
{
123-
string.append(",")
115+
string.append(".")
116+
}
117+
118+
string += source[component]
119+
120+
if generics.isEmpty
121+
{
122+
continue
124123
}
125124

126-
type.format(source: source, into: &string)
125+
string.append("<")
126+
var first:Bool = true
127+
for type:UCF.TypePattern in generics
128+
{
129+
if first
130+
{
131+
first = false
132+
}
133+
else
134+
{
135+
string.append(",")
136+
}
137+
138+
type.format(source: source, into: &string)
139+
}
140+
string.append(">")
127141
}
128-
string.append(">")
129142
}
130143

131144
case .single(let type?):

Sources/UCF/Codelinks/Grammar/UCF.TypePatternRule.PostfixOperand.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ extension UCF.TypePatternRule
1414
Diagnostics.Source.Element == Terminal,
1515
Diagnostics.Source.Index == Location
1616
{
17-
if let path:[(Range<Location>, [UCF.TypePattern])] = input.parse(
17+
if let composition:[[(Range<Location>, [UCF.TypePattern])]] = input.parse(
1818
as: UCF.NominalPatternRule?.self)
1919
{
20-
if let (range, generics):(Range<Location>, [UCF.TypePattern]) = path.first,
20+
if let path:[(Range<Location>, [UCF.TypePattern])] = composition.first,
21+
composition.count == 1,
22+
let (range, generics):(Range<Location>, [UCF.TypePattern]) = path.first,
2123
generics.isEmpty,
2224
path.count == 1,
2325
input.source.index(after: range.lowerBound) == range.upperBound,
@@ -26,7 +28,7 @@ extension UCF.TypePatternRule
2628
return .single(nil)
2729
}
2830

29-
return .nominal(path)
31+
return .nominal(composition)
3032
}
3133
else if
3234
let (first, value):(UCF.TypePattern, UCF.TypePattern?) = input.parse(

Sources/UCFTests/CodelinkDisambiguators.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ struct CodelinkDisambiguators:ParsingSuite
106106
signature: .returns(["Set<String>"]))))
107107
}
108108
@Test
109+
static func SignatureProtocolComposition() throws
110+
{
111+
let link:UCF.Selector = try Self.roundtrip("""
112+
Foo.bar(_:_:) (StringProtocol & Error, [Sendable & RandomAccessCollection<UInt8>])
113+
""")
114+
#expect(link.base == .relative)
115+
#expect(link.path.components == ["Foo", "bar(_:_:)"])
116+
#expect(link.path.hasTrailingParentheses)
117+
#expect(link.suffix == .unidoc(.init(
118+
conditions: [],
119+
signature: .function([
120+
"StringProtocol&Error",
121+
"[Sendable&RandomAccessCollection<UInt8>]"
122+
]))))
123+
}
124+
@Test
109125
static func All() throws
110126
{
111127
let link:UCF.Selector = try Self.roundtrip("""

0 commit comments

Comments
 (0)