Skip to content

Commit 1062dd4

Browse files
authored
Diagnose broken out-of-package links at compile time (#310)
1 parent c940671 commit 1062dd4

File tree

110 files changed

+2484
-1659
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+2484
-1659
lines changed

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,10 @@ let package:Package = .init(
228228

229229
.target(name: "LinkResolution",
230230
dependencies: [
231-
.target(name: "UCF"),
231+
.target(name: "InlineArray"),
232232
.target(name: "SourceDiagnostics"),
233233
.target(name: "Symbols"),
234+
.target(name: "UCF"),
234235
// This dependency is present for (questionable?) performance reasons.
235236
.target(name: "Unidoc"),
236237
]),
@@ -373,13 +374,13 @@ let package:Package = .init(
373374

374375
.target(name: "SymbolGraphCompiler",
375376
dependencies: [
377+
.target(name: "LinkResolution"),
376378
.target(name: "SymbolGraphParts"),
377379
.product(name: "TraceableErrors", package: "swift-grammar"),
378380
]),
379381

380382
.target(name: "SymbolGraphLinker",
381383
dependencies: [
382-
.target(name: "LinkResolution"),
383384
.target(name: "InlineArray"),
384385
.target(name: "InlineDictionary"),
385386
.target(name: "MarkdownParsing"),
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import UCF
2+
import Symbols
3+
4+
extension UCF
5+
{
6+
@frozen public
7+
struct ArticleResolver
8+
{
9+
public
10+
let table:ArticleTable
11+
public
12+
let scope:ArticleScope
13+
14+
@inlinable public
15+
init(table:ArticleTable, scope:ArticleScope)
16+
{
17+
self.table = table
18+
self.scope = scope
19+
}
20+
}
21+
}
22+
extension UCF.ArticleResolver
23+
{
24+
public
25+
func resolve(_ doclink:Doclink, docc:Bool = false) -> Int32?
26+
{
27+
self.table.resolve(doclink, docc: docc, in: self.scope)
28+
}
29+
}

Sources/LinkResolution/Doclinks/DoclinkResolver.Scope.swift renamed to Sources/LinkResolution/Articles/UCF.ArticleScope.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import Symbols
2+
import UCF
23

3-
extension DoclinkResolver
4+
extension UCF
45
{
56
@frozen public
6-
struct Scope
7+
struct ArticleScope
78
{
89
public
910
let namespace:Symbol.Module?

Sources/LinkResolution/Doclinks/DoclinkResolver.Prefix.swift renamed to Sources/LinkResolution/Articles/UCF.ArticleTable.Prefix.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Symbols
2+
import UCF
23

3-
extension DoclinkResolver
4+
extension UCF.ArticleTable
45
{
56
@frozen public
67
enum Prefix
@@ -9,7 +10,7 @@ extension DoclinkResolver
910
case tutorials(Symbol.Module)
1011
}
1112
}
12-
extension DoclinkResolver.Prefix:RandomAccessCollection
13+
extension UCF.ArticleTable.Prefix:RandomAccessCollection
1314
{
1415
@inlinable public
1516
var startIndex:Int { 0 }
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,69 @@
1-
import UCF
21
import Symbols
2+
import UCF
33

4-
@frozen public
5-
struct DoclinkResolver
4+
extension UCF
65
{
7-
public
8-
let table:Table
9-
public
10-
let scope:Scope
6+
@frozen public
7+
struct ArticleTable
8+
{
9+
@usableFromInline
10+
var entries:[ResolutionPath: Int32]
1111

12+
@inlinable public
13+
init()
14+
{
15+
self.entries = [:]
16+
}
17+
}
18+
}
19+
extension UCF.ArticleTable
20+
{
1221
@inlinable public
13-
init(table:Table, scope:Scope)
22+
subscript(prefix:Prefix, name:String) -> Int32?
1423
{
15-
self.table = table
16-
self.scope = scope
24+
_read
25+
{
26+
yield self.entries[.join(prefix + [name])]
27+
}
28+
_modify
29+
{
30+
yield &self.entries[.join(prefix + [name])]
31+
}
1732
}
1833
}
19-
extension DoclinkResolver
34+
extension UCF.ArticleTable
2035
{
21-
public
22-
func resolve(_ link:Doclink) -> Int32?
36+
func resolve(_ link:Doclink, in scope:UCF.ArticleScope) -> Int32?
2337
{
2438
if !link.absolute
2539
{
26-
if let namespace:Symbol.Module = self.scope.namespace
40+
if let namespace:Symbol.Module = scope.namespace
2741
{
2842
for prefix:Prefix in [.documentation(namespace), .tutorials(namespace)]
2943
{
3044
for index:Int in prefix.indices.reversed()
3145
{
3246
let path:UCF.ResolutionPath = .join(prefix[...index] + link.path)
33-
if let address:Int32 = self.table.entries[path]
47+
if let address:Int32 = self.entries[path]
3448
{
3549
return address
3650
}
3751
}
3852
}
3953
}
4054
}
41-
return self.table.entries[.join(link.path)]
55+
return self.entries[.join(link.path)]
4256
}
4357

44-
public
45-
func resolve(_ doclink:Doclink, docc:Bool) -> Int32?
58+
func resolve(_ doclink:Doclink, docc:Bool, in scope:UCF.ArticleScope) -> Int32?
4659
{
47-
if let resolved:Int32 = self.resolve(doclink)
60+
if let resolved:Int32 = self.resolve(doclink, in: scope)
4861
{
4962
return resolved
5063
}
5164

5265
guard docc,
53-
let namespace:Symbol.Module = self.scope.namespace
66+
let namespace:Symbol.Module = scope.namespace
5467
else
5568
{
5669
return nil
@@ -66,6 +79,6 @@ extension DoclinkResolver
6679
}
6780

6881
let path:UCF.ResolutionPath = .join([_].init(prefix) + doclink.path.dropFirst())
69-
return self.table.entries[path]
82+
return self.entries[path]
7083
}
7184
}

Sources/LinkResolution/Codelinks/UCF.Overload.Resolver.swift

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,19 @@ extension UCF.Overload.Resolver
3030
switch selector.base
3131
{
3232
case .relative:
33-
if let namespace:Symbol.Module = self.scope.namespace
33+
for index:Int in
34+
(self.scope.path.startIndex ... self.scope.path.endIndex).reversed()
3435
{
35-
for index:Int in
36-
(self.scope.path.startIndex ... self.scope.path.endIndex).reversed()
37-
{
38-
let overloads:UCF.Overload<Scalar>.Group = self.table.query(
39-
qualified: ["\(namespace)"]
40-
+ self.scope.path[..<index]
41-
+ selector.path.components,
42-
suffix: selector.suffix)
36+
let overloads:UCF.Overload<Scalar>.Group = self.table.query(
37+
qualified: ["\(self.scope.namespace)"]
38+
+ self.scope.path[..<index]
39+
+ selector.path.components,
40+
suffix: selector.suffix)
4341

44-
guard overloads.isEmpty
45-
else
46-
{
47-
return overloads
48-
}
42+
guard overloads.isEmpty
43+
else
44+
{
45+
return overloads
4946
}
5047
}
5148
for namespace:Symbol.Module in self.scope.imports where

Sources/LinkResolution/Codelinks/UCF.Overload.Table.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ extension UCF.Overload
88
@frozen public
99
struct Table
1010
{
11-
@usableFromInline internal
11+
@usableFromInline
1212
var entries:[UCF.ResolutionPath: Group]
1313

1414
@inlinable public

Sources/LinkResolution/Codelinks/UCF.OverloadResolutionError.Note.swift

Lines changed: 0 additions & 37 deletions
This file was deleted.

Sources/LinkResolution/Codelinks/UCF.OverloadResolutionError.swift

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,33 +25,41 @@ extension UCF
2525
}
2626
extension UCF.OverloadResolutionError:Diagnostic
2727
{
28-
@inlinable public static
29-
func += (output:inout DiagnosticOutput<Symbolicator>, self:Self)
28+
@inlinable public
29+
func emit(summary:inout DiagnosticOutput<Symbolicator>)
3030
{
3131
if self.overloads.isEmpty
3232
{
33-
output[.warning] += """
33+
summary[.warning] += """
3434
selector '\(self.selector)' does not refer to any known declarations
3535
"""
3636
}
3737
else
3838
{
39-
output[.warning] += """
39+
summary[.warning] += """
4040
selector '\(self.selector)' is ambiguous
4141
"""
4242
}
4343
}
4444

4545
@inlinable public
46-
var notes:[Note]
46+
func emit(details:inout DiagnosticOutput<Symbolicator>)
4747
{
48-
self.overloads.map
48+
for overload:UCF.Overload<Symbolicator.Address> in self.overloads
4949
{
50-
.init(suggested: .init(
51-
base: self.selector.base,
52-
path: self.selector.path,
53-
suffix: .hash($0.hash)),
54-
target: $0.target)
50+
let suggested:UCF.Selector = .init(
51+
base: self.selector.base,
52+
path: self.selector.path,
53+
suffix: .hash(overload.hash))
54+
55+
switch overload.target
56+
{
57+
case .scalar(let scalar),
58+
.vector(let scalar, self: _):
59+
details[.note] = """
60+
did you mean '\(suggested)'? (\(details.symbolicator[scalar]))
61+
"""
62+
}
5563
}
5664
}
5765
}

0 commit comments

Comments
 (0)