File: SuiteDeclarationMacro.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 (150 lines) | stat: -rw-r--r-- 6,269 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
145
146
147
148
149
150
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 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
//

public import SwiftSyntax
public import SwiftSyntaxMacros

/// A type describing the expansion of the `@Suite` attribute macro.
///
/// This type is used to implement the `@Suite` attribute macro. Do not use it
/// directly.
public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable {
  public static func expansion(
    of node: AttributeSyntax,
    providingMembersOf declaration: some DeclGroupSyntax,
    in context: some MacroExpansionContext
  ) throws -> [DeclSyntax] {
    guard _diagnoseIssues(with: declaration, suiteAttribute: node, in: context) else {
      return []
    }
    return _createTestContainerDecls(for: declaration, suiteAttribute: node, in: context)
  }

  public static func expansion(
    of node: AttributeSyntax,
    providingPeersOf declaration: some DeclSyntaxProtocol,
    in context: some MacroExpansionContext
  ) throws -> [DeclSyntax] {
    // The peer macro expansion of this macro is only used to diagnose misuses
    // on symbols that are not decl groups.
    if !declaration.isProtocol((any DeclGroupSyntax).self) {
      _ = _diagnoseIssues(with: declaration, suiteAttribute: node, in: context)
    }
    return []
  }

  /// Diagnose issues with a `@Suite` declaration.
  ///
  /// - Parameters:
  ///   - declaration: The type declaration to diagnose.
  ///   - suiteAttribute: The `@Suite` attribute applied to `declaration`.
  ///   - context: The macro context in which the expression is being parsed.
  ///
  /// - Returns: Whether or not macro expansion should continue (i.e. stopping
  ///   if a fatal error was diagnosed.)
  private static func _diagnoseIssues(
    with declaration: some DeclSyntaxProtocol,
    suiteAttribute: AttributeSyntax,
    in context: some MacroExpansionContext
  ) -> Bool {
    var diagnostics = [DiagnosticMessage]()
    defer {
      context.diagnose(diagnostics)
    }

    // Check if the lexical context is appropriate for a suite or test.
    diagnostics += diagnoseIssuesWithLexicalContext(context.lexicalContext, containing: declaration, attribute: suiteAttribute)
    diagnostics += diagnoseIssuesWithLexicalContext(declaration, containing: declaration, attribute: suiteAttribute)

    // Suites inheriting from XCTestCase are not supported.
    if let declaration = declaration.asProtocol((any DeclGroupSyntax).self),
       declaration.inherits(fromTypeNamed: "XCTestCase", inModuleNamed: "XCTest") {
      diagnostics.append(.xcTestCaseNotSupported(declaration, whenUsing: suiteAttribute))
    }

    // @Suite cannot be applied to a type extension (although a type extension
    // can still contain test functions and test suites.)
    if let extensionDecl = declaration.as(ExtensionDeclSyntax.self) {
      diagnostics.append(.attributeHasNoEffect(suiteAttribute, on: extensionDecl))
    }

    // Check other attributes on the declaration. Note that it should be
    // impossible to reach this point if the declaration can't have attributes.
    if let attributedDecl = declaration.asProtocol((any WithAttributesSyntax).self) {
      // Only one @Suite attribute is supported.
      let suiteAttributes = attributedDecl.attributes(named: "Suite")
      if suiteAttributes.count > 1 {
        diagnostics.append(.multipleAttributesNotSupported(suiteAttributes, on: declaration))
      }
    }

    return !diagnostics.lazy.map(\.severity).contains(.error)
  }

  /// Create a declaration for a type that conforms to the `__TestContainer`
  /// protocol and which contains the given suite type.
  ///
  /// - Parameters:
  ///   - declaration: The type declaration the result should encapsulate.
  ///   - suiteAttribute: The `@Suite` attribute applied to `declaration`.
  ///   - context: The macro context in which the expression is being parsed.
  ///
  /// - Returns: An array of declarations providing runtime information about
  ///   the test suite type `declaration`.
  private static func _createTestContainerDecls(
    for declaration: some DeclGroupSyntax,
    suiteAttribute: AttributeSyntax,
    in context: some MacroExpansionContext
  ) -> [DeclSyntax] {
    var result = [DeclSyntax]()

    if declaration.is(ExtensionDeclSyntax.self) {
      // No declaration is emitted for an extension. One can be synthesized at
      // runtime if one is not available for the type declaration itself.
      return []
    }

    if let genericGuardDecl = makeGenericGuardDecl(guardingAgainst: declaration, in: context) {
      result.append(genericGuardDecl)
    }

    // Parse the @Suite attribute.
    let attributeInfo = AttributeInfo(byParsing: suiteAttribute, on: declaration, in: context)

    // The emitted type must be public or the compiler can optimize it away
    // (since it is not actually used anywhere that the compiler can see.)
    //
    // The emitted type must be deprecated to avoid causing warnings in client
    // code since it references the suite metatype, which may be deprecated
    // to allow test functions to validate deprecated APIs. The emitted type is
    // also annotated unavailable, since it's meant only for use by the testing
    // library at runtime. The compiler does not allow combining 'unavailable'
    // and 'deprecated' into a single availability attribute: rdar://111329796
    let typeName = declaration.type.tokens(viewMode: .fixedUp).map(\.textWithoutBackticks).joined()
    let enumName = context.makeUniqueName("__🟠$test_container__suite__\(typeName)")
    result.append(
      """
      @available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.")
      enum \(enumName): Testing.__TestContainer {
        static var __tests: [Testing.Test] {
          get async {[
            .__type(
              \(declaration.type.trimmed).self,
              \(raw: attributeInfo.functionArgumentList(in: context))
            )
          ]}
        }
      }
      """
    )

    return result
  }
}