Seemingly getting null in non-nullable function parameter

73 Views Asked by At

NSTextElementProvider has a function

optional func offset(
    from: NSTextLocation,
    to: NSTextLocation
) -> Int

The to param here isn't an optional. NSTextLocation is a protocol. So, I was expecting it to require an instance of an object implementing the protocol. But, I seem to be getting passed what seems to be a null (from AppKit) (BuiltIn.RawPointer) 0x0.

Why is the compiler allowing this (or is that allowed)?

To guard against this, I'm now doing something like this


 var end: MarkupText.Location
 if let to = to as? MarkupText.Location {
   end = to
 } else {
   end = range.upperBound
 }

Is there a better way to do this (or avoid it)? Thanks

Edit:

Sample Code to Reproduce (also available at https://github.com/georgemp/TextLocationCrash). I'm using a custom NSTextLocation, and NSTextLayoutManager. The crash seems to occur when setting a NSTextContainer on the NSTextLayoutManager (if I don't set the text container, the code does not crash).

//
//  ViewController.swift
//  TextLocationCrash
//
//  Created by George Philip Malayil on 06/10/23.
//

import Cocoa

class ViewController: NSViewController {
    var layoutManager: NSTextLayoutManager!
    var textContainer: NSTextContainer!
    var documentModel: DocumentModel!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        let textContainer = NSTextContainer(size: NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
        self.textContainer = textContainer

        let layoutManager = NSTextLayoutManager()
        layoutManager.textContainer = textContainer
        self.layoutManager = layoutManager

        self.documentModel = DocumentModel()
        documentModel.attach(textLayoutManager: layoutManager)

        layoutManager.enumerateTextLayoutFragments(from: CustomTextLocation(column: 1), options: [.reverse, .ensuresLayout]) { fragment in
            print("\(fragment)")

            return false
        }
    }
}
//
//  Document.swift
//  TextLocationCrash
//
//  Created by George Philip Malayil on 06/10/23.
//

import AppKit

class CustomTextLocation: NSObject {
    var column: Int = 1 // Not zero indexed.

    init(column: Int) {
        self.column = column
    }

    override public var description: String {
        "\(column)"
    }
}

extension CustomTextLocation: NSTextLocation {
    func compare(_ location: NSTextLocation) -> ComparisonResult {
        guard let location = location as? CustomTextLocation else {
            fatalError("Expected Document.Location")
        }

        if column < location.column {
            return .orderedAscending
        } else if column == location.column {
            return .orderedSame
        } else {
            return .orderedDescending
        }
    }
}
//
//  DocumentModel.swift
//  TextLocationCrash
//
//  Created by George Philip Malayil on 06/10/23.
//

import AppKit

class DocumentModel: NSTextContentManager {
    var layoutManager: NSTextLayoutManager!
    var data = ""

    override var documentRange: NSTextRange {
        NSTextRange(location: CustomTextLocation(column: 1), end: CustomTextLocation(column: 1))!
    }

    func attach(textLayoutManager: NSTextLayoutManager) {
        self.layoutManager = textLayoutManager
        super.addTextLayoutManager(layoutManager)
    }

    // MARK: NSTextContentManager overrides
    override func textElements(for range: NSTextRange) -> [NSTextElement] {
        [NSTextParagraph(textContentManager: self)]
    }

    // MARK: NSTextElementProvider Overrides
    override func enumerateTextElements(from textLocation: NSTextLocation?, options: NSTextContentManager.EnumerationOptions = [], using block: (NSTextElement) -> Bool) -> NSTextLocation? {
        guard let from = textLocation as? CustomTextLocation else {
            return nil
        }

        let textElements = textElements(for: NSTextRange(location: from, end: documentRange.endLocation)!)
        for textElement in textElements {
            let _ = block(textElement)
        }

        return documentRange.endLocation
    }

    override func offset(from: NSTextLocation, to: NSTextLocation) -> Int {
        guard let from = from as? CustomTextLocation,
              let to = to as? CustomTextLocation else {
            fatalError("Expected CustomTextLocation")
        }

        return to.column - from.column
    }
}
0

There are 0 best solutions below