Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 157 additions & 25 deletions Demo/Demo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

44 changes: 29 additions & 15 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 6.0
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
Expand All @@ -21,28 +21,21 @@ let package = Package(
name: "QuickLayout",
targets: ["QuickLayout"]
),
.library(
name: "QuickLayoutCore",
targets: ["QuickLayoutCore"]
),
.library(
name: "FastResultBuilder",
targets: ["FastResultBuilder"]
),
.library(
name: "QuickLayoutBridge",
targets: ["QuickLayoutBridge"]
),
],
dependencies: [
.package(url: "https://github.com/apple/swift-syntax.git", from: "600.0.0")
.package(url: "https://github.com/apple/swift-syntax.git", from: "602.0.0"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing.git", from: "0.6.4"),
],
targets: [
.target(
name: "QuickLayout",
dependencies: [
"QuickLayoutMacro",
"QuickLayoutBridge",
.target(name: "QuickLayoutBridge", condition: .when(platforms: [.iOS])),
],
path: "Sources/QuickLayout/QuickLayout",
exclude: [
Expand All @@ -55,11 +48,11 @@ let package = Package(
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
],
path: "Sources/QuickLayout/QuickLayoutMacro",
path: "Sources/QuickLayout/QuickLayoutMacro"
),
.target(
name: "QuickLayoutCore",
path: "Sources/QuickLayout/QuickLayoutCore",
path: "Sources/QuickLayout/QuickLayoutCore"
),
.target(
name: "FastResultBuilder",
Expand All @@ -70,12 +63,33 @@ let package = Package(
),
.target(
name: "QuickLayoutBridge",
dependencies: ["FastResultBuilder", "QuickLayoutCore"],
dependencies: [
"FastResultBuilder",
.target(name: "QuickLayoutCore", condition: .when(platforms: [.iOS])),
],
path: "Sources/QuickLayout/QuickLayoutBridge",
exclude: [
"__server_snapshot_tests__",
"__tests__",
]
),
],
.testTarget(
name: "QuickLayoutMacroTests",
dependencies: [
"QuickLayoutMacro",
.swiftSyntaxMacrosTestSupport,
],
path: "Sources/QuickLayout/QuickLayoutMacroTests"
)
]
)

extension Target.Dependency {
// swift-syntax
static let swiftSyntax = Self.product(name: "SwiftSyntax", package: "swift-syntax")
static let swiftSyntaxMacros = Self.product(name: "SwiftSyntaxMacros", package: "swift-syntax")
static let swiftCompilerPlugin = Self.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
static let swiftSyntaxBuilder = Self.product(name: "SwiftSyntaxBuilder", package: "swift-syntax")
static let swiftParserDiagnostics = Self.product(name: "SwiftParserDiagnostics", package: "swift-syntax")
static let swiftSyntaxMacrosTestSupport = Self.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax")
}
2 changes: 2 additions & 0 deletions Sources/QuickLayout/QuickLayout/QuickLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/

#if canImport(QuickLayoutBridge)
@_exported import QuickLayoutBridge
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ struct BarModel {
let description: String
let shareURL: URL
}
#if compiler(>=6.1)
nonisolated extension BarModel: Hashable {}
#else
extension BarModel: Hashable {}
#endif

// swiftlint:disable force_unwrapping
actor BarsStore {
Expand Down
2 changes: 1 addition & 1 deletion Sources/QuickLayout/QuickLayoutBridge/QuickLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public func Grid(
alignment: Alignment = .center,
horizontalSpacing: CGFloat = 0,
verticalSpacing: CGFloat = 0,
@FastArrayBuilder<GridRowElement> rows: () -> [GridRowElement] = { [] },
@FastArrayBuilder<GridRowElement> rows: () -> [GridRowElement] = { [] }
) -> Element & Layout {
GridElement(rows: rows(), alignment: alignment, horizontalSpacing: horizontalSpacing, verticalSpacing: verticalSpacing)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
@attached(extension, conformances: HasBody)
public macro QuickLayout() = #externalMacro(module: "QuickLayoutMacro", type: "QuickLayout")

#if compiler(>=6)
/**
This macro is an implementation detail of @QuickLayout. It is not intended to be used directly.
*/
@attached(body)
public macro _QuickLayoutInjection(_ value: String) = #externalMacro(module: "QuickLayoutMacro", type: "QuickLayoutInjection")
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ private let disappearanceAnimationKey = "BodyAppearanceCoordinator-disappearance
private let disappearanceAnimationIDKey = "BodyAppearanceCoordinator-disappearance-id"

@MainActor
final class BodyAppearanceCoordinator: NSObject, @preconcurrency CAAnimationDelegate {
final class BodyAppearanceCoordinator: NSObject {
private var disappearanceAnimationID: Int = 0
private var appearingViews: Set<UIView> = []
private var activeViews: Set<UIView> = []
Expand Down Expand Up @@ -123,3 +123,9 @@ fileprivate extension UIView {
animationKeys.forEach { layer.removeAnimation(forKey: $0) }
}
}

#if compiler(>=6)
extension BodyAppearanceCoordinator: @preconcurrency CAAnimationDelegate {}
#else
extension BodyAppearanceCoordinator: CAAnimationDelegate {}
#endif
7 changes: 6 additions & 1 deletion Sources/QuickLayout/QuickLayoutCore/Element+UIKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@ private func sanitizeSize(_ size: CGSize) -> CGSize {
}

// patternlint-disable-next-line retroactive-conformance-systemlib
extension UIView: @preconcurrency LeafElement {
#if compiler(>=6)
extension UIView: @preconcurrency LeafElement {}
#else
extension UIView: LeafElement {}
#endif

extension UIView {
public func quick_layoutThatFits(_ proposedSize: CGSize) -> LayoutNode {
assert(Thread.isMainThread, "UIViews can be laid out only on the main thread.")

Expand Down
44 changes: 44 additions & 0 deletions Sources/QuickLayout/QuickLayoutMacro/QuickLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,27 @@

import SwiftSyntax
import SwiftSyntaxMacros
import SwiftDiagnostics
import Foundation

enum QuickLayoutDiagnostic: DiagnosticMessage {
case missingRequiredImplementation(functionName: String, requiredCall: String)

var message: String {
switch self {
case .missingRequiredImplementation(let functionName, let requiredCall):
return "Method '\(functionName)' is missing required implementation call. Below Swift 6.0, you must manually add the following call:\n\(requiredCall)"
}
}

var diagnosticID: MessageID {
MessageID(domain: "QuickLayout", id: "missingRequiredImplementation")
}

var severity: DiagnosticSeverity {
.error
}
}

public struct QuickLayout: ExtensionMacro, MemberMacro, MemberAttributeMacro {
// MARK: - ExtensionMacro
Expand Down Expand Up @@ -63,7 +84,30 @@ public struct QuickLayout: ExtensionMacro, MemberMacro, MemberAttributeMacro {
case .deferToProvidedDefinition:
continue
case .injectIntoProvidedDefinition(let impl):
#if compiler(>=6)
return ["@_QuickLayoutInjection(\"\(raw: impl(memberFunction))\")"]
#else
// 在 Swift 6.0 以下,检查方法体中是否已包含必要的实现调用
let requiredCall = impl(memberFunction)
let functionBody = memberFunction.body?.statements.description ?? ""

// 检查方法体中是否包含必要的调用
if (functionBody as NSString).contains(requiredCall) {
// 已经包含必要的实现,跳过
continue
} else {
// 缺少必要的实现,抛出诊断错误
let diagnostic = Diagnostic(
node: memberFunction,
message: QuickLayoutDiagnostic.missingRequiredImplementation(
functionName: memberFunction.name.text,
requiredCall: requiredCall
)
)
context.diagnose(diagnostic)
continue
}
#endif
}
}
} else if let memberVariable = member.variable {
Expand Down
77 changes: 76 additions & 1 deletion Sources/QuickLayout/QuickLayoutMacroTests/QuickLayoutTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/

import QuickLayoutMacro
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import XCTest
#if canImport(QuickLayoutMacro)
@testable import QuickLayoutMacro

// patternlint-disable meta-subclass-view

Expand Down Expand Up @@ -117,6 +118,7 @@ class QuickLayoutTests: XCTestCase {
}

func testApplicableMethodsInjectQuickLayoutBridgeementation() throws {
#if compiler(>=6)
assertMacroExpansion(
#"""
@QuickLayout
Expand Down Expand Up @@ -171,6 +173,78 @@ class QuickLayoutTests: XCTestCase {
macros: testMacros,
indentationWidth: .spaces(2)
)
#else
assertMacroExpansion(
#"""
@QuickLayout
class TestView: UIView {
var body: Layout {
EmptyLayout()
}

public override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow)
someUniqueWillMoveToWindowLogic()
}

public override func layoutSubviews() {
super.layoutSubviews()
someUniqueLayoutSubviewsLogic()
}
}
"""#,
expandedSource:
#"""
class TestView: UIView {
@LayoutBuilder
var body: Layout {
EmptyLayout()
}

public override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow)
someUniqueWillMoveToWindowLogic()
}

public override func layoutSubviews() {
super.layoutSubviews()
someUniqueLayoutSubviewsLogic()
}

public override func sizeThatFits(_ size: CGSize) -> CGSize {
return _QuickLayoutViewImplementation.sizeThatFits(self, size: size) ?? super.sizeThatFits(size)
}

public override func quick_flexibility(for axis: QuickLayoutCore.Axis) -> Flexibility {
return _QuickLayoutViewImplementation.quick_flexibility(self, for: axis) ?? super.quick_flexibility(for: axis)
}
}

extension TestView: HasBody {
}
"""#,
diagnostics: [
.init(
message: """
Method 'willMove' is missing required implementation call. Below Swift 6.0, you must manually add the following call:
_QuickLayoutViewImplementation.willMove(self, toWindow: newWindow)
""",
line: 7,
column: 3
),
.init(
message: """
Method 'layoutSubviews' is missing required implementation call. Below Swift 6.0, you must manually add the following call:
_QuickLayoutViewImplementation.layoutSubviews(self)
""",
line: 12,
column: 3
),
],
macros: testMacros,
indentationWidth: .spaces(2)
)
#endif
}

func testTypeScopedFunctionsDoNotConflict() throws {
Expand Down Expand Up @@ -476,3 +550,4 @@ class QuickLayoutTests: XCTestCase {
)
}
}
#endif