SourceKitten icon indicating copy to clipboard operation
SourceKitten copied to clipboard

Off-by-one bug in generic inherited type.

Open MatyasKriz opened this issue 5 years ago • 2 comments

Hey, I'm maintaining Cuckoo and recently a bug surfaced (though it was present all this time, just under our radar) where SourceKitten includes the last > of the generic inherited type.

I've been using SourceKitten 0.23.2 until now; though upgrading to 0.29.0 didn't help, unfortunately.

Xcode 11.5, though it's the same result on 10.2.1.

Some examples:

struct gg<G: T<Int>> {}  // "key.inheritedTypes" is "T<Int>>"
func bg<T: G<Void>>()    // "key.inheritedTypes" is "G<Void>>"

This applies for any class, struct, or function.

I've tried tracking this bug down inside the project (master) with the intention of filing a PR, though the source code gets increasingly complex for my iOS brain. I found that at Request.swift:88, the string is already incorrect and I'm unsure whether it's a SourceKitten bug or SourceKit provides incorrect numbers.

I've created a general structure test that should go green when this is resolved:

func testGenericInheritedType() throws {
        let structure = try Structure(file: File(contents: "class Foo<T, U: V<T>> {}"))
        let expected: NSDictionary = [
            "key.substructure": [
                [
                    "key.kind": "source.lang.swift.decl.class",
                    "key.accessibility": "source.lang.swift.accessibility.internal",
                    "key.offset": 0,
                    "key.nameoffset": 6,
                    "key.namelength": 3,
                    "key.bodyoffset": 23,
                    "key.bodylength": 0,
                    "key.length": 24,
                    "key.name": "Foo",
                    "key.substructure": [
                        [
                            "key.kind": "source.lang.swift.decl.generic_type_param",
                            "key.length": 1,
                            "key.name": "T",
                            "key.namelength": 1,
                            "key.nameoffset": 10,
                            "key.offset": 10,
                        ],
                        [
                            "key.elements": [
                                [
                                    "key.kind": "source.lang.swift.structure.elem.typeref",
                                    "key.length": 4,
                                    "key.offset": 16,
                                ]
                            ],
                            "key.inheritedtypes": [
                                [
                                    "key.name": "V<T>",
                                ]
                            ],
                            "key.kind": "source.lang.swift.decl.generic_type_param",
                            "key.length": 7,
                            "key.name": "U",
                            "key.namelength": 1,
                            "key.nameoffset": 13,
                            "key.offset": 13,
                      ]
                    ]
                ]
            ],
            "key.offset": 0,
            "key.diagnostic_stage": "source.diagnostic.stage.swift.parse",
            "key.length": 24,
        ]
        XCTAssertEqual(toNSDictionary(structure.dictionary), expected, "should generate expected structure")
    }

Though I'm not sure about the specific offsets, so I wouldn't trust me on this.

In Cuckoo I added some simple protection against this, but it's pretty fragile (it just checks for >> in the last parameter), so I wanted to get your feedback on the complexity of this issue and whether we should rather compensate on our side rather than waiting for a fix.

Oh and I tried looking for this issue, so forgive me if I overlooked a duplicate, feel free to close this if it's known and/or unfixable. If I forgot to disclose some important info, let me know! 🙂

One last thing, this bug can be sidestepped by adding a space in front of the last >. So <T: G<Void> > actually returns the correct inherited type of just G<Void>.

EDIT: One actually important thing might be, this issue only arises when the inheritance type is generic as well, for example <T: G> correctly returns just G as the inherited type.

MatyasKriz avatar Jun 28 '20 17:06 MatyasKriz

This is a Swift bug, it's wrong coming out of SourceKit. Latest swift/master:

; cat Test.swift
struct gg<G: T<Int>> {}
; sourcekitd-test -req structure Test.swift 
{
  key.request: source.request.configuration.global,
  key.optimize_for_ide: 1
}
{
  key.request: source.request.editor.open,
  key.name: "/Users/johnf/project/SourceKit/Test.swift",
  key.sourcefile: "/Users/johnf/project/SourceKit/Test.swift",
  key.enablesyntaxmap: 0,
  key.syntaxtreetransfermode: source.syntaxtree.transfer.off,
  key.enablesubstructure: 1,
  key.syntactic_only: 1
}
{
  key.offset: 0,
  key.length: 24,
  key.diagnostic_stage: source.diagnostic.stage.swift.parse,
  key.substructure: [
    {
      key.kind: source.lang.swift.decl.struct,
      key.accessibility: source.lang.swift.accessibility.internal,
      key.name: "gg",
      key.offset: 0,
      key.length: 23,
      key.nameoffset: 7,
      key.namelength: 2,
      key.bodyoffset: 22,
      key.bodylength: 0,
      key.substructure: [
        {
          key.kind: source.lang.swift.decl.generic_type_param,
          key.name: "G",
          key.offset: 10,
          key.length: 10,
          key.nameoffset: 10,
          key.namelength: 1,
          key.inheritedtypes: [
            {
              key.name: "T<Int>>"
            }
          ],
...

You could open it on https://bugs.swift.org ?

johnfairh avatar Jun 28 '20 19:06 johnfairh

Thanks for the tip, I haven't had the chance to file a Swift bug issue before, so the link was very helpful.

The issue in question is here: https://bugs.swift.org/browse/SR-13115

MatyasKriz avatar Jun 30 '20 06:06 MatyasKriz