File: MacroExpansionContextAdditions.swift

package info (click to toggle)
swiftlang 6.1.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 2,791,532 kB
  • sloc: cpp: 9,901,743; ansic: 2,201,431; asm: 1,091,827; python: 308,252; objc: 82,166; f90: 80,126; lisp: 38,358; pascal: 25,559; sh: 20,429; ml: 5,058; perl: 4,745; makefile: 4,484; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (150 lines) | stat: -rw-r--r-- 5,226 bytes parent folder | download | duplicates (2)
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))
  }
}