Repository: apollographql/apollo-codegen
Pull request #336 (2017-12-20 17:09:37)

Alternate implementation of propertyAssignmentForField to avoid “Ambiguous use of ‘map’” error
This is an alternate output for propertyAssignmentForField that avoids the "Ambiguous use of 'map'" error described here: https://github.com/apollographql/apollo-ios/issues/178

meteor-bot: @plarson: Thank you for submitting a pull request! Before we can merge it, you'll need to sign the Meteor Contributor Agreement here: https://contribute.meteor.com/
horsejockey: @plarson Thanks for building this fix. I'm just running into it now.  Any word on when this might get merged?

martijnwalraven: @plarson Sorry, I missed this because of the holiday break. Great work! Before I can merge it, I'd need you to sign the CLA and we'd want the tests to reflect the changes so they pass.
martijnwalraven: @plarson I'd love to merge this, but before I can do that I really need you to sign the CLA.
matteinn: @plarson any news here? It would be awesome to have this merged :)
annjose: Our team also faces the same code generation issue with multiple variables in mutation. Hence this PR is very important for us also. @plarson Thanks you for fixing this. Please get this merged as soon as possible. cc @martijnwalraven
martijnwalraven: I'd really like to merge this, but I can't merge code without the author signing the CLA @plarson
martijnwalraven: @frenchdonuts Yes, that would be great!
RabeaWahab: It would be great if that can be merged 👍 
annjose: Yes @frenchdonuts - please merge. It would be awesome 👍 
frenchdonuts: Hi all, I don't have time to go through the fixes right now. However, I found a temporary fix-around. I don't know why this works (for me), but it does.
1) Click "Discard all changes" under the "Source Control" menu in XCode.
2) Run pod install.
3) Build and run

Patch:
diff --git a/src/swift/codeGeneration.ts b/src/swift/codeGeneration.ts
index be0165e7..86c76ef1 100644
--- a/src/swift/codeGeneration.ts
+++ b/src/swift/codeGeneration.ts
@@ -490,13 +490,14 @@ export class SwiftAPIGenerator extends SwiftGenerator<CompilerContext> {
     }
   }
 
-  propertyAssignmentForField(field: { responseKey: string; propertyName: string; type: GraphQLType }) {
-    const { responseKey, propertyName, type } = field;
+  propertyAssignmentForField(field: Field & Property & Struct) {
+    const { responseKey, propertyName, type, structName } = field;
     const valueExpression = isCompositeType(getNamedType(type))
       ? this.helpers.mapExpressionForType(
           type,
           identifier => `${identifier}.snapshot`,
-          escapeIdentifierIfNeeded(propertyName)
+          escapeIdentifierIfNeeded(propertyName),
+          structName
         )
       : escapeIdentifierIfNeeded(propertyName);
     return `"${responseKey}": ${valueExpression}`;
@@ -521,15 +522,17 @@ export class SwiftAPIGenerator extends SwiftGenerator<CompilerContext> {
           this.printOnNewline('get');
           this.withinBlock(() => {
             const snapshotTypeName = this.helpers.typeNameFromGraphQLType(type, 'Snapshot', false);
-            let getter;
+            let identifier;
             if (isOptional) {
-              getter = `return (snapshot["${responseKey}"] as? ${snapshotTypeName})`;
+              identifier = `(snapshot["${responseKey}"] as? ${snapshotTypeName})`;
             } else {
-              getter = `return (snapshot["${responseKey}"] as! ${snapshotTypeName})`;
+              identifier = `(snapshot["${responseKey}"] as! ${snapshotTypeName})`;
             }
-            getter += this.helpers.mapExpressionForType(
+            let getter = 'return ' + this.helpers.mapExpressionForType(
               type,
-              identifier => `${structName}(snapshot: ${identifier})`
+              identifier => `${structName}(snapshot: ${identifier})`,
+							identifier,
+							'Snapshot'
             );
             this.printOnNewline(getter);
           });
@@ -538,7 +541,8 @@ export class SwiftAPIGenerator extends SwiftGenerator<CompilerContext> {
             let newValueExpression = this.helpers.mapExpressionForType(
               type,
               identifier => `${identifier}.snapshot`,
-              'newValue'
+              'newValue',
+              structName
             );
             this.printOnNewline(`snapshot.updateValue(${newValueExpression}, forKey: "${responseKey}")`);
           });
diff --git a/src/swift/helpers.ts b/src/swift/helpers.ts
index af4295cd..fb678098 100644
--- a/src/swift/helpers.ts
+++ b/src/swift/helpers.ts
@@ -38,7 +38,7 @@ export class Helpers {
 
   // Types
 
-  typeNameFromGraphQLType(type: GraphQLType, unmodifiedTypeName?: string, isOptional?: boolean): string {
+  typeNameFromGraphQLType(type: GraphQLType, unmodifiedTypeName?: string, isOptional?: boolean, isSingular: boolean = false): string {
     if (type instanceof GraphQLNonNull) {
       return this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName, false);
     } else if (isOptional === undefined) {
@@ -47,7 +47,11 @@ export class Helpers {
 
     let typeName;
     if (type instanceof GraphQLList) {
-      typeName = '[' + this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName) + ']';
+      if (isSingular) {
+      	typeName = this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName);
+      } else {
+      	typeName = '[' + this.typeNameFromGraphQLType(type.ofType, unmodifiedTypeName) + ']';
+      }
     } else if (type instanceof GraphQLScalarType) {
       typeName = this.typeNameForScalarType(type);
     } else {
@@ -175,7 +179,7 @@ export class Helpers {
   propertiesForSelectionSet(
     selectionSet: SelectionSet,
     namespace?: string
-  ): (Field & Property)[] | undefined {
+  ): (Field & Property & Struct)[] | undefined {
     const properties = collectAndMergeFields(selectionSet, true)
       .filter(field => field.name !== '__typename')
       .map(field => this.propertyFromField(field, namespace));
@@ -231,7 +235,8 @@ export class Helpers {
   mapExpressionForType(
     type: GraphQLType,
     expression: (identifier: string) => string,
-    identifier = ''
+    identifier: string = '',
+    subtypeName: string
   ): string {
     let isOptional;
     if (type instanceof GraphQLNonNull) {
@@ -240,19 +245,25 @@ export class Helpers {
     } else {
       isOptional = true;
     }
-
     if (type instanceof GraphQLList) {
       if (isOptional) {
-        return `${identifier}.flatMap { $0.map { ${this.mapExpressionForType(
+        const prologue = subtypeName === undefined ? '' : `(i: ${subtypeName}) in `
+        const subidentifier = subtypeName === undefined ? '$0' : 'i'
+        return `${identifier}?.map({ ${prologue}${this.mapExpressionForType(
           type.ofType,
           expression,
-          '$0'
-        )} } }`;
+          subidentifier,
+          this.typeNameFromGraphQLType(type.ofType, undefined, false, true)
+        )} })`;
       } else {
-        return `${identifier}.map { ${this.mapExpressionForType(type.ofType, expression, '$0')} }`;
+        const prologue = subtypeName === undefined ? '' : `(j: ${subtypeName}) in `
+        const subidentifier = subtypeName === undefined ? '$0' : 'j'
+        return `${identifier}.map { ${prologue}${this.mapExpressionForType(type.ofType, expression, subidentifier, this.typeNameFromGraphQLType(type, undefined, false, true))} }`;
       }
     } else if (isOptional) {
-      return `${identifier}.flatMap { ${expression('$0')} }`;
+      const prologue = subtypeName === undefined ? '' : `(j: ${subtypeName}) in `
+      const subidentifier = subtypeName === undefined ? '$0' : 'j'
+      return `${identifier}.flatMap { ${prologue}${expression(subidentifier)} }`;
     } else {
       return expression(identifier);
     }


Test patch:
diff --git a/test/swift/__snapshots__/codeGeneration.ts.snap b/test/swift/__snapshots__/codeGeneration.ts.snap
index b130a5ef..b98f63f7 100644
--- a/test/swift/__snapshots__/codeGeneration.ts.snap
+++ b/test/swift/__snapshots__/codeGeneration.ts.snap
@@ -29,7 +29,7 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a
     }
 
     public init(createReview: CreateReview? = nil) {
-      self.init(snapshot: [\\"__typename\\": \\"Mutation\\", \\"createReview\\": createReview.flatMap { $0.snapshot }])
+      self.init(snapshot: [\\"__typename\\": \\"Mutation\\", \\"createReview\\": createReview.flatMap { (j: CreateReview) in j.snapshot }])
     }
 
     public var createReview: CreateReview? {
@@ -107,7 +107,7 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a
     }
 
     public init(hero: Hero? = nil) {
-      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { $0.snapshot }])
+      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { (j: Hero) in j.snapshot }])
     }
 
     public var hero: Hero? {
@@ -233,7 +233,7 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a
     }
 
     public init(hero: Hero? = nil) {
-      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { $0.snapshot }])
+      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { (j: Hero) in j.snapshot }])
     }
 
     public var hero: Hero? {
@@ -383,7 +383,7 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a
     }
 
     public init(hero: Hero? = nil) {
-      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { $0.snapshot }])
+      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { (j: Hero) in j.snapshot }])
     }
 
     public var hero: Hero? {
@@ -481,7 +481,7 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a
     }
 
     public init(hero: Hero? = nil) {
-      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { $0.snapshot }])
+      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { (j: Hero) in j.snapshot }])
     }
 
     public var hero: Hero? {
@@ -554,7 +554,7 @@ exports[`Swift code generation #classDeclarationForOperation() should generate a
     }
 
     public init(hero: Hero? = nil) {
-      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { $0.snapshot }])
+      self.init(snapshot: [\\"__typename\\": \\"Query\\", \\"hero\\": hero.flatMap { (j: Hero) in j.snapshot }])
     }
 
     public var hero: Hero? {
@@ -775,11 +775,11 @@ exports[`Swift code generation #structDeclarationForFragment() should generate a
   }
 
   public static func makeHuman(name: String, friends: [Friend?]? = nil) -> HeroDetails {
-    return HeroDetails(snapshot: [\\"__typename\\": \\"Human\\", \\"name\\": name, \\"friends\\": friends.flatMap { $0.map { $0.flatMap { $0.snapshot } } }])
+    return HeroDetails(snapshot: [\\"__typename\\": \\"Human\\", \\"name\\": name, \\"friends\\": friends?.map({ (i: Friend) in i.flatMap { (j: Character) in j.snapshot } })])
   }
 
   public static func makeDroid(name: String, friends: [Friend?]? = nil) -> HeroDetails {
-    return HeroDetails(snapshot: [\\"__typename\\": \\"Droid\\", \\"name\\": name, \\"friends\\": friends.flatMap { $0.map { $0.flatMap { $0.snapshot } } }])
+    return HeroDetails(snapshot: [\\"__typename\\": \\"Droid\\", \\"name\\": name, \\"friends\\": friends?.map({ (i: Friend) in i.flatMap { (j: Character) in j.snapshot } })])
   }
 
   /// The name of the character
@@ -795,10 +795,10 @@ exports[`Swift code generation #structDeclarationForFragment() should generate a
   /// The friends of the character, or an empty list if they have none
   public var friends: [Friend?]? {
     get {
-      return (snapshot[\\"friends\\"] as? [Snapshot?]).flatMap { $0.map { $0.flatMap { Friend(snapshot: $0) } } }
+      return (snapshot[\\"friends\\"] as? [Snapshot?])?.map({ (i: Snapshot) in i.flatMap { (j: Character) in Friend(snapshot: j) } })
     }
     set {
-      snapshot.updateValue(newValue.flatMap { $0.map { $0.flatMap { $0.snapshot } } }, forKey: \\"friends\\")
+      snapshot.updateValue(newValue?.map({ (i: Friend) in i.flatMap { (j: Character) in j.snapshot } }), forKey: \\"friends\\")
     }
   }
 
@@ -933,20 +933,20 @@ exports[`Swift code generation #structDeclarationForSelectionSet() should genera
   }
 
   public static func makeHuman(friends: [Friend?]? = nil) -> Hero {
-    return Hero(snapshot: [\\"__typename\\": \\"Human\\", \\"friends\\": friends.flatMap { $0.map { $0.flatMap { $0.snapshot } } }])
+    return Hero(snapshot: [\\"__typename\\": \\"Human\\", \\"friends\\": friends?.map({ (i: Friend) in i.flatMap { (j: Character) in j.snapshot } })])
   }
 
   public static func makeDroid(friends: [Friend?]? = nil) -> Hero {
-    return Hero(snapshot: [\\"__typename\\": \\"Droid\\", \\"friends\\": friends.flatMap { $0.map { $0.flatMap { $0.snapshot } } }])
+    return Hero(snapshot: [\\"__typename\\": \\"Droid\\", \\"friends\\": friends?.map({ (i: Friend) in i.flatMap { (j: Character) in j.snapshot } })])
   }
 
   /// The friends of the character, or an empty list if they have none
   public var friends: [Friend?]? {
     get {
-      return (snapshot[\\"friends\\"] as? [Snapshot?]).flatMap { $0.map { $0.flatMap { Friend(snapshot: $0) } } }
+      return (snapshot[\\"friends\\"] as? [Snapshot?])?.map({ (i: Snapshot) in i.flatMap { (j: Character) in Friend(snapshot: j) } })
     }
     set {
-      snapshot.updateValue(newValue.flatMap { $0.map { $0.flatMap { $0.snapshot } } }, forKey: \\"friends\\")
+      snapshot.updateValue(newValue?.map({ (i: Friend) in i.flatMap { (j: Character) in j.snapshot } }), forKey: \\"friends\\")
     }
   }