Skip to content

Commit

Permalink
Add support for @Swift(OptionSet) attribute (#1461)
Browse files Browse the repository at this point in the history
Added support for `@Swift(OptionSet)` attribute. An enum marked with this
attribute is generated as a Swift `struct` conforming to `OptionSet` protocol.
Moreover, for each such enum `Foo`, any LIME usage of `Set<Foo>` will be `Foo`
itself in Swift, per the use pattern for `OptionSet`.

Resolves: #1197
Signed-off-by: Daniel Kamkha <daniel.kamkha@here.com>

Signed-off-by: Daniel Kamkha <daniel.kamkha@here.com>
  • Loading branch information
DanielKamkha authored Aug 18, 2022
1 parent ada4902 commit 65c4c39
Show file tree
Hide file tree
Showing 19 changed files with 428 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
### Features:
* Added support for per-platform visibility attributes (e.g. `@Java(Internal)`, etc.).
* Added a `null` check in Dart for non-nullable class and interface usages.
* Added support for `@Swift(OptionSet)` attribute.
### Bug fixes:
* Fixed Java compilation issue for enum set literals.
### Removed:
Expand Down
3 changes: 3 additions & 0 deletions docs/lime_attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ generated code. _Attribute_ does not need to be prepended with `@`. _Attribute_
literals, their enclosing quotes need to be backslash-escaped, as in the example.
* **ParameterDefaults**: marks a "field constructor" of a struct to have field default values as parameter defaults
in Swift, for those fields that are listed in the "filed constructor's" signature.
* **OptionSet**: marks an enumeration to be generated in Swift as a `struct` implementing the `OptionSet` protocol.
Additionally, for each enum `MyEnum` marked as such, any usage of `Set<MyEnum>` will be replaced by `MyEnum` itself in
Swift, per the `OptionSet` usage pattern.
* **Public** or **Internal**: marks an element to have the corresponding visibility in Swift, disregarding any "global"
visibility modifiers the element might have.

Expand Down
1 change: 1 addition & 0 deletions functional-tests/functional/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ feature(Enums cpp android swift dart SOURCES

input/lime/Enums.lime
input/lime/EnumeratorAlias.lime
input/lime/EnumOptionSet.lime
input/lime/EnumsTypeCollection.lime
)

Expand Down
41 changes: 41 additions & 0 deletions functional-tests/functional/input/lime/EnumOptionSet.lime
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright (C) 2016-2022 HERE Europe B.V.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
# License-Filename: LICENSE

package test

@Swift(OptionSet)
enum EnumOptionSet {
ONE = 1,
TWO = 2,
THREE = 4
}

@Swift(OptionSet)
enum EnumOptionSetComments {
ONE = 1,
@Deprecated
TWO = 2,
// Foo bar
THREE = 4
}

struct UseEnumOptionSet {
setField: Set<EnumOptionSet>
setFieldEmpty: Set<EnumOptionSet> = []
setFieldValue: Set<EnumOptionSet> = [EnumOptionSet.ONE, EnumOptionSet.THREE]
static fun roundTrip(input: Set<EnumOptionSet>): Set<EnumOptionSet>
}
13 changes: 12 additions & 1 deletion functional-tests/functional/input/src/cpp/Enums.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
// -------------------------------------------------------------------------------------------------

#include "test/Enums.h"
#include "test/UseEnumOptionSet.h"
#include "test/UseEnumWithAlias.h"
#include <unordered_set>

namespace
{
Expand All @@ -30,7 +32,7 @@ flip_enum( const ::test::Enums::InternalError val )
? ::test::Enums::InternalError::ERROR_FATAL
: ::test::Enums::InternalError::ERROR_NONE;
}
} // namespace
}

namespace test
{
Expand Down Expand Up @@ -76,4 +78,13 @@ UseEnumWithAlias::compare_to_first(const test::EnumWithAlias input) {
test::EnumWithAlias
UseEnumWithAlias::get_first() { return test::EnumWithAlias::FIRST; }

// UseEnumOptionSet

std::unordered_set<test::EnumOptionSet, lorem_ipsum::test::hash<test::EnumOptionSet>>
UseEnumOptionSet::round_trip(
const std::unordered_set<test::EnumOptionSet, lorem_ipsum::test::hash<test::EnumOptionSet>>& input
) {
return input;
}

}
25 changes: 24 additions & 1 deletion functional-tests/functional/swift/Tests/EnumsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@ class EnumsTests: XCTestCase {
XCTAssertTrue(result)
}

func testEnumOptionSet() {
let value: EnumOptionSet = [.one, .three]

XCTAssertEqual(value.rawValue, 5)
}

func testEnumOptionSetDefault() {
let value = UseEnumOptionSet(setField: []).setFieldValue

XCTAssertEqual(value.rawValue, 5)
}

func testEnumOptionSetRoundTrip() {
let value: EnumOptionSet = [.one, .three]

let result = UseEnumOptionSet.roundTrip(input: value)

XCTAssertEqual(result.rawValue, 5)
}

static var allTests = [
("testFlipEnumValue", testFlipEnumValue),
("testExtractEnumFromStruct", testExtractEnumFromStruct),
Expand All @@ -115,6 +135,9 @@ class EnumsTests: XCTestCase {
("testDoubleAliasInSwift", testDoubleAliasInSwift),
("testAliasFromCpp", testAliasFromCpp),
("testAliasToTargetCpp", testAliasToTargetCpp),
("testAliasToAlias", testAliasToAlias)
("testAliasToAlias", testAliasToAlias),
("testEnumOptionSet", testEnumOptionSet),
("testEnumOptionSetDefault", testEnumOptionSetDefault),
("testEnumOptionSetRoundTrip", testEnumOptionSetRoundTrip)
]
}
9 changes: 8 additions & 1 deletion functional-tests/scripts/valgrind_suppressions
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@
fun:__swift_instantiateGenericMetadata
fun:*
}
{
instantiateGenericMetadata 2

Memcheck:Leak
match-leak-kinds: possible
...
fun:__swift_instantiateGenericMetadata
}
{
allocateGenericValueMetadata

Expand Down Expand Up @@ -312,4 +320,3 @@
fun:__swift_instantiateConcreteTypeFromMangledName
fun:*
}

Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ import com.here.gluecodium.generator.common.CommentsProcessor
import com.here.gluecodium.generator.common.NameResolver
import com.here.gluecodium.generator.common.ReferenceMapBasedResolver
import com.here.gluecodium.model.lime.LimeAttributeType.OPTIMIZED
import com.here.gluecodium.model.lime.LimeAttributeType.SWIFT
import com.here.gluecodium.model.lime.LimeAttributeValueType.OPTION_SET
import com.here.gluecodium.model.lime.LimeBasicType
import com.here.gluecodium.model.lime.LimeBasicType.TypeId
import com.here.gluecodium.model.lime.LimeClass
import com.here.gluecodium.model.lime.LimeComment
import com.here.gluecodium.model.lime.LimeConstant
import com.here.gluecodium.model.lime.LimeContainer
import com.here.gluecodium.model.lime.LimeElement
import com.here.gluecodium.model.lime.LimeEnumeration
import com.here.gluecodium.model.lime.LimeException
import com.here.gluecodium.model.lime.LimeFunction
import com.here.gluecodium.model.lime.LimeGenericType
Expand Down Expand Up @@ -178,7 +181,14 @@ internal class SwiftNameResolver(
when (limeType) {
is LimeList -> "[${resolveName(limeType.elementType)}]"
is LimeMap -> "[${resolveName(limeType.keyType)}: ${resolveName(limeType.valueType)}]"
is LimeSet -> "Set<${resolveName(limeType.elementType)}>"
is LimeSet -> {
val actualType = limeType.elementType.type.actualType
val elementTypeName = resolveName(limeType.elementType)
when {
actualType is LimeEnumeration && actualType.attributes.have(SWIFT, OPTION_SET) -> elementTypeName
else -> "Set<$elementTypeName>"
}
}
else -> throw GluecodiumExecutionException("Unsupported element type ${limeType.javaClass.name}")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{{!!
!
! Copyright (C) 2016-2022 HERE Europe B.V.
!
! Licensed under the Apache License, Version 2.0 (the "License");
! you may not use this file except in compliance with the License.
! You may obtain a copy of the License at
!
! http://www.apache.org/licenses/LICENSE-2.0
!
! Unless required by applicable law or agreed to in writing, software
! distributed under the License is distributed on an "AS IS" BASIS,
! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
! See the License for the specific language governing permissions and
! limitations under the License.
!
! SPDX-License-Identifier: Apache-2.0
! License-Filename: LICENSE
!
!}}
{{>swift/TypeVisibility}} struct {{resolveName}}{{#if external.swift.converter}}_internal{{/if}} : OptionSet, CaseIterable, Codable {
public let rawValue: UInt32
public init(rawValue: UInt32) {
self.rawValue = rawValue
}

{{#set enum=this}}{{#enumerators}}
{{prefixPartial "optionSetEnumerator" " "}}
{{/enumerators}}{{/set}}

{{resolveName "visibility"}} static var allCases: [{{resolveName}}] {
return [{{#enumerators}}.{{resolveName}}{{#if iter.hasNext}}, {{/if}}{{/enumerators}}]
}
}
{{!!
}}{{+optionSetEnumerator}}
{{>swift/SwiftComment}}{{>swift/SwiftAttributes}}
{{resolveName "visibility"}} static let {{resolveName}} = {{!!
}}{{#if isAlias}}{{resolveName explicitValue}}{{/if}}{{!!
}}{{#unless isAlias}}{{resolveName enum}}(rawValue: {{resolveName explicitValue}}){{/unless}}
{{/optionSetEnumerator}}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@
{{#if external.swift.converter}}
return {{external.swift.converter}}.convertFromInternal({{resolveName this "" "ref"}}_internal(rawValue: cValue)!)
{{/if}}{{#unless external.swift.converter}}
return {{resolveName this "" "ref"}}(rawValue: {{#ifPredicate "skipDeclaration"}}UInt({{/ifPredicate}}cValue{{#ifPredicate "skipDeclaration"}}){{/ifPredicate}})!
return {{resolveName this "" "ref"}}(rawValue: {{!!
}}{{#ifPredicate "skipDeclaration"}}UInt({{/ifPredicate}}cValue{{!!
}}{{#ifPredicate "skipDeclaration"}}){{/ifPredicate}}){{#unless attributes.swift.optionSet}}!{{/unless}}
{{/unless}}
}
{{>swift/ConversionVisibility}} func moveFromCType(_ cValue: UInt32) -> {{resolveName this "" "ref"}} {
Expand All @@ -81,7 +83,9 @@
{{#if external.swift.converter}}
return {{external.swift.converter}}.convertFromInternal({{resolveName this "" "ref"}}_internal(rawValue: uint32_t_value_get(handle))!)
{{/if}}{{#unless external.swift.converter}}
return {{resolveName this "" "ref"}}(rawValue: {{#ifPredicate "skipDeclaration"}}UInt({{/ifPredicate}}uint32_t_value_get(handle){{#ifPredicate "skipDeclaration"}}){{/ifPredicate}})!
return {{resolveName this "" "ref"}}(rawValue: {{!!
}}{{#ifPredicate "skipDeclaration"}}UInt({{/ifPredicate}}uint32_t_value_get(handle){{!!
}}{{#ifPredicate "skipDeclaration"}}){{/ifPredicate}}){{#unless attributes.swift.optionSet}}!{{/unless}}
{{/unless}}
}
{{>swift/ConversionVisibility}} func moveFromCType(_ handle: _baseRef) -> {{resolveName this "" "ref"}}? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
! License-Filename: LICENSE
!
!}}
{{#unlessPredicate "skipDeclaration"}}{{>swift/SwiftComment}}{{>swift/SwiftAttributes}}
{{#unlessPredicate "skipDeclaration"}}{{>swift/SwiftComment}}{{>swift/SwiftAttributes}}{{#if attributes.swift.optionSet}}
{{>swift/EnumOptionSetDefinition}}
{{/if}}{{!!
}}{{#unless attributes.swift.optionSet}}
{{>swift/TypeVisibility}} enum {{resolveName}}{{#if external.swift.converter}}_internal{{/if}} : UInt32, CaseIterable, Codable {
{{#uniqueEnumerators}}{{prefixPartial "enumerator" " "}}
{{/uniqueEnumerators}}{{#if aliasEnumerators}}
Expand Down Expand Up @@ -62,7 +65,9 @@
}
{{/ifPredicate}}
}
{{/unless}}
{{/unlessPredicate}}{{!!
}}{{+enumerator}}{{>swift/SwiftComment}}{{>swift/SwiftAttributes}}
{{#if isAlias}}{{resolveName "visibility"}} static let {{resolveName}} = {{resolveName explicitValue}}{{/if}}{{!!
}}{{#unless isAlias}}case {{resolveName}}{{#if explicitValue}} = {{resolveName explicitValue}}{{/if}}{{/unless}}{{/enumerator}}
13 changes: 11 additions & 2 deletions gluecodium/src/main/resources/templates/swift/SwiftSets.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import Foundation
import {{this}}
{{/imports}}

{{#collections}}
{{#collections}}{{!!
}}{{#set isOptionSet=elementType.type.actualType.attributes.swift.optionSet collection=this}}{{#collection}}
internal func {{>functionPrefix}}copyFromCType(_ handle: _baseRef) -> {{resolveName}} {
var result: {{resolveName}} = []
let iterator_handle = {{resolveName "CBridge"}}_iterator(handle)
Expand All @@ -48,7 +49,11 @@ internal func {{>functionPrefix}}moveFromCType(_ handle: _baseRef) -> {{resolveN

internal func {{>functionPrefix}}copyToCType(_ swiftSet: {{resolveName}}) -> RefHolder {
let handle = {{resolveName "CBridge"}}_create_handle()
{{#if isOptionSet}}
for item in {{elementType}}.allCases.filter({ swiftSet.contains($0) }) {
{{/if}}{{#unless isOptionSet}}
for item in swiftSet {
{{/unless}}
let _item = {{#set typeRef=elementType}}{{>swift/ConversionPrefixTo}}{{/set}}moveToCType(item)
{{resolveName "CBridge"}}_insert(handle, _item.ref)
}
Expand All @@ -68,7 +73,11 @@ internal func {{>functionPrefix}}copyToCType(_ swiftSet: {{resolveName}}?) -> Re
}
let optionalHandle = {{resolveName "CBridge"}}_create_optional_handle()
let handle = {{resolveName "CBridge"}}_unwrap_optional_handle(optionalHandle)
{{#if isOptionSet}}
for item in {{elementType}}.allCases.filter({ swiftSet.contains($0) }) {
{{/if}}{{#unless isOptionSet}}
for item in swiftSet {
{{/unless}}
let _item = {{#set typeRef=elementType}}{{>swift/ConversionPrefixTo}}{{/set}}moveToCType(item)
{{resolveName "CBridge"}}_insert(handle, _item.ref)
}
Expand All @@ -94,7 +103,7 @@ internal func {{>functionPrefix}}moveFromCType(_ handle: _baseRef) -> {{resolveN
return {{>functionPrefix}}copyFromCType(handle)
}

{{/collections}}{{!!
{{/collection}}{{/set}}{{/collections}}{{!!
}}{{+functionPrefix}}{{#ifPredicate "hasCppTypeAttribute"}}{{resolveName "CBridge"}}{{/ifPredicate}}{{!!
}}{{#unlessPredicate "hasCppTypeAttribute"}}{{internalPrefix}}{{!!
Expand Down
41 changes: 41 additions & 0 deletions gluecodium/src/test/resources/smoke/enums/input/EnumOptionSet.lime
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright (C) 2016-2022 HERE Europe B.V.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
# License-Filename: LICENSE

package smoke

@Swift(OptionSet)
enum EnumOptionSet {
ONE = 1,
TWO = 2,
THREE = 4
}

@Swift(OptionSet)
enum EnumOptionSetComments {
ONE = 1,
@Deprecated
TWO = 2,
// Foo bar
THREE = 4
}

struct UseEnumOptionSet {
setField: Set<EnumOptionSet>
setFieldEmpty: Set<EnumOptionSet> = []
setFieldValue: Set<EnumOptionSet> = [EnumOptionSet.ONE, EnumOptionSet.THREE]
static fun roundTrip(input: Set<EnumOptionSet>): Set<EnumOptionSet>
}
Loading

0 comments on commit 65c4c39

Please sign in to comment.