1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//
import SwiftSyntax
import SwiftSyntaxMacros
import SwiftDiagnostics
extension MacroExpansionContext {
/// Get the type of the lexical context enclosing the given node.
///
/// - Parameters:
/// - node: The node whose lexical context should be examined.
///
/// - Returns: The type of the lexical context enclosing `node`, or `nil` if
/// the lexical context cannot be represented as a type.
///
/// If the lexical context includes functions, closures, or some other
/// non-type scope, the value of this property is `nil`.
var typeOfLexicalContext: TypeSyntax? {
var typeNames = [String]()
for lexicalContext in lexicalContext.reversed() {
guard let decl = lexicalContext.asProtocol((any DeclGroupSyntax).self) else {
return nil
}
typeNames.append(decl.type.trimmedDescription)
}
if typeNames.isEmpty {
return nil
}
return "\(raw: typeNames.joined(separator: "."))"
}
}
// MARK: -
extension MacroExpansionContext {
/// Create a unique name for a function that thunks another function.
///
/// - Parameters:
/// - functionDecl: The function to thunk.
/// - prefix: A prefix to apply to the thunked name before returning.
///
/// - Returns: A unique name to use for a thunk function that thunks
/// `functionDecl`.
func makeUniqueName(thunking functionDecl: FunctionDeclSyntax, withPrefix prefix: String = "") -> TokenSyntax {
// Find all the tokens of the function declaration including argument
// types, specifiers, etc. (but not any attributes nor the body of the
// function.) Use them as the base name we pass to makeUniqueName(). This
// ensures that we will end up with a unique identifier even if two
// functions in the same scope have the exact same identifier.
let identifierCharacters = functionDecl
.with(\.attributes, [])
.with(\.body, nil)
.tokens(viewMode: .fixedUp)
.map(\.textWithoutBackticks)
.joined()
let crcValue = crc32(identifierCharacters.utf8)
let suffix = String(crcValue, radix: 16, uppercase: false)
// If the caller did not specify a prefix and the CRC32 value starts with a
// digit, include a single-character prefix to ensure that Swift's name
// demangling still works correctly.
var prefix = prefix
if prefix.isEmpty, let firstSuffixCharacter = suffix.first, firstSuffixCharacter.isWholeNumber {
prefix = "Z"
}
return makeUniqueName("\(prefix)\(suffix)")
}
}
// MARK: -
extension MacroExpansionContext {
/// Whether or not our generated warnings are suppressed in the current
/// lexical context.
///
/// The value of this property is `true` if the current lexical context
/// contains a node with the `@_semantics("testing.macros.nowarnings")`
/// attribute applied to it.
///
/// - Warning: This functionality is not part of the public interface of the
/// testing library. It may be modified or removed in a future update.
var areWarningsSuppressed: Bool {
#if DEBUG
for lexicalContext in self.lexicalContext {
guard let lexicalContext = lexicalContext.asProtocol((any WithAttributesSyntax).self) else {
continue
}
for attribute in lexicalContext.attributes {
if case let .attribute(attribute) = attribute,
attribute.attributeNameText == "_semantics",
case let .string(argument) = attribute.arguments,
argument.representedLiteralValue == "testing.macros.nowarnings" {
return true
}
}
}
#endif
return false
}
/// Emit a diagnostic message.
///
/// - Parameters:
/// - message: The diagnostic message to emit. The `node` and `position`
/// arguments to `Diagnostic.init()` are derived from the message's
/// `syntax` property.
func diagnose(_ message: DiagnosticMessage) {
diagnose(CollectionOfOne(message))
}
/// Emit a sequence of diagnostic messages.
///
/// - Parameters:
/// - messages: The diagnostic messages to emit.
func diagnose(_ messages: some Collection<DiagnosticMessage>) {
lazy var areWarningsSuppressed = areWarningsSuppressed
for message in messages {
if message.severity == .warning && areWarningsSuppressed {
continue
}
diagnose(
Diagnostic(
node: message.syntax,
position: message.syntax.positionAfterSkippingLeadingTrivia,
message: message,
fixIts: message.fixIts
)
)
}
}
/// Emit a diagnostic message for debugging purposes during development of the
/// testing library.
///
/// - Parameters:
/// - message: The message to emit into the build log.
func debug(_ message: some Any, node: some SyntaxProtocol) {
diagnose(DiagnosticMessage(syntax: Syntax(node), message: String(describing: message), severity: .warning))
}
}
|