File: MacroExpansionContextAdditions.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (144 lines) | stat: -rw-r--r-- 5,121 bytes parent folder | download
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
//
// 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()

    // Strip out any characters in the function's signature that won't play well
    // in a generated symbol name.
    let identifier = String(
      identifierCharacters.map { character in
        if character.isLetter || character.isWholeNumber {
          return character
        }
        return "_"
      }
    )

    // If there is a non-ASCII character in the identifier, we might be
    // stripping it out above because we are only looking for letters and
    // digits. If so, add in a hash of the identifier to improve entropy and
    // reduce the risk of a collision.
    //
    // For example, the following function names will produce identical unique
    // names without this mutation:
    //
    // @Test(arguments: [0]) func A(🙃: Int) {}
    // @Test(arguments: [0]) func A(🙂: Int) {}
    //
    // Note the check here is not the same as the one above: punctuation like
    // "(" should be replaced, but should not cause a hash to be emitted since
    // it does not contribute any entropy to the makeUniqueName() algorithm.
    //
    // The intent here is not to produce a cryptographically strong hash, but to
    // disambiguate between superficially similar function names. A collision
    // may still occur, but we only need it to be _unlikely_. CRC-32 is good
    // enough for our purposes.
    if !identifierCharacters.allSatisfy(\.isASCII) {
      let crcValue = crc32(identifierCharacters.utf8)
      let suffix = String(crcValue, radix: 16, uppercase: false)
      return makeUniqueName("\(prefix)\(identifier)_\(suffix)")
    }

    return makeUniqueName("\(prefix)\(identifier)")
  }
}

// MARK: -

extension MacroExpansionContext {
  /// 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(
      Diagnostic(
        node: message.syntax,
        position: message.syntax.positionAfterSkippingLeadingTrivia,
        message: message,
        fixIts: message.fixIts
      )
    )
  }

  /// Emit a sequence of diagnostic messages.
  ///
  /// - Parameters:
  ///   - messages: The diagnostic messages to emit.
  func diagnose(_ messages: some Sequence<DiagnosticMessage>) {
    for message in messages {
      diagnose(message)
    }
  }

  /// 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))
  }
}