Skip to content

[pigeon] removes safe casting from nullables in kotlin and swift #3284

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

Merged
merged 7 commits into from
Feb 28, 2023

Conversation

tarrinneal
Copy link
Contributor

@tarrinneal tarrinneal commented Feb 23, 2023

Removes safe casting during decode process for kotlin and swift to avoid hiding potential data type inconsistencies across channels.

fixes flutter/flutter#116999

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the relevant style guides and ran the auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages repo does use dart format.)
  • I signed the CLA.
  • The title of the PR starts with the name of the package surrounded by square brackets, e.g. [shared_preferences]
  • I listed at least one issue that this PR fixes in the description above.
  • I updated pubspec.yaml with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.
  • I updated CHANGELOG.md to add a description of the change, following repository CHANGELOG style.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

var aNullableEnum: AnEnum? = nil
if let aNullableEnumRawValue = list[12] as? Int {
if let aNullableEnumRawValue = list[12] as! Int? {
aNullableEnum = AnEnum(rawValue: aNullableEnumRawValue)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

you can avoid the var by either

let aNullableEnum: AnEnum?
if ... {
  aNullableEnum = ...
} else {
  aNullableEnum = nil
}

or using a map:

let aNullableEnum = (list[12] as! Int?).map { AnEnum(rawValue: $0) }

@@ -37,7 +37,7 @@ class AllDatatypesTests: XCTestCase {

func testAllEquals() throws {
let everything = AllNullableTypes(
aNullableBool: false,
aNullableBool: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

were you able to resolve the Bool type issue the other day? What was the fix?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

switching from Any? to Any

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you provide more details? Not sure which Any? you are referring to.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's the same decode issue as the other comments I've replied to

let aBool = list[0] as! Bool
let anInt = list[1] as! Int32
let aDouble = list[2] as! Double
let aByteArray = list[3] as! FlutterStandardTypedData
let a4ByteArray = list[4] as! FlutterStandardTypedData
let a8ByteArray = list[5] as! FlutterStandardTypedData
let aFloatArray = list[6] as! FlutterStandardTypedData
let aList = list[7] as! [Any?]
let aList = list[7] as! [Any]
Copy link
Contributor

Choose a reason for hiding this comment

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

do we want to test out both [Any?] and [Any]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean? I'm not sure why we would need Any? for anything in this context since Any can already be nil

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh interesting - what about the maps below? Do you also want to remove the ?

e.g.

var aNullableMap: [AnyHashable: Any?]? = nil
var nullableMapWithObject: [String?: Any?]? = nil

Copy link
Contributor Author

Choose a reason for hiding this comment

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

probably a good idea

Copy link
Contributor

@hellohuanlin hellohuanlin Feb 24, 2023

Choose a reason for hiding this comment

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

I'm not sure why we would need Any? for anything in this context since Any can already be nil

Actually, Any cannot represent nil in Swift:

Screenshot 2023-02-23 at 4 13 41 PM

Hmmm, if I provide concrete type of nil then it works. I think it's fine to keep Any then. Though I am not sure how this is related to the Bool? crash you had the other day. May be helpful if you can provide a minimal reproducible code snippet so I can try out in playground.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The Bool? crash was nil attempting to be cast to Bool?, it failed for the reasons mentioned here. I'm not sure how to replicate it without a lot of extra steps.

@@ -179,7 +179,7 @@ data class TestMessage (
companion object {
@Suppress("UNCHECKED_CAST")
fun fromList(list: List<Any?>): TestMessage {
val testList = list[0] as? List<Any?>
val testList = list[0] as List<Any?>?
Copy link
Contributor

Choose a reason for hiding this comment

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

The kotlin version is still List<Any?>, but Swift changed from [Any?] to [Any]. Is it intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Swift optional Any causes issues with force casting into optional types when the data is nil. Kotlin doesn't have that problem.

Copy link
Contributor

Choose a reason for hiding this comment

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

Swift optional Any causes issues with force casting into optional types when the data is nil.

Not sure if I follow this. Can you share a short code snippet when it fails? Are you talking about this following scenario?

let list: [Any?] = [nil, 1, "Hi"]
let optionalBool = list[0] as! Bool?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is the scenario that was happening before. The only difference now is
list: [Any]

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting. This scenario actually did not crash when I try it on the playground.

Copy link
Contributor

Choose a reason for hiding this comment

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

The kotlin version is still List<Any?>, but Swift changed from [Any?] to [Any]. Is it intended?

The core answer here is that Any is fundamentally different between the two languages: Any is a root type in Swift, but not Kotlin.

This Kotlin code:

val i: Int? = null
println(i is Any)
println(i is Any?)

prints

false
true

but this Swift code:

var i: Int? = nil
print(i is Any)
print(i is Any?)

prints

true
true

We're generating different code because the expression of "any type, including nullable types" is different in the two languages.

(I wasn't aware of this difference when reviewing the initial generator code, so didn't catch the incorrect use of Any? in Swift in the first place.)

val aNullable4ByteArray = list[4] as IntArray?
val aNullable8ByteArray = list[5] as LongArray?
val aNullableFloatArray = list[6] as DoubleArray?
val aNullableList = list[7] as List<Any?>?
Copy link
Contributor

Choose a reason for hiding this comment

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

i'm curious - in Kotlin, can Any already represent a nullable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

nope

Copy link
Contributor

@hellohuanlin hellohuanlin left a comment

Choose a reason for hiding this comment

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

The PR itself LGTM. Though I am still very curious why nil would fail to be casted to Bool? since my code snippet worked. I would need a minimal reproducible example to look into, but looks like you cannot create such example easily...

@@ -179,7 +179,7 @@ data class TestMessage (
companion object {
@Suppress("UNCHECKED_CAST")
fun fromList(list: List<Any?>): TestMessage {
val testList = list[0] as? List<Any?>
val testList = list[0] as List<Any?>?
Copy link
Contributor

Choose a reason for hiding this comment

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

The kotlin version is still List<Any?>, but Swift changed from [Any?] to [Any]. Is it intended?

The core answer here is that Any is fundamentally different between the two languages: Any is a root type in Swift, but not Kotlin.

This Kotlin code:

val i: Int? = null
println(i is Any)
println(i is Any?)

prints

false
true

but this Swift code:

var i: Int? = nil
print(i is Any)
print(i is Any?)

prints

true
true

We're generating different code because the expression of "any type, including nullable types" is different in the two languages.

(I wasn't aware of this difference when reviewing the initial generator code, so didn't catch the incorrect use of Any? in Swift in the first place.)

Copy link
Contributor

@stuartmorgan-g stuartmorgan-g left a comment

Choose a reason for hiding this comment

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

LGTM!

@stuartmorgan-g stuartmorgan-g added the autosubmit Merge PR when tree becomes green via auto submit App label Feb 28, 2023
@auto-submit auto-submit bot merged commit e20c1b7 into flutter:main Feb 28, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Mar 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[pigeon] Remove most use of safe casting in Swift and Kotlin generators
4 participants