Skip to content

Commit 750dc6c

Browse files
authored
Merge pull request #19174 from lancep/INShortcut-cherry-pick
Cherry-pick INShortcut overlay
2 parents 49a9b4e + 00b5061 commit 750dc6c

File tree

4 files changed

+209
-1
lines changed

4 files changed

+209
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ supported host development operating systems.
6969

7070
#### macOS
7171

72-
To build for macOS, you need [Xcode 10 beta 3](https://developer.apple.com/xcode/downloads/).
72+
To build for macOS, you need [Xcode 10 beta 6](https://developer.apple.com/xcode/downloads/).
7373
The required version of Xcode changes frequently, and is often a beta release.
7474
Check this document or the host information on <https://ci.swift.org> for the
7575
current required version.

stdlib/public/SDK/Intents/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ add_swift_library(swiftIntents ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SDK_O
2222
INSetProfileInCarIntent.swift
2323
INSetRadioStationIntent.swift
2424
INSetSeatSettingsInCarIntent.swift
25+
INShortcut.swift
2526
INStartPhotoPlaybackIntentResponse.swift
2627
INStartWorkoutIntent.swift
2728
NSStringIntents.swift
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
@_exported import Intents
14+
import Foundation
15+
16+
#if os(iOS) || os(watchOS)
17+
18+
@available(iOS 12.0, watchOS 5.0, *)
19+
public enum INShortcut : ReferenceConvertible {
20+
public typealias ReferenceType = INShortcutReference
21+
22+
case intent(INIntent)
23+
case userActivity(NSUserActivity)
24+
25+
init(from objcShortcut: INShortcutReference) {
26+
if let intent = objcShortcut.intent {
27+
self = .intent(intent)
28+
} else if let userActivity = objcShortcut.userActivity {
29+
self = .userActivity(userActivity)
30+
} else {
31+
fatalError("INShortcutReference object must have either intent or userActivity")
32+
}
33+
}
34+
}
35+
36+
// Convenience initializers, to mimic the ObjC initializer API
37+
@available(iOS 12.0, watchOS 5.0, *)
38+
public extension INShortcut {
39+
public init?(intent: INIntent) {
40+
// use the ObjC initializer, to re-use its validation of the intent
41+
guard let ref = INShortcutReference(intent: intent) else { return nil }
42+
self.init(from: ref)
43+
}
44+
public init(userActivity: NSUserActivity) {
45+
self = .userActivity(userActivity)
46+
}
47+
}
48+
49+
// Convenience properties, to mimic the ObjC API
50+
@available(iOS 12.0, watchOS 5.0, *)
51+
public extension INShortcut {
52+
public var intent: INIntent? {
53+
guard case let .intent(intent) = self else { return nil }
54+
return intent
55+
}
56+
public var userActivity: NSUserActivity? {
57+
guard case let .userActivity(userActivity) = self else { return nil }
58+
return userActivity
59+
}
60+
}
61+
62+
@available(iOS 12.0, watchOS 5.0, *)
63+
extension INShortcut : CustomStringConvertible {
64+
public var description: String {
65+
return reference.description
66+
}
67+
}
68+
69+
@available(iOS 12.0, watchOS 5.0, *)
70+
extension INShortcut : CustomDebugStringConvertible {
71+
public var debugDescription: String {
72+
return reference.debugDescription
73+
}
74+
}
75+
76+
@available(iOS 12.0, watchOS 5.0, *)
77+
extension INShortcut : Hashable {
78+
public func hash(into hasher: inout Hasher) {
79+
reference.hash(into: &hasher)
80+
}
81+
}
82+
83+
@available(iOS 12.0, watchOS 5.0, *)
84+
extension INShortcut : Equatable {}
85+
86+
@available(iOS 12.0, watchOS 5.0, *)
87+
private extension INShortcut {
88+
fileprivate var reference: INShortcutReference {
89+
switch self {
90+
case .intent(let intent):
91+
return INShortcutReference(intent: intent)!
92+
case .userActivity(let userActivity):
93+
return INShortcutReference(userActivity: userActivity)
94+
}
95+
}
96+
}
97+
98+
@available(iOS 12.0, watchOS 5.0, *)
99+
extension INShortcut : _ObjectiveCBridgeable {
100+
@_semantics("convertToObjectiveC")
101+
public func _bridgeToObjectiveC() -> INShortcutReference {
102+
return self.reference
103+
}
104+
105+
public static func _forceBridgeFromObjectiveC(_ source: INShortcutReference, result: inout INShortcut?) {
106+
if !_conditionallyBridgeFromObjectiveC(source, result: &result) {
107+
fatalError("Unable to bridge \(_ObjectiveCType.self) to \(self)")
108+
}
109+
}
110+
111+
public static func _conditionallyBridgeFromObjectiveC(_ source: INShortcutReference, result: inout INShortcut?) -> Bool {
112+
result = INShortcut(from: source)
113+
return true
114+
}
115+
116+
public static func _unconditionallyBridgeFromObjectiveC(_ source: INShortcutReference?) -> INShortcut {
117+
guard let src = source else { fatalError("Missing source") }
118+
return INShortcut(from: src)
119+
}
120+
}
121+
122+
#endif

test/stdlib/Intents.swift

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,91 @@ if #available(iOS 11.0, *) {
5555
}
5656
#endif
5757

58+
#if os(iOS) || os(watchOS)
59+
if #available(iOS 12.0, watchOS 5.0, *) {
60+
61+
// helper functions to build test data
62+
func buildIntent(_ identifier: String) -> INIntent {
63+
let person = INPerson(personHandle: INPersonHandle(value: "123-456-7890", type: .phoneNumber), nameComponents: nil, displayName: "test person \(identifier)", image: nil, contactIdentifier: nil, customIdentifier: "testPerson \(identifier)")
64+
return INSendMessageIntent(recipients: [person], content: "test content \(identifier)", speakableGroupName: INSpeakableString(spokenPhrase: "test group \(identifier)"), conversationIdentifier: nil, serviceName: "test service \(identifier)", sender: person)
65+
}
66+
func buildUserActivity(_ identifier: String) -> NSUserActivity {
67+
return NSUserActivity(activityType: "test activity \(identifier)")
68+
}
69+
70+
IntentsTestSuite.test("INShortcutOverlay/is enum/\(swiftVersion)") {
71+
// INIntent
72+
let originalIntent = buildIntent("A")
73+
let shortcutWithIntent: INShortcut = .intent(originalIntent)
74+
switch shortcutWithIntent {
75+
case .intent(let intent):
76+
expectEqual(intent, originalIntent)
77+
case .userActivity:
78+
expectUnreachable()
79+
}
80+
// test convenince properties
81+
expectEqual(shortcutWithIntent.intent, originalIntent)
82+
expectEqual(shortcutWithIntent.userActivity, nil)
83+
// test convenince init
84+
expectEqual(INShortcut(intent: originalIntent), shortcutWithIntent)
85+
86+
// NSUserActivity
87+
let originalUserActivity = buildUserActivity("A")
88+
let shortcutWithNSUA: INShortcut = .userActivity(originalUserActivity)
89+
switch shortcutWithNSUA {
90+
case .intent:
91+
expectUnreachable()
92+
case .userActivity(let userActivity):
93+
expectEqual(userActivity, originalUserActivity)
94+
}
95+
// test convenince properties
96+
expectEqual(shortcutWithNSUA.intent, nil)
97+
expectEqual(shortcutWithNSUA.userActivity, originalUserActivity)
98+
// test convenince init
99+
expectEqual(INShortcut(userActivity: originalUserActivity), shortcutWithNSUA)
100+
}
101+
102+
IntentsTestSuite.test("INShortcutOverlay/conformances/\(swiftVersion)") {
103+
let intentA = buildIntent("A")
104+
let shortcutIntentA: INShortcut = .intent(intentA)
105+
let shortcutIntentA2: INShortcut = .intent(intentA)
106+
let shortcutIntentB: INShortcut = .intent(buildIntent("B"))
107+
let userActivityA = buildUserActivity("A")
108+
let shortcutUserActivityA: INShortcut = .userActivity(userActivityA)
109+
let shortcutUserActivityA2: INShortcut = .userActivity(userActivityA)
110+
let shortcutUserActivityB: INShortcut = .userActivity(buildUserActivity("B"))
111+
112+
// Equatable
113+
expectEqual(shortcutIntentA, shortcutIntentA2)
114+
expectNotEqual(shortcutIntentA, shortcutIntentB)
115+
expectEqual(shortcutUserActivityA, shortcutUserActivityA2)
116+
expectNotEqual(shortcutUserActivityA, shortcutUserActivityB)
117+
expectNotEqual(shortcutIntentA, shortcutUserActivityA)
118+
119+
// Hashable
120+
// expectEqual(shortcutIntentA.hashValue, shortcutIntentA.hashValue)
121+
// expectEqual(shortcutUserActivityA.hashValue, shortcutUserActivityA.hashValue)
122+
123+
// Strings
124+
let _: String = shortcutIntentA.description
125+
let _: String = shortcutIntentA.debugDescription
126+
}
127+
128+
// Make sure the shortcut property of INVoiceShortcut is imported as the overlay enum type
129+
IntentsTestSuite.test("INShortcutOverlay/INVoiceShortcut propertyIsEnum/\(swiftVersion)") {
130+
// NOTE: we can't actually run this one becuase we can't easily create an INVoiceShortcut, but at least type-check it
131+
func f(voiceShortcut: INVoiceShortcut) {
132+
switch voiceShortcut.shortcut {
133+
case .intent(let intent):
134+
print("got intent \(intent)")
135+
case .userActivity(let userActivity):
136+
print("got userActivity \(userActivity)")
137+
}
138+
}
139+
}
140+
}
141+
#endif
142+
58143
#if os(iOS) || os(watchOS)
59144
if #available(iOS 10.0, watchOS 3.2, *) {
60145

0 commit comments

Comments
 (0)