diff --git a/CHANGELOG.md b/CHANGELOG.md
index edee4e26b..036ffb475 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,10 @@
[Brian Osborn](https://github.com/bosborn)
[John Fairhurst](https://github.com/johnfairh)
+* Support Swift concurrency features: identify actors and asynchronous
+ methods.
+ [John Fairhurst](https://github.com/johnfairh)
+
##### Bug Fixes
* None.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 79ebdef2b..fb3158a26 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -49,7 +49,7 @@ git push
You'll need push access to the integration specs repo to do this. You can
request access from one of the maintainers when filing your PR.
-You must have Xcode 12.5 installed to build the integration specs.
+You must have Xcode 13 installed to build the integration specs.
## Making changes to SourceKitten
diff --git a/lib/jazzy/doc_builder.rb b/lib/jazzy/doc_builder.rb
index 5ca4d12f6..8de6a5d8d 100644
--- a/lib/jazzy/doc_builder.rb
+++ b/lib/jazzy/doc_builder.rb
@@ -370,6 +370,8 @@ def self.render_item(item, source_module)
deprecation_message: item.deprecation_message,
unavailable_message: item.unavailable_message,
usage_discouraged: item.usage_discouraged?,
+ async: item.async,
+ declaration_note: item.declaration_note,
}
end
# rubocop:enable Metrics/MethodLength
diff --git a/lib/jazzy/highlighter.rb b/lib/jazzy/highlighter.rb
index 32bcae732..2320d98bf 100644
--- a/lib/jazzy/highlighter.rb
+++ b/lib/jazzy/highlighter.rb
@@ -2,6 +2,18 @@
require 'rouge'
+# While Rouge is downlevel (Rouge PR#1715 unreleased)
+module Rouge
+ module Lexers
+ class Swift
+ prepend :root do
+ rule(/\b(?:async|await|isolated)\b/, Keyword)
+ rule(/\b(?:actor|nonisolated)\b/, Keyword::Declaration)
+ end
+ end
+ end
+end
+
module Jazzy
# This module helps highlight code
module Highlighter
diff --git a/lib/jazzy/source_declaration.rb b/lib/jazzy/source_declaration.rb
index 828056052..f31a6b6ee 100644
--- a/lib/jazzy/source_declaration.rb
+++ b/lib/jazzy/source_declaration.rb
@@ -138,6 +138,7 @@ def display_other_language_declaration
attr_accessor :unavailable_message
attr_accessor :generic_requirements
attr_accessor :inherited_types
+ attr_accessor :async
def usage_discouraged?
unavailable || deprecated
@@ -188,6 +189,14 @@ def type_from_doc_module?
(swift? && usr && modulename.nil?)
end
+ # Info text for contents page by collapsed item name
+ def declaration_note
+ notes = [default_impl_abstract ? 'default implementation' : nil,
+ from_protocol_extension ? 'extension method' : nil,
+ async ? 'asynchronous' : nil].compact
+ notes.join(', ').humanize unless notes.empty?
+ end
+
def alternative_abstract
if file = alternative_abstract_file
Pathname(file).read
diff --git a/lib/jazzy/source_declaration/type.rb b/lib/jazzy/source_declaration/type.rb
index 076071dde..26833ebc2 100644
--- a/lib/jazzy/source_declaration/type.rb
+++ b/lib/jazzy/source_declaration/type.rb
@@ -12,11 +12,24 @@ def self.all
attr_reader :kind
- def initialize(kind)
+ def initialize(kind, declaration = nil)
+ kind = fixup_kind(kind, declaration) if declaration
@kind = kind
@type = TYPES[kind]
end
+ # Improve kind from full declaration
+ def fixup_kind(kind, declaration)
+ if kind == 'source.lang.swift.decl.class' &&
+ declaration.include?(
+ 'actor',
+ )
+ 'source.lang.swift.decl.actor'
+ else
+ kind
+ end
+ end
+
def dash_type
@type && @type[:dash]
end
@@ -115,7 +128,8 @@ def swift_extension?
end
def swift_extensible?
- kind =~ /^source\.lang\.swift\.decl\.(class|struct|protocol|enum)$/
+ kind =~
+ /^source\.lang\.swift\.decl\.(class|struct|protocol|enum|actor)$/
end
def swift_protocol?
@@ -274,6 +288,11 @@ def ==(other)
}.freeze,
# Swift
+ 'source.lang.swift.decl.actor' => {
+ jazzy: 'Actor',
+ dash: 'Actor',
+ global: true,
+ }.freeze,
'source.lang.swift.decl.function.accessor.address' => {
jazzy: 'Addressor',
dash: 'Function',
diff --git a/lib/jazzy/sourcekitten.rb b/lib/jazzy/sourcekitten.rb
index fa6d037c8..8cbc45eda 100644
--- a/lib/jazzy/sourcekitten.rb
+++ b/lib/jazzy/sourcekitten.rb
@@ -524,6 +524,14 @@ def self.make_swift_declaration(doc, declaration)
.join("\n")
end
+ # Exclude non-async routines that accept async closures
+ def self.swift_async?(fully_annotated_decl)
+ document = REXML::Document.new(fully_annotated_decl)
+ !document.elements['/*/syntaxtype.keyword[text()="async"]'].nil?
+ rescue StandardError
+ nil
+ end
+
# Strip default property attributes because libclang
# adds them all, even if absent in the original source code.
DEFAULT_ATTRIBUTES = %w[atomic readwrite assign unsafe_unretained].freeze
@@ -565,7 +573,9 @@ def self.make_source_declarations(docs, parent = nil, mark = SourceMark.new)
end
declaration = SourceDeclaration.new
declaration.parent_in_code = parent
- declaration.type = SourceDeclaration::Type.new(doc['key.kind'])
+ declaration.type =
+ SourceDeclaration::Type.new(doc['key.kind'],
+ doc['key.fully_annotated_decl'])
declaration.typename = doc['key.typename']
declaration.objc_name = doc['key.name']
documented_name = if Config.instance.hide_objc? && doc['key.swift_name']
@@ -610,6 +620,11 @@ def self.make_source_declarations(docs, parent = nil, mark = SourceMark.new)
inherited_types = doc['key.inheritedtypes'] || []
declaration.inherited_types =
inherited_types.map { |type| type['key.name'] }.compact
+ declaration.async =
+ doc['key.symgraph_async'] ||
+ if xml_declaration = doc['key.fully_annotated_decl']
+ swift_async?(xml_declaration)
+ end
next unless make_doc_info(doc, declaration)
@@ -819,7 +834,9 @@ def self.mark_and_merge_protocol_extensions(protocol, extensions)
extensions.each do |ext|
ext.children = ext.children.select do |ext_member|
proto_member = protocol.children.find do |p|
- p.name == ext_member.name && p.type == ext_member.type
+ p.name == ext_member.name &&
+ p.type == ext_member.type &&
+ p.async == ext_member.async
end
# Extension-only method, keep.
diff --git a/lib/jazzy/symbol_graph/graph.rb b/lib/jazzy/symbol_graph/graph.rb
index db98490e6..a738fb9c2 100644
--- a/lib/jazzy/symbol_graph/graph.rb
+++ b/lib/jazzy/symbol_graph/graph.rb
@@ -78,8 +78,13 @@ def rel_source_name(rel, source_node)
# Protocol conformance is redundant if it's unconditional
# and already expressed in the type's declaration.
+ #
+ # Skip implementation-detail conformances.
def redundant_conformance?(rel, type, protocol)
- type && rel.constraints.empty? && type.conformance?(protocol)
+ return false unless type
+
+ (rel.constraints.empty? && type.conformance?(protocol)) ||
+ (type.actor? && rel.actor_protocol?)
end
# source is a member/protocol requirement of target
diff --git a/lib/jazzy/symbol_graph/relationship.rb b/lib/jazzy/symbol_graph/relationship.rb
index 97959d140..a32a11b80 100644
--- a/lib/jazzy/symbol_graph/relationship.rb
+++ b/lib/jazzy/symbol_graph/relationship.rb
@@ -22,6 +22,12 @@ def default_implementation?
kind == :defaultImplementationOf
end
+ # Protocol conformances added by compiler to actor decls that
+ # users aren't interested in.
+ def actor_protocol?
+ %w[Actor Sendable].include?(target_fallback)
+ end
+
def initialize(hash)
kind = hash[:kind]
unless KINDS.include?(kind)
diff --git a/lib/jazzy/symbol_graph/sym_node.rb b/lib/jazzy/symbol_graph/sym_node.rb
index 65200b6c7..4b307ca96 100644
--- a/lib/jazzy/symbol_graph/sym_node.rb
+++ b/lib/jazzy/symbol_graph/sym_node.rb
@@ -25,6 +25,7 @@ def children_to_sourcekit
# A SymNode is a node of the reconstructed syntax tree holding a symbol.
# It can turn itself into SourceKit and helps decode extensions.
+ # rubocop:disable Metrics/ClassLength
class SymNode < BaseNode
attr_accessor :symbol
attr_writer :override
@@ -61,6 +62,10 @@ def protocol?
symbol.kind.end_with?('protocol')
end
+ def actor?
+ symbol.kind.end_with?('actor')
+ end
+
def constraints
symbol.constraints
end
@@ -112,6 +117,11 @@ def inherits_clause
" : #{superclass_name}"
end
+ # approximately...
+ def async?
+ symbol.declaration =~ /\basync\b[^)]*$/
+ end
+
def full_declaration
symbol.attributes
.append(symbol.declaration + inherits_clause + where_clause)
@@ -130,6 +140,7 @@ def to_sourcekit
'key.accessibility' => symbol.acl,
'key.parsed_decl' => declaration,
'key.annotated_decl' => xml_declaration,
+ 'key.symgraph_async' => async?,
}
if docs = symbol.doc_comments
hash['key.doc.comment'] = docs
@@ -160,5 +171,6 @@ def <=>(other)
symbol <=> other.symbol
end
end
+ # rubocop:enable Metrics/ClassLength
end
end
diff --git a/lib/jazzy/symbol_graph/symbol.rb b/lib/jazzy/symbol_graph/symbol.rb
index a2bb66c7d..7429934c6 100644
--- a/lib/jazzy/symbol_graph/symbol.rb
+++ b/lib/jazzy/symbol_graph/symbol.rb
@@ -25,8 +25,8 @@ def name
def initialize(hash)
self.usr = hash[:identifier][:precise]
self.path_components = hash[:pathComponents]
- raw_decl = hash[:declarationFragments].map { |f| f[:spelling] }.join
- init_kind(hash[:kind][:identifier])
+ raw_decl, keywords = parse_decl_fragments(hash[:declarationFragments])
+ init_kind(hash[:kind][:identifier], keywords)
init_declaration(raw_decl)
if func_signature = hash[:functionSignature]
init_func_signature(func_signature)
@@ -44,6 +44,16 @@ def initialize(hash)
init_generic_type_params(hash)
end
+ def parse_decl_fragments(fragments)
+ decl = ''
+ keywords = Set.new
+ fragments.each do |frag|
+ decl += frag[:spelling]
+ keywords.add(frag[:spelling]) if frag[:kind] == 'keyword'
+ end
+ [decl, keywords]
+ end
+
# Repair problems with SymbolGraph's declprinter
def init_declaration(raw_decl)
@@ -89,17 +99,22 @@ def init_func_signature(func_signature)
'static.subscript' => 'function.subscript',
'typealias' => 'typealias',
'associatedtype' => 'associatedtype',
+ 'actor' => 'actor',
}.freeze
# We treat 'static var' differently to 'class var'
- def adjust_kind_for_declaration(kind)
- return kind unless declaration =~ /\bstatic\b/
+ # We treat actors as first-class entities
+ def adjust_kind_for_declaration(kind, keywords)
+ if kind == 'swift.class' && keywords.member?('actor')
+ return 'swift.actor'
+ end
+ return kind unless keywords.member?('static')
kind.gsub(/type/, 'static')
end
- def init_kind(kind)
- adjusted = adjust_kind_for_declaration(kind)
+ def init_kind(kind, keywords)
+ adjusted = adjust_kind_for_declaration(kind, keywords)
sourcekit_kind = KIND_MAP[adjusted.sub('swift.', '')]
raise "Unknown symbol kind '#{kind}'" unless sourcekit_kind
diff --git a/lib/jazzy/themes/apple/templates/task.mustache b/lib/jazzy/themes/apple/templates/task.mustache
index ea738c009..1353dafd5 100644
--- a/lib/jazzy/themes/apple/templates/task.mustache
+++ b/lib/jazzy/themes/apple/templates/task.mustache
@@ -28,16 +28,11 @@
{{{name_html}}}
{{/usage_discouraged}}
- {{#default_impl_abstract}}
+ {{#declaration_note}}
- Default implementation
+ {{.}}
- {{/default_impl_abstract}}
- {{#from_protocol_extension}}
-
- Extension method
-
- {{/from_protocol_extension}}
+ {{/declaration_note}}
diff --git a/lib/jazzy/themes/fullwidth/templates/task.mustache b/lib/jazzy/themes/fullwidth/templates/task.mustache
index 456d7576a..a33f74f62 100644
--- a/lib/jazzy/themes/fullwidth/templates/task.mustache
+++ b/lib/jazzy/themes/fullwidth/templates/task.mustache
@@ -28,16 +28,11 @@
{{{name_html}}}
{{/usage_discouraged}}
- {{#default_impl_abstract}}
+ {{#declaration_note}}
- Default implementation
+ {{.}}
- {{/default_impl_abstract}}
- {{#from_protocol_extension}}
-
- Extension method
-
- {{/from_protocol_extension}}
+ {{/declaration_note}}
diff --git a/lib/jazzy/themes/jony/templates/task.mustache b/lib/jazzy/themes/jony/templates/task.mustache
index ea738c009..1353dafd5 100644
--- a/lib/jazzy/themes/jony/templates/task.mustache
+++ b/lib/jazzy/themes/jony/templates/task.mustache
@@ -28,16 +28,11 @@
{{{name_html}}}
{{/usage_discouraged}}
- {{#default_impl_abstract}}
+ {{#declaration_note}}
- Default implementation
+ {{.}}
- {{/default_impl_abstract}}
- {{#from_protocol_extension}}
-
- Extension method
-
- {{/from_protocol_extension}}
+ {{/declaration_note}}
diff --git a/spec/integration_specs b/spec/integration_specs
index 55663efa5..c28a0b31e 160000
--- a/spec/integration_specs
+++ b/spec/integration_specs
@@ -1 +1 @@
-Subproject commit 55663efa5dad0f3526c4b1cfa12a073c8c4704e1
+Subproject commit c28a0b31eb89969f59ea25f466f0c3cac7435d19