Property Wrapper Error with Variadic Parameter in Initializer

I was trying to implement a property wrapper and I was hit with the following error:

1.	While emitting IR SIL function "@$s17Firebase_Practice11ContentViewVACycfC".
 for 'init()' (at /Users/noahwilder/Desktop/Dating App/Practice/Firebase Practice/Firebase Practice/ContentView.swift:14:8)
0  swift                    0x000000010f144a63 PrintStackTraceSignalHandler(void*) + 51
1  swift                    0x000000010f144236 SignalHandler(int) + 358
2  libsystem_platform.dylib 0x00007fff7140a42d _sigtramp + 29
3  libsystem_platform.dylib 0x00007fd087198880 _sigtramp + 366535792
4  libsystem_c.dylib        0x00007fff712dfa1c abort + 120
5  libsystem_malloc.dylib   0x00007fff713d5647 has_default_zone0 + 0
6  libsystem_malloc.dylib   0x00007fff713d840e malloc_report + 151
7  libsystem_malloc.dylib   0x00007fff713c9275 realloc + 312
8  swift                    0x000000010ae5dacd swift::irgen::SingleScalarTypeInfo<(anonymous namespace)::ClassTypeInfo, swift::irgen::ReferenceTypeInfo>::getSchema(swift::irgen::ExplosionSchema&) const + 157
9  swift                    0x000000010afd31fe swift::SILInstructionVisitor<(anonymous namespace)::IRGenSILFunction, void>::visit(swift::SILInstruction*) + 35646
10 swift                    0x000000010afc7b4a swift::irgen::IRGenModule::emitSILFunction(swift::SILFunction*) + 9834
11 swift                    0x000000010ae72c50 swift::irgen::IRGenerator::emitGlobalTopLevel() + 1712
12 swift                    0x000000010afa48c5 performIRGeneration(swift::IRGenOptions&, swift::ModuleDecl*, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule> >, llvm::StringRef, swift::PrimarySpecificPaths const&, llvm::LLVMContext&, swift::SourceFile*, llvm::GlobalVariable**) + 1189
13 swift                    0x000000010ad8fa01 performCompile(swift::CompilerInstance&, swift::CompilerInvocation&, llvm::ArrayRef<char const*>, int&, swift::FrontendObserver*, swift::UnifiedStatsReporter*) + 36673
14 swift                    0x000000010ad832b4 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 6820
15 swift                    0x000000010ad107b3 main + 1219
16 libdyld.dylib            0x00007fff712117fd start + 1
17 libdyld.dylib            0x00000000000000a6 start + 2396973226
error: Abort trap: 6 (in target 'Firebase Practice' from project 'Firebase Practice')

After playing around with it for a bit, I am pretty sure that this is being caused by the convenience initializer in DatabaseBackedArray. More specifically I think the compiler is having trouble with the variadic parameter.

Here is the code that produced the error:

import SwiftUI
import Firebase
import CodableFirebase
import Combine 

struct SecondaryView: View {
    
    @DatabaseBackedArray(\.school, events: .all, actions: .on(.constructive) { $0.sort { $0.name < $1.name } })
    var schools: [School] = []
    
    var body: some View {
        Text("School: ").bold() +
            Text(schools.isEmpty ? "Loading..." : schools.first!.name)
    }
}

@propertyWrapper
final class DatabaseBackedArray<Element>: ObservableObject where Element: Codable & Identifiable {
    typealias ObserverHandle = UInt
    typealias Action = RealtimeDatabase.Action
    typealias Event = RealtimeDatabase.Event
    
    private(set) var reference: DatabaseReference
    
    private var currentValue: [Element]
    
    private var childAddedObserverHandle: ObserverHandle?
    private var childChangedObserverHandle: ObserverHandle?
    private var childRemovedObserverHandle: ObserverHandle?
    
    private var childAddedActions: [Action<[Element]>] = []
    private var childChangedActions: [Action<[Element]>] = []
    private var childRemovedActions: [Action<[Element]>] = []
    
    init(wrappedValue: [Element], _ path: KeyPath<RealtimeDatabase, RealtimeDatabase>, events: Event = .all,
         actions: [Action<[Element]>]) {
        currentValue = wrappedValue
        reference = RealtimeDatabase()[keyPath: path].reference
        
        for action in actions {
            if action.event.contains(.childAdded) {
                childAddedActions.append(action)
            }
            if action.event.contains(.childChanged) {
                childChangedActions.append(action)
            }
            if action.event.contains(.childRemoved) {
                childRemovedActions.append(action)
            }
        }
        
        if events.contains(.childAdded) {
            childAddedObserverHandle = reference.observe(.childAdded) { [self] snapshot in
                guard let value = snapshot.value, let decodedValue = try? FirebaseDecoder().decode(Element.self, from: value) else {
                    fatalError("Could not decode value from Firebase.")
                }
                self.objectWillChange.send()
                self.currentValue.append(decodedValue)
                self.childAddedActions.forEach { $0.action(&self.currentValue) }
            }
        }
        if events.contains(.childChanged) {
            childChangedObserverHandle = reference.observe(.childChanged) { [self] snapshot in
                guard let value = snapshot.value, let decodedValue = try? FirebaseDecoder().decode(Element.self, from: value) else {
                    fatalError("Could not decode value from Firebase.")
                }
                guard let changeIndex = self.currentValue.firstIndex(where: { $0.id == decodedValue.id }) else {
                    return
                }
                self.objectWillChange.send()
                self.currentValue[changeIndex] = decodedValue
                self.childChangedActions.forEach { $0.action(&self.currentValue) }
            }
        }
        if events.contains(.childRemoved) {
            childRemovedObserverHandle = reference.observe(.childRemoved) { [self] snapshot in
                guard let value = snapshot.value, let decodedValue = try? FirebaseDecoder().decode(Element.self, from: value) else {
                    fatalError("Could not decode value from Firebase.")
                }
                self.objectWillChange.send()
                self.currentValue.removeAll { $0.id == decodedValue.id }
                self.childRemovedActions.forEach { $0.action(&self.currentValue) }
            }
        }
    }
    
    convenience init(wrappedValue: [Element], _ path: KeyPath<RealtimeDatabase, RealtimeDatabase>, events: Event = .all, actions: Action<[Element]>...) {
        self.init(wrappedValue: wrappedValue, path, events: events, actions: actions)
    }
    
    private func setValue(to value: [Element]) {
        guard let encodedValue = try? FirebaseEncoder().encode(currentValue) else {
            fatalError("Could not encode value to Firebase.")
        }
        reference.setValue(encodedValue)
    }
    
    var wrappedValue: [Element] {
        get {
            return currentValue
        }
        set {
            self.objectWillChange.send()
            setValue(to: newValue)
        }
    }
    
    var projectedValue: Binding<[Element]> {
        return Binding(get: {
            return self.wrappedValue
        }) { newValue in
            self.wrappedValue = newValue
        }
    }
    
    var hasActiveObserver: Bool {
        return childAddedObserverHandle != nil || childChangedObserverHandle != nil || childRemovedObserverHandle != nil
    }
    var hasChildAddedObserver: Bool {
        return childAddedObserverHandle != nil
    }
    var hasChildChangedObserver: Bool {
        return childChangedObserverHandle != nil
    }
    var hasChildRemovedObserver: Bool {
        return childRemovedObserverHandle != nil
    }
    
    func connectObservers(for event: Event) {
        if event.contains(.childAdded) && childAddedObserverHandle == nil {
            childAddedObserverHandle = reference.observe(.childAdded) { [self] snapshot in
                guard let value = snapshot.value, let decodedValue = try? FirebaseDecoder().decode(Element.self, from: value) else {
                    fatalError("Could not decode value from Firebase.")
                }
                self.objectWillChange.send()
                self.currentValue.append(decodedValue)
                self.childAddedActions.forEach { $0.action(&self.currentValue) }
            }
        }
        if event.contains(.childChanged) && childChangedObserverHandle == nil {
            childChangedObserverHandle = reference.observe(.childChanged) { [self] snapshot in
                guard let value = snapshot.value, let decodedValue = try? FirebaseDecoder().decode(Element.self, from: value) else {
                    fatalError("Could not decode value from Firebase.")
                }
                guard let changeIndex = self.currentValue.firstIndex(where: { $0.id == decodedValue.id }) else {
                    return
                }
                self.objectWillChange.send()
                self.currentValue[changeIndex] = decodedValue
                self.childChangedActions.forEach { $0.action(&self.currentValue) }
                print("Child Changed")
            }
        }
        if event.contains(.childRemoved) && childRemovedObserverHandle == nil {
            childRemovedObserverHandle = reference.observe(.childRemoved) { [self] snapshot in
                guard let value = snapshot.value, let decodedValue = try? FirebaseDecoder().decode(Element.self, from: value) else {
                    fatalError("Could not decode value from Firebase.")
                }
                self.objectWillChange.send()
                self.currentValue.removeAll { $0.id == decodedValue.id }
                self.childRemovedActions.forEach { $0.action(&self.currentValue) }
                print("Child Removed")
                
            }
        }
    }
    
    func removeObserver(for event: Event) {
        if event.contains(.childAdded), let handle = childAddedObserverHandle {
            reference.removeObserver(withHandle: handle)
            self.childAddedObserverHandle = nil
        }
        if event.contains(.childChanged), let handle = childChangedObserverHandle {
            reference.removeObserver(withHandle: handle)
            self.childChangedObserverHandle = nil
        }
        if event.contains(.childRemoved), let handle = childRemovedObserverHandle {
            reference.removeObserver(withHandle: handle)
            self.childRemovedObserverHandle = nil
        }
    }
    func removeAction(_ action: Action<[Element]>) {
        if action.event.contains(.childAdded) {
            childAddedActions.removeAll { $0.id == action.id }
        }
        if action.event.contains(.childChanged) {
            childChangedActions.removeAll { $0.id == action.id }
        }
        if action.event.contains(.childRemoved) {
            childRemovedActions.removeAll { $0.id == action.id }
        }
    }
    
    func removeAllActions(for event: Event) {
        if event.contains(.childAdded) {
            childAddedActions = []
        }
        if event.contains(.childChanged) {
            childChangedActions = []
        }
        if event.contains(.childRemoved) {
            childRemovedActions = []
        }
    }
}

struct School: Codable, Identifiable {
    /// The unique id of the school.
    var id: String
    
    /// The name of the school.
    var name: String
    
    /// The city of the school.
    var city: String
    
    /// The province of the school.
    var province: String
    
    /// Email domains for student emails from the school.
    var domains: [String]
}

@dynamicMemberLookup
struct RealtimeDatabase {
    private var path: [String]
    
    var reference: DatabaseReference {
        var ref = Database.database().reference()
        for component in path {
            ref = ref.child(component)
        }
        return ref
    }
    
    init(previous: Self? = nil, child: String? = nil) {
        if let previous = previous {
            path = previous.path
        } else {
            path = []
        }
        if let child = child {
            path.append(child)
        }
    }
    
    static subscript(dynamicMember member: String) -> Self {
        return Self(child: member)
    }
    
    subscript(dynamicMember member: String) -> Self {
        return Self(child: member)
    }
    
    static subscript(dynamicMember keyPath: KeyPath<Self, Self>) -> Self {
        return Self()[keyPath: keyPath]
    }
    
    static let reference = Database.database().reference()
    
    struct Event: OptionSet, Hashable {
        let rawValue: UInt
        static let childAdded = Event(rawValue: 1 << 0)
        static let childChanged = Event(rawValue: 1 << 1)
        static let childRemoved = Event(rawValue: 1 << 2)
        
        static let all: Event = [.childAdded, .childChanged, .childRemoved]
        static let constructive: Event = [.childAdded, .childChanged]
        static let destructive: Event = .childRemoved
    }
    
    struct Action<Value>: Identifiable {
        
        let id = UUID()
        let event: Event
        let action: (inout Value) -> Void
        
        private init(on event: Event, perform action: @escaping (inout Value) -> Void) {
            self.event = event
            self.action = action
        }
        
        static func on<Value>(_ event: RealtimeDatabase.Event, perform action: @escaping (inout Value) -> Void) -> Action<Value> {
            return Action<Value>(on: event, perform: action)
        }
    }
}

Does anyone know why this is happening? Is this indeed a bug?


Update

Here is a more generalized version of the problem that does not have the same external dependencies that the previous example has:

import SwiftUI

struct FooView: View {
    
    @Bar(things: 4)
    var foo: Int = []
    var body: some View {
        Text("Hello, World!")
    }
}


@propertyWrapper
struct Bar<Value> {
    var baz: [Value]
    var someStuff: [Value]
    
    init(wrappedValue: [Value], things: [Value]) {
        self.baz = wrappedValue
        self.someStuff = things
    }
    
    init(wrappedValue: [Value], things: Value...) {
        self.init(wrappedValue: wrappedValue, things: things)
    }
    
    var wrappedValue: Value {
        return baz.randomElement()!
    }
}

This fails with a Segmentation fault 11:

1.	While emitting IR SIL function "@$s17Firebase_Practice7FooViewV3fooACSi_tcfC".
 for 'init(foo:)' (at /Users/noahwilder/Desktop/Dating App/Practice/Firebase Practice/Firebase Practice/FooView.swift:11:8)
0  swift                    0x000000010b1e7a63 PrintStackTraceSignalHandler(void*) + 51
1  swift                    0x000000010b1e7236 SignalHandler(int) + 358
2  libsystem_platform.dylib 0x00007fff7140a42d _sigtramp + 29
3  swift                    0x00000001070180ed swift::irgen::RecordTypeInfoImpl<(anonymous namespace)::LoadableStructTypeInfo, swift::irgen::LoadableTypeInfo, (anonymous namespace)::StructFieldInfo, true>::getSchema(swift::irgen::ExplosionSchema&) const + 61
4  swift                    0x000000010706ab4a swift::irgen::IRGenModule::emitSILFunction(swift::SILFunction*) + 9834
5  swift                    0x0000000106f15c50 swift::irgen::IRGenerator::emitGlobalTopLevel() + 1712
6  swift                    0x00000001070478c5 performIRGeneration(swift::IRGenOptions&, swift::ModuleDecl*, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule> >, llvm::StringRef, swift::PrimarySpecificPaths const&, llvm::LLVMContext&, swift::SourceFile*, llvm::GlobalVariable**) + 1189
7  swift                    0x0000000106e32a01 performCompile(swift::CompilerInstance&, swift::CompilerInvocation&, llvm::ArrayRef<char const*>, int&, swift::FrontendObserver*, swift::UnifiedStatsReporter*) + 36673
8  swift                    0x0000000106e262b4 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 6820
9  swift                    0x0000000106db37b3 main + 1219
10 libdyld.dylib            0x00007fff712117fd start + 1
error: Segmentation fault: 11 (in target 'Firebase Practice' from project 'Firebase Practice')

I think this may be https://bugs.swift.org/browse/SR-11059 . It may be worth trying a recent dev snapshot to see if that resolves it.

I think this is fixed on master, might be worth downloading the latest development toolchain and see if it works.

(Also, please try to isolate the crashing code without including external dependencies if possible)

Thank you, I provided a more general example at the bottom of my post per your suggestion.

Your example does not compile. I fixed it though and it compiles now:

struct Foo {
  @Bar(wrappedValue: [1], things: [1, 2])
  var foo: Int
}

@propertyWrapper
struct Bar<Value> {
  var baz: [Value]
  var someStuff: [Value]
    
  init(wrappedValue: [Value], things: [Value]) {
    self.baz = wrappedValue
    self.someStuff = things
  }
    
  init(wrappedValue: [Value], things: Value...) {
    self.init(wrappedValue: wrappedValue, things: things)
  }
    
  var wrappedValue: Value {
    return baz.randomElement()!
  }
}

Is this what you were trying to do?

@suyashsrijan, the point was that when trying to use the variadic initializer for the property wrapper rather than the array, it does not compile. So I’m assuming this is a bug?

@Bar(wrappedValue: [1], things: 1, 2, 3) var foo: Int

works for me.

Do you know why my example doesn’t compile? What is wrong with the code?

What version of Xcode/Swift are you using? I tried on Xcode 11.3 (Swift 5.1.3) and master (Swift 5.2 dev) and the reduced example compiles. I haven't tried the original example because I don't have the right dependencies at the moment (i.e. CodableFirebase).

Btw the original problem with the reduced example was that you were doing var foo: Int = [] which is not correct (since Int != [Int]). In order to do that, you need to pass it directly to the wrapper i.e. @Bar(wrappedValue: [], things: 4) var foo: Int

I do think that defining a wrappedValue argument in the initializer with a type that is different from the wrappedValue property might be a bug.

Terms of Service

Privacy Policy

Cookie Policy