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

feat(smithy-swift): Big Number Support #64

Merged
merged 17 commits into from
Nov 12, 2020

Conversation

sadiredd-sv
Copy link

@sadiredd-sv sadiredd-sv commented Oct 30, 2020

Issue #:
174932864 (from Pivotal)
Big Number Support

Description of changes:
Changing the big number library from cocoapod package to Apple numerics - https://github.com/apple/swift-numerics. This is needed because Swift SDK will be distributed for both mobile and server

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@@ -136,12 +136,12 @@ class SymbolVisitor(private val model: Model, private val rootNamespace: String

override fun shortShape(shape: ShortShape): Symbol = numberShape(shape, "Int16", "0")

override fun bigIntegerShape(shape: BigIntegerShape): Symbol = createBigSymbol(shape, "BInt")
override fun bigIntegerShape(shape: BigIntegerShape): Symbol = createBigSymbol(shape, "Int64")
Copy link
Author

@sadiredd-sv sadiredd-sv Nov 5, 2020

Choose a reason for hiding this comment

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

BigInteger in smithy is "Arbitrarily large signed integer" as per https://awslabs.github.io/smithy/spec/core.html. The corresponding data type in Apple's swift-numerics (BigInt) isn't implemented in https://github.com/apple/swift-numerics.

Here are the proofs for its implementation in progress:

  1. Arbitrary-precision bigints apple/swift-numerics#5
  2. [Sketch]Arbitrary-precision BigInt apple/swift-numerics#84
  3. Mentioned in this video at 11:16 in https://developer.apple.com/videos/play/wwdc2020/10217/

Screen Shot 2020-11-05 at 12 50 19 AM

Alternative options:

  1. Use this prototype of BigInt in the official Swift repository:
    https://github.com/apple/swift/blob/master/test/Prototypes/BigInt.swift

  2. Use this module which has BigInt, Complex double and Complex float:
    https://github.com/dankogai/swift-pons

  3. Use "Array(UInt64)" data structure to represent the BigInt using:
    https://github.com/attaswift/BigInt

  4. Write our own BigInt module in aws-crt-swift using the SignedInteger protocol

  5. Since the smithy spec says that bigInteger is a Json number https://awslabs.github.io/smithy/1.0/spec/aws/aws-restjson1-protocol.html?highlight=biginteger ,

Screen Shot 2020-11-05 at 11 06 51 AM

can we use Complex for BigInteger, because a Json number follows JavaScript’s 64 bit floating-point format (source: https://restfulapi.net/json-data-types/#:~:text=JSON%20numbers%20follow%20JavaScript's%20double,digits%20between%200%20and%209. )

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm this is an interesting predicament. Okay so the best option of the ones you mentioned is one we can maintain indefinitely where we don't take a third party dependency so that's number 4, number 1 is something I previously found and is also a possibility. We want to leave room basically for apple to release something and then somehow change it to be what's in the stdlib. so we need to figure out the best way to do this. Writing our own wrapper might be the way cuz when it comes to the stdlib if it doesnt have th same name we can just keep it wrapped in our type. What do you think?

Copy link
Author

Choose a reason for hiding this comment

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

Per our discussion, we decided to wait until Apple finishes the BigInteger implementation with ComplexModule where Integer would conform to Real and their timeline would fall within our delivery of swift-sdk. So, I'm using array of bytes [UInt8] for now.

@@ -274,12 +274,12 @@ class ShapeValueGenerator(

ShapeType.BIG_INTEGER -> {
writer.addImport(SwiftDependency.BIG.namespace)
writer.writeInline("BInt(\$L)", node.value)
writer.writeInline("(Int64)(String(\$L))", node.value)
}
Copy link
Author

@sadiredd-sv sadiredd-sv Nov 5, 2020

Choose a reason for hiding this comment

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

Only Double and Float conform to the Real protocol in Complex , which is available in the ComplexModule of https://github.com/apple/swift-numerics. Integer doesn't conform to this Real protocol. Hence, we can't have something like "Complex" here.

Since the smithy's big integer is a large signed integer, Int64 of Swift wont be able to handle the integer values greater than INT64_MAX and less than INT64_MIN. We cant use Complex double or float here because they don't store the "sign" of the number. So, I thought of using String, but that would imply not utilizing Apple's swift-numerics.

Apart from the above 2 reasons, the other reason why I opted to use String(node.value) is https://awslabs.github.io/smithy/1.0/spec/core/model.html?highlight=biginteger:
Screen Shot 2020-11-05 at 10 59 55 AM
Question: Is this wrong ? If not, should we not cast it to Int64 at all and just use String ?

Copy link
Contributor

Choose a reason for hiding this comment

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

so they can be serialized as strings but can they also be deserialized as strings? that statement is a bit confusing. we need to ask the smithy folks. Seems like we still need to figure out a solution for int. How difficult would it be to conform Int to Real? Time to look into that option. We can also ask Apple if they have suggestions or guidance.

@sadiredd-sv sadiredd-sv marked this pull request as ready for review November 5, 2020 19:12
@sadiredd-sv sadiredd-sv requested a review from kneekey23 November 5, 2020 19:12
@@ -61,7 +61,9 @@ fun writePackageManifest(settings: SwiftSettings, fileManifest: FileManifest, de
writer.openBlock(".target(", "),") {
writer.write("name: \"${settings.moduleName}\",")
writer.openBlock("dependencies: [", "],") {
writer.write(distinctDependencies.map { "\"${it.packageName}\"" }.joinToString(separator = ", "))
var str = distinctDependencies.map { "\"${it.packageName}\"" }.joinToString(separator = ", ")
str = str.replace("\"ComplexModule\"", ".product(name: \"ComplexModule\", package: \"swift-numerics\")")
Copy link
Contributor

Choose a reason for hiding this comment

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

this shouldn't be hard coded. you should be able to pull this information from the distinctDependencies array and if you cant we need to add properties to SwiftDependency

Copy link
Author

Choose a reason for hiding this comment

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

Addressed. Please check my other comment


val statements = decls.toString()
val expected = "import BigNumber\nimport Foundation"
val expected = "import Foundation\nimport Numerics"
Copy link
Contributor

@kneekey23 kneekey23 Nov 6, 2020

Choose a reason for hiding this comment

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

think this should be import ComplexModule not Numerics

Copy link
Author

Choose a reason for hiding this comment

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

Ah! my bad. Let me change this.

Copy link
Author

Choose a reason for hiding this comment

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

Corrected

Copy link
Contributor

@kneekey23 kneekey23 left a comment

Choose a reason for hiding this comment

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

Seems like we have more research to do here. really nice start on this. We might be leaving this open for a while while we figure out what to do. Might require a baby design review with our sdk folks to see what they think. Usually defaulting to the CRT types is the answer....

ShapeType.BIG_INTEGER -> {
writer.addImport(SwiftDependency.BIG.namespace)
writer.writeInline("BInt(\$L)", node.value)
writer.writeInline("Array(String(\$L).utf8)", node.value)
Copy link
Author

Choose a reason for hiding this comment

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

This converts a big integer to array of bytes [UInt8] in Swift.

Copy link
Contributor

Choose a reason for hiding this comment

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

looks good to me

for(dependency in distinctDependencies) {
if(dependency.packageName.equals(SwiftDependency.BIG.namespace))
str = str.replace("\""+dependency.packageName+"\"", dependency.dependencyType)
}
Copy link
Author

@sadiredd-sv sadiredd-sv Nov 12, 2020

Choose a reason for hiding this comment

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

@kneekey23 , I removed the hardcoding and now retrieving the dependencies needed in targets of Package.swift from "distinctDependencies" array. I loaded the "type" field in SwiftDependency.kt because:
- Namespace's value "ComplexModule" is being used as import in ShapeValueGenerator.kt
- Dependency needed in the targets of Package.swift is different ( .product(name: "ComplexModule", package: "swift-numerics") ) from namespace

Alternative ideas:

  1. Instead of looping over the distinctDependencies array, we can just do this:
    str.replace("""+SwiftDependency.BIG.namespace+""", SwiftDependency.BIG.dependencies[0].dependencyType)

  2. Instead of loading the "type", we can load namespace for BIG in SwiftDependency.kt with ".product(name: "ComplexModule", package: "swift-numerics") " and extract the "ComplexModule" substring for writer.addImport in ShapeValueGenerator.kt

Copy link
Contributor

Choose a reason for hiding this comment

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

I think im confused how is it different from namespace? the target and the namespace are the same....ComplexModule right? did I miss something, I would prefer case 2 here if I missed something but I am confused

Copy link
Author

@sadiredd-sv sadiredd-sv Nov 12, 2020

Choose a reason for hiding this comment

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

@kneekey23 ,
Since distinctDependencies is an array of SymbolDependency objects and SymbolDependency.java doesn't have both package and namespace, I added the package name(example: swift-numerics) as property (just like the url). So, the mapping is:

  1. namespace in SwiftDependency.kt -> packageName in Smithy's SymbolDependency.java
  2. packageName in SwiftDependency.kt -> packageName property in Smithy's SymbolDependency.java

Is this fine?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah got it. So you're saying you would need to cast it to have those props from SwiftDependency right? I say just cast it and if you can't this is totally fine.

@@ -19,7 +19,7 @@ import software.amazon.smithy.codegen.core.SymbolDependency
import software.amazon.smithy.codegen.core.SymbolDependencyContainer

enum class SwiftDependency(val type: String, val namespace: String, val version: String, val url: String) : SymbolDependencyContainer {
BIG("", "BigNumber", "2.0", url = "https://github.com/mkrd/Swift-Big-Integer.git"),
BIG(".product(name: \"ComplexModule\", package: \"swift-numerics\")", "ComplexModule", "0.0.5", url = "https://github.com/apple/swift-numerics"),
Copy link
Contributor

Choose a reason for hiding this comment

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

ohh I think I see what you did. let's discuss this really quick and we can fix this so we can merge this in

/*
TODO:: When https://github.com/apple/swift-numerics supports Integer conforming to Real protocol, we need to
change [UInt8] to Complex<Integer>. Apple's work is being tracked in apple/swift-numerics#5
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

love this. we need to add another pivotal ticket for this as well and put in the icebox for now :)

Copy link
Author

Choose a reason for hiding this comment

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

I added a story in icebox #175706745

Copy link
Contributor

@kneekey23 kneekey23 left a comment

Choose a reason for hiding this comment

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

okay so everything looks good except the package manifest generator. let's discuss really fast to clean it up a bit and then we can merge this in.

Copy link
Contributor

@kneekey23 kneekey23 left a comment

Choose a reason for hiding this comment

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

Small changes needed. Then merge this in!

for (dependency in distinctDependencies) {
writer.openBlock(".product(", "),") {
writer.write("name: \"${dependency.packageName}\",")
writer.write("package: \"${dependency.expectProperty("packageName", String::class.java)}\"")
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be the namespace property not the package name

writer.openBlock(".product(", "),") {
writer.write("name: \"${dependency.packageName}\",")
writer.write("package: \"${dependency.expectProperty("packageName", String::class.java)}\"")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why expect property here? And not just dependency.packageName

writer.write("name: \"${dependency.packageName}\",")
writer.write("package: \"${dependency.expectProperty("packageName", String::class.java)}\"")
}
}
}
writer.write("path: \"./${settings.moduleName}\"")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove commented out lines. No longer needed :)

Copy link
Author

Choose a reason for hiding this comment

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

I removed just before you commented :)

" .product(\n" +
" name: \"ClientRuntime\",\n" +
" package: \"ClientRuntime\"\n" +
" ),\n" +
" ],\n" +
Copy link
Author

@sadiredd-sv sadiredd-sv Nov 12, 2020

Choose a reason for hiding this comment

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

This is what is generated at the end (just the dependencies snippet inside targets):
Screen Shot 2020-11-12 at 11 54 05 AM

Does this look good?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep this looks perfect 👌🏻

@sadiredd-sv sadiredd-sv merged commit d450a9c into swift-client-runtime Nov 12, 2020
@kneekey23 kneekey23 deleted the big-number-support branch November 16, 2020 04:04
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.

2 participants