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

Add generics to types #660

Closed
wants to merge 19 commits into from
Closed

Add generics to types #660

wants to merge 19 commits into from

Conversation

truizlop
Copy link

@truizlop truizlop commented Aug 8, 2018

As stated in issue #551, current type declaration only provides a boolean flag stating whether a type has a generic declaration or not. This PR parses the list of generic types associated to a type declaration as an Array<String>. They can be accessed during generation through the property type.genericTypes. Default value (when type is not generic) is an empty array.

@SourceryBot
Copy link

1 Error
🚫 Any changes to library code need a summary in the Changelog.

Generated by 🚫 Danger

@krzysztofzablocki
Copy link
Owner

@truizlop looks good, could you add changelog info?

@ilyapuchka
Copy link
Collaborator

@truizlop @krzysztofzablocki I don't feel like this is an extensible solution though. How are we going to extend it to support generic constraints, i.e.? I think we should provide full information about generic parameters like we do for variable/parameters types. And I feel it will make sence to implement it so that we can parse generic parameters of both type and methods declarations, because the syntax is the same.

@marcelofabri
Copy link
Contributor

BTW SourceKit in Swift 4.1 (or 4.2 not so sure) provides some of that information

@ilyapuchka
Copy link
Collaborator

@marcelofabri It's probably only for 4.2, I quickly checked on Swift 4.2 with SourceKitten 0.20 and there is nothing helpful

@marcelofabri
Copy link
Contributor

Yeah, just double checked and it's for 4.2:

struct Foo<T: Equatable> {}
{
  "key.diagnostic_stage" : "source.diagnostic.stage.swift.parse",
  "key.length" : 27,
  "key.offset" : 0,
  "key.substructure" : [
    {
      "key.accessibility" : "source.lang.swift.accessibility.internal",
      "key.bodylength" : 0,
      "key.bodyoffset" : 26,
      "key.kind" : "source.lang.swift.decl.struct",
      "key.length" : 27,
      "key.name" : "Foo",
      "key.namelength" : 3,
      "key.nameoffset" : 7,
      "key.offset" : 0,
      "key.substructure" : [
        {
          "key.elements" : [
            {
              "key.kind" : "source.lang.swift.structure.elem.typeref",
              "key.length" : 9,
              "key.offset" : 14
            }
          ],
          "key.inheritedtypes" : [
            {
              "key.name" : "Equatable"
            }
          ],
          "key.kind" : "source.lang.swift.decl.generic_type_param",
          "key.length" : 12,
          "key.name" : "T",
          "key.namelength" : 1,
          "key.nameoffset" : 11,
          "key.offset" : 11
        }
      ]
    }
  ]
}

It doesn't provide any information about constraints added using where though.

@krzysztofzablocki
Copy link
Owner

@ilyapuchka fair point, @truizlop would you be able to add support for that?

@truizlop
Copy link
Author

truizlop commented Aug 9, 2018

Yes, I can take care of that. Is there any class that you think I can reuse for this task, or should I create a new one? I am not 100% familiar with the project, so perhaps you can advise in this regard. Also, should we include where constraints as well? How should constraints be represented? They may be protocols or superclasses, but we may not have enough information to infer what they are.

@@ -0,0 +1,25 @@
import Foundation

@objcMembers public final class Generic: NSObject, SourceryModel {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 Undocumented symbol.

@truizlop
Copy link
Author

truizlop commented Aug 9, 2018

@ilyapuchka @krzysztofzablocki @marcelofabri please take a look at the new changes I introduced. These changes are able to parse constraints on generics on types and methods, and also in where clauses. Before going any further, I'd like your opininon on it.

There is still one case that I am not sure how to handle. When there is a constraint on a protocol with an associated type, how should we model it?

var openingAngles = 1
while openingAngles > 0 && end != source.endIndex {
end = source.index(after: end)
if source[end] == ">" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it really possible to have nested generic blocks?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if they appear on the constraints. Something like: class Foo<A, T : Bar<A>> is syntactically correct.

declaration = String(declaration[declaration.startIndex ..< whereStart])
}

if let start = source.index(of: "<") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems to work on class MyClass: SuperClass<Generic> when it shouldn't, so we should check for inheritance part and drop it first.

@@ -24,7 +24,8 @@ import Foundation
typealiases: [Typealias] = [],
attributes: [String: Attribute] = [:],
annotations: [String: NSObject] = [:],
isGeneric: Bool = false) {
isGeneric: Bool = false,
genericTypes: [Generic] = []) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think genericTypeParameters is a name that is more aligned with Swift terminology, same applies to type of elements, GenericTypeParameter


it("extracts generics correctly") {
let result = parse("""
class Foo<A : Equatable & Codable, B : Baz<A>,C> : Bar<B> where C : CustomStringConvertible, B : Comparable & Decodable, C : CustomDebugStringConvertible {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be interesting to add some line breaks in where clause to make sure it's parsed correctly from multiple lines


@objcMembers public final class Generic: NSObject, SourceryModel {
public let name: String
public var constraints: [TypeName]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should also infer type of the constraint, when it is known, so this should be probably

class GenericTypeParameterConstraint {
  let typeName: TypeName
  let type: Type?
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how you are dealing with this in the rest of the project. Can you provide some hints on how to infer the type?


fileprivate func parseGenerics(declaration: String, whereClause: String) -> [Generic] {
func extractGenerics(source: String) -> [Generic] {
return source.components(separatedBy: ",")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should use commaSeparated method, it will ensure that separators are processed correctly, otherwise things like Foo<A: [(String, String)]> will be parsed incorrectly

if let generic = filtered.first {
return generic
}
fatalError("Undeclared generic found: \(name)")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be Log.error() instead

var genericTypes: [Generic] = []
if let genericSource = extract(.name, from: source),
let parameterStartIndex = genericSource.index(of: "("),
let whereClauseSource = extract(.nameSuffixUpToBody, from: source) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might not work with protocols where methods don't have body. we should probably extract it along with extracting return type (above), which handles this case


/// Represents an inheritance/implementation constraint in a generic type parameter
@objcMembers public final class GenericTypeParameterConstraint: NSObject, SourceryModel {
public let name: TypeName

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 Undocumented symbol.

/// Represents an inheritance/implementation constraint in a generic type parameter
@objcMembers public final class GenericTypeParameterConstraint: NSObject, SourceryModel {
public let name: TypeName
public let type: Type?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 Undocumented symbol.

@truizlop
Copy link
Author

@ilyapuchka I have addressed all the comments in your review, except for the type inference, which right now I am not sure how to do. Any guidance would be appreciated.

@truizlop
Copy link
Author

truizlop commented Sep 5, 2018

@ilyapuchka @krzysztofzablocki could anyone take a look at the most recent changes?

@ilyapuchka
Copy link
Collaborator

ilyapuchka commented Sep 7, 2018

@truizlop I'll try to find time to look at that this weekend. For inference look at Composer.swift, it already has all the methods you need to use to resolve type references. You'll probably need to add new one, similar to one of resolve...Types

@DenTelezhkin
Copy link
Contributor

Hello guys!

Hitting the same wall, writing templates that depend on the type of generic argument of type is currently what I want with my code generation, but cannot do because generic arguments are not exposed, only the fact that they are generic.

Is there something I can do to help move this PR forward?

@truizlop
Copy link
Author

truizlop commented Nov 7, 2018

I'd love to move it forward as well, as this has been sitting here for a very long time. I was waiting for a review, and I know that we need to add support for the inference of types, which was not added yet. Perhaps you can take a look at that?

it("doesn't extract generics if they are concrete parameters to the supertype") {
let result = parse("class Foo : Bar<Int> {}")
let expected = Class(name: "Foo", isGeneric: false)
expected.inheritedTypes = ["Bar<Int>"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally the generic type information should be still available in this case. In the end if Boo is defined as Boo<Value>, then Foo.Value can be used in the Foo implementation. And it should have a concrete type information too.

}
}
""")
let expected = Class(name: "Foo", isGeneric: true, genericTypeParameters: [GenericTypeParameter(typeName: TypeName("A"), constraints: [GenericTypeParameterConstraint(name: TypeName("Equatable")), GenericTypeParameterConstraint(name: TypeName("Codable"))]), GenericTypeParameter(typeName: TypeName("B"), constraints: [GenericTypeParameterConstraint(name: TypeName("Baz<A>")), GenericTypeParameterConstraint(name: TypeName("Comparable")), GenericTypeParameterConstraint(name: TypeName("Decodable"))]), GenericTypeParameter(typeName: TypeName("C"), constraints: [GenericTypeParameterConstraint(name: TypeName("CustomStringConvertible")), GenericTypeParameterConstraint(name: TypeName("CustomDebugStringConvertible"))])])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some code formatting is probably needed here to make it more readable

@DenTelezhkin
Copy link
Contributor

Ok, guys, so I've managed to make some progress on this task here in my fork - https://github.com/DenHeadless/Sourcery/tree/trl/add-generics-to-types

Stuff that was made:

  1. Rebase on master to get project running with Xcode 10
  2. Improve some text formatting in unit tests(Specifically FileParserSpec.swift)
  3. Implement getting inheritedTypes property as [Type] instead of [String], so that generic information is retrievable(at least partially)

Unresolved issues

  1. I was not able to regenerate AutoJSExport files for some reason - I always get this error - Problem with JSExport.ejs #438
  2. Some unit tests fail(14 of them), 13 related to templates.
  3. One test that is not related to templates, but I currently have hard time understanding why it's failing - https://github.com/DenHeadless/Sourcery/blob/trl/add-generics-to-types/SourceryTests/Parsing/FileParserSpec.swift#L632-L637
  4. I'm very new to this project and could and probably did use codebase inefficiently, so this would require review and probably some changes.

@ilyapuchka @truizlop Hope for your help in this one, should I open another PR that subsumes this one, or lets somehow pull my changes into this PR?

@truizlop
Copy link
Author

truizlop commented Nov 8, 2018

Given the discussion is already here, it would be nice if you can merge your branch with mine and we'll continue here. I can try to take care of the broken tests (probably there has been some change since last time I checked it out). After that, we can decide how to move it forward.

@DenTelezhkin
Copy link
Contributor

@truizlop makes sense. I think the simplest way would be for you to checkout my branch and force push changes from it into your branch, replacing current commits with rebased and new ones.

Sent with GitHawk

@DenTelezhkin
Copy link
Contributor

Small update: I was able to resolve almost all issues I mentioned in previous post. I am continuing to add tests and improving generic parsing system to be recursive, and handle all cases from simple to more complex, for example A<B<C: D>. Things are looking good, but need more time to finish with all this stuff, as current workload on my day-job is pretty high at the moment.

Hope to get more updates soon.

@DenTelezhkin
Copy link
Contributor

Hello guys, sorry it took so long, generic handling turned out to be much harder task than initially anticipated. I also diverged from this PR and current code base quite a bit, so I opened a new PR to clearly view and discuss all changes - #707.

Would love to hear your thoughts and feedback about it @truizlop @ilyapuchka.

@truizlop
Copy link
Author

truizlop commented Dec 7, 2018

Thanks @DenHeadless, I'll take a look at your PR.

@truizlop truizlop closed this Apr 8, 2020
@truizlop truizlop deleted the trl/add-generics-to-types branch April 8, 2020 07:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants