Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lift restrictions on computed properties #59

Merged
merged 1 commit into from
Dec 22, 2023
Merged
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
13 changes: 9 additions & 4 deletions Sources/MMIOMacros/Macros/RegisterBankMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,15 @@ extension RegisterBankMacro: MMIOMemberMacro {
// Walk all the members of the struct.
var error = false
for member in structDecl.memberBlock.members {
// Ignore non-variable declaration.
guard let variableDecl = member.decl.as(VariableDeclSyntax.self) else {
guard
// Ignore non-variable declarations.
let variableDecl = member.decl.as(VariableDeclSyntax.self),
// Ignore non-stored properties.
!variableDecl.isComputedProperty
else {
continue
}

// Each variable declaration must be annotated with the
// RegisterBankOffsetMacro. Further syntactic checking will be performed
// by that macro.
Expand All @@ -64,10 +69,10 @@ extension RegisterBankMacro: MMIOMemberMacro {

// Retrieve the access level of the struct, so we can use the same
// access level for the unsafeAddress property and initializer.
let acl = structDecl.accessLevel
let acl = structDecl.accessLevel?.trimmed

return [
"\(acl)private(set) var unsafeAddress: UInt",
"\(acl) let unsafeAddress: UInt",
"""
#if FEATURE_INTERPOSABLE
var interposer: (any MMIOInterposer)?
Expand Down
13 changes: 8 additions & 5 deletions Sources/MMIOMacros/Macros/RegisterMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,26 @@ extension RegisterMacro: MMIOMemberMacro {
let declaration = declaration as! DeclSyntaxProtocol
// Can only applied to structs.
let structDecl = try declaration.requireAs(StructDeclSyntax.self, context)
let accessLevel = structDecl.accessLevel
let accessLevel = structDecl.accessLevel?.trimmed
let bitWidth = self.bitWidth.value

// Walk all the members of the struct.
var error = false
var isSymmetric = true
var bitFields = [BitFieldDescription]()
for member in structDecl.memberBlock.members {
// Each member must be a variable declaration.
guard let variableDecl = member.decl.as(VariableDeclSyntax.self) else {
_ = context.error(at: member.decl, message: .onlyMemberVarDecls())
error = true
guard
// Ignore non-variable declarations.
let variableDecl = member.decl.as(VariableDeclSyntax.self),
// Ignore non-stored properties.
!variableDecl.isComputedProperty
else {
continue
}

guard
// Each declaration must be annotated with exactly one bitField macro.
// Further syntactic checking will be performed by that macro.
let value = try? variableDecl.requireMacro(bitFieldMacros, context),
// This will always succeed.
let macroType = value.macroType as? any (BitFieldMacro.Type),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,36 @@ extension VariableDeclSyntax {
}
}

extension VariableDeclSyntax {
var isComputedProperty: Bool {
guard
self.bindings.count == 1,
let binding = self.bindings.first
else {
// Computed properties cannot have multiple bindings.
return false
}

// Computed properties must have an accessor block
guard let accessorBlock = binding.accessorBlock else { return false }

switch accessorBlock.accessors {
case .accessors(let accessors):
for accessor in accessors {
switch accessor.accessorSpecifier.tokenKind {
case .keyword(.willSet), .keyword(.didSet):
return false
default:
return true
}
}
return false
case .getter:
return true
}
}
}

extension ErrorDiagnostic {
static func expectedBindingSpecifier(_ node: Keyword) -> Self {
.init("'\(Macro.signature)' can only be applied to '\(node)' bindings")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ final class RegisterBankAndOffsetMacroTests: XCTestCase {
}
}

private (set) var unsafeAddress: UInt
let unsafeAddress: UInt

#if FEATURE_INTERPOSABLE
var interposer: (any MMIOInterposer)?
Expand Down
29 changes: 26 additions & 3 deletions Tests/MMIOMacrosTests/Macros/RegisterBankMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,21 @@ final class RegisterBankMacroTests: XCTestCase {
macros: Self.macros)
}

func test_members_varDeclsAreAnnotated() {
func test_members_storedVarDeclsAreAnnotated() {
assertMacroExpansion(
"""
@RegisterBank
struct S {
var v1: Int
@OtherAttribute var v2: Int
var v3: Int { willSet {} }
}
""",
expandedSource: """
struct S {
var v1: Int
@OtherAttribute var v2: Int
var v3: Int { willSet {} }
}
""",
diagnostics: [
Expand All @@ -112,26 +114,47 @@ final class RegisterBankMacroTests: XCTestCase {
fixIts: [
.init(message: "Insert '@RegisterBank(offset:)' macro")
]),
.init(
message:
ErrorDiagnostic
.expectedMemberAnnotatedWithMacro([RegisterBankOffsetMacro.self])
.message,
line: 5,
column: 3,
highlight: "var v3: Int { willSet {} }",
fixIts: [
.init(message: "Insert '@RegisterBank(offset:)' macro")
]),
],
macros: Self.macros,
indentationWidth: Self.indentationWidth)
}

func test_members_nonVarDeclIsOk() {
func test_members_nonStoredVarDeclsAreOk() {
assertMacroExpansion(
"""
@RegisterBank
struct S {
func f() {}
class C {}
var v: Void {}
var v: Void { get {} }
var v: Void { set {} }
var v: Void { _read {} }
var v: Void { _modify {} }
}
""",
expandedSource: """
struct S {
func f() {}
class C {}
var v: Void {}
var v: Void { get {} }
var v: Void { set {} }
var v: Void { _read {} }
var v: Void { _modify {} }

private (set) var unsafeAddress: UInt
let unsafeAddress: UInt

#if FEATURE_INTERPOSABLE
var interposer: (any MMIOInterposer)?
Expand Down
111 changes: 81 additions & 30 deletions Tests/MMIOMacrosTests/Macros/RegisterMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,56 +66,35 @@ final class RegisterMacroTests: XCTestCase {
indentationWidth: Self.indentationWidth)
}

func test_members_onlyVarDecls() {
func test_decl_onlyStruct_broken() {
assertMacroExpansion(
"""
@Register(bitWidth: 0x8)
struct S {
func f() {}
class C {}
}
@Register(bitWidth: 0x8) var v: Int
""",
expandedSource: """
struct S {
func f() {}
class C {}
}

extension S: RegisterValue {
}
var v: Int
""",
diagnostics: [
.init(
message: ErrorDiagnostic.onlyMemberVarDecls().message,
line: 3,
column: 3,
// FIXME: Improve this highlight
highlight: "func f() {}"),
.init(
message: ErrorDiagnostic.onlyMemberVarDecls().message,
line: 4,
column: 3,
// FIXME: Improve this highlight
highlight: "class C {}"),

// FIXME: https://github.com/apple/swift-syntax/issues/2206
],
macros: Self.macros,
indentationWidth: Self.indentationWidth)
macros: Self.macros)
}

func test_members_varDeclsAreAnnotated() {
func test_members_storedVarDeclsAreAnnotated() {
assertMacroExpansion(
"""
@Register(bitWidth: 0x8)
struct S {
var v1: Int
@OtherAttribute var v2: Int
var v3: Int { willSet {} }
}
""",
expandedSource: """
struct S {
var v1: Int
@OtherAttribute var v2: Int
var v3: Int { willSet {} }
}

extension S: RegisterValue {
Expand All @@ -132,11 +111,84 @@ final class RegisterMacroTests: XCTestCase {
line: 4,
column: 3,
highlight: "@OtherAttribute var v2: Int"),
.init(
message: ErrorDiagnostic.expectedMemberAnnotatedWithMacro(bitFieldMacros).message,
line: 5,
column: 3,
highlight: "var v3: Int { willSet {} }"),
],
macros: Self.macros,
indentationWidth: Self.indentationWidth)
}

func test_members_nonStoredVarDeclsAreOk() {
assertMacroExpansion(
"""
@Register(bitWidth: 0x8)
struct S {
func f() {}
class C {}
var v: Void {}
var v: Void { get {} }
var v: Void { set {} }
var v: Void { _read {} }
var v: Void { _modify {} }
}
""",
expandedSource: """
struct S {
func f() {}
class C {}
var v: Void {}
var v: Void { get {} }
var v: Void { set {} }
var v: Void { _read {} }
var v: Void { _modify {} }

private init() {
fatalError()
}

private var _never: Never

struct Raw: RegisterValueRaw {
typealias Value = S
typealias Storage = UInt8
var storage: Storage
init(_ storage: Storage) {
self.storage = storage
}
init(_ value: Value.ReadWrite) {
self.storage = value.storage
}

}

typealias Read = ReadWrite

typealias Write = ReadWrite

struct ReadWrite: RegisterValueRead, RegisterValueWrite {
typealias Value = S
var storage: UInt8
init(_ value: ReadWrite) {
self.storage = value.storage
}
init(_ value: Raw) {
self.storage = value.storage
}

}
}

extension S: RegisterValue {
}
""",
diagnostics: [],
macros: Self.macros,
indentationWidth: Self.indentationWidth)
}

func test_expansion_noFields() {
// FIXME: see expanded source formatting
assertMacroExpansion(
Expand Down Expand Up @@ -190,7 +242,6 @@ final class RegisterMacroTests: XCTestCase {
}

func test_expansion_noTypedFields() {
// FIXME: see expanded source formatting
assertMacroExpansion(
"""
@Register(bitWidth: 0x8)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,57 @@ final class VariableDeclSyntaxTests: XCTestCase {
}
}
}

func test_isComputedProperty() {
struct Vector {
var decl: VariableDeclSyntax
var isComputedProperty: Bool
var file: StaticString
var line: UInt

init(
decl: DeclSyntax,
isComputedProperty: Bool,
file: StaticString = #file,
line: UInt = #line
) {
self.decl = decl.as(VariableDeclSyntax.self)!
self.isComputedProperty = isComputedProperty
self.file = file
self.line = line
}
}

let vectors: [Vector] = [
.init(decl: "var v: Int", isComputedProperty: false),
.init(decl: "inout v: Int", isComputedProperty: false),
.init(decl: "let v: Int", isComputedProperty: false),
.init(decl: "var v, w: Int", isComputedProperty: false),
.init(decl: "var v: Int, b: Int", isComputedProperty: false),
.init(decl: "var _: Int", isComputedProperty: false),
.init(decl: "var (v, w): Int", isComputedProperty: false),
.init(decl: "var a", isComputedProperty: false),
.init(decl: "var v: _", isComputedProperty: false),
.init(decl: "var v: Int?", isComputedProperty: false),
.init(decl: "var v: [Int]", isComputedProperty: false),
.init(decl: "var v: (Int, Int)", isComputedProperty: false),
.init(decl: "var v: Reg<T>", isComputedProperty: false),
.init(decl: "var v: Swift.Int", isComputedProperty: false),
.init(decl: "var v: Int { willSet {} }", isComputedProperty: false),
.init(decl: "var v: Int { didSet {} }", isComputedProperty: false),
.init(decl: "var v: Void {}", isComputedProperty: true),
.init(decl: "var v: Void { get {} }", isComputedProperty: true),
.init(decl: "var v: Void { set {} }", isComputedProperty: true),
.init(decl: "var v: Void { _read {} }", isComputedProperty: true),
.init(decl: "var v: Void { _modify {} }", isComputedProperty: true),
]

for vector in vectors {
XCTAssertEqual(
vector.decl.isComputedProperty,
vector.isComputedProperty,
file: vector.file,
line: vector.line)
}
}
}