Skip to content

Commit 792145c

Browse files
committed
[copy-operator] Add parsing support for copy operator.
I followed the same model that we used for consume, namely we need an identifier to copy. This ensures that if we have a function called copy or a variable called copy, we do not break source stability. rdar://101862423
1 parent 94959f0 commit 792145c

File tree

5 files changed

+239
-3
lines changed

5 files changed

+239
-3
lines changed

CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,22 @@ public let EXPR_NODES: [Node] = [
12041204
]
12051205
),
12061206

1207+
Node(
1208+
name: "CopyExpr",
1209+
nameForDiagnostics: "'copy' expression",
1210+
kind: "Expr",
1211+
children: [
1212+
Child(
1213+
name: "CopyKeyword",
1214+
kind: .token(choices: [.keyword(text: "copy")])
1215+
),
1216+
Child(
1217+
name: "Expression",
1218+
kind: .node(kind: "Expr")
1219+
),
1220+
]
1221+
),
1222+
12071223
Node(
12081224
name: "MultipleTrailingClosureElementList",
12091225
nameForDiagnostics: nil,

CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public let KEYWORDS: [KeywordSpec] = [
110110
KeywordSpec("class", isLexerClassified: true, requiresTrailingSpace: true),
111111
KeywordSpec("compiler"),
112112
KeywordSpec("consume"),
113+
KeywordSpec("copy"),
113114
KeywordSpec("consuming"),
114115
KeywordSpec("continue", isLexerClassified: true, requiresTrailingSpace: true),
115116
KeywordSpec("convenience"),

Sources/SwiftParser/Expressions.swift

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -474,18 +474,51 @@ extension Parser {
474474
)
475475
)
476476

477+
case (.copyKeyword, let handle)?:
478+
// `copy` is only contextually a keyword, if it's followed by an
479+
// identifier or keyword on the same line. We do this to ensure that we do
480+
// not break any copy functions defined by users. This is following with
481+
// what we have done for the consume keyword.
482+
let next = peek()
483+
484+
// We do this check early so it applies to identifier, dollarIdentifier,
485+
// and the self check.
486+
if next.isAtStartOfLine {
487+
break
488+
}
489+
490+
if next.rawTokenKind != .identifier,
491+
next.rawTokenKind != .dollarIdentifier,
492+
!(TokenSpec(.`self`) ~= next) {
493+
break
494+
}
495+
496+
let copyTok = self.eat(handle)
497+
let sub = self.parseSequenceExpressionElement(
498+
flavor,
499+
forDirective: forDirective,
500+
pattern: pattern
501+
)
502+
return RawExprSyntax(
503+
RawCopyExprSyntax(
504+
copyKeyword: copyTok,
505+
expression: sub,
506+
arena: self.arena
507+
)
508+
)
509+
477510
case (.consumeKeyword, let handle)?:
478511
// `consume` is only contextually a keyword, if it's followed by an
479512
// identifier or keyword on the same line.
480513
let next = peek()
481514
if next.isAtStartOfLine {
482-
fallthrough
515+
break
483516
}
484517
if next.rawTokenKind != .identifier,
485518
next.rawTokenKind != .dollarIdentifier,
486519
next.rawTokenKind != .keyword
487520
{
488-
fallthrough
521+
break
489522
}
490523

491524
let consumeTok = self.eat(handle)
@@ -502,8 +535,9 @@ extension Parser {
502535
)
503536
)
504537
case nil:
505-
return self.parseUnaryExpression(flavor, forDirective: forDirective, pattern: pattern)
538+
break
506539
}
540+
return self.parseUnaryExpression(flavor, forDirective: forDirective, pattern: pattern)
507541
}
508542

509543
/// Parse an optional prefix operator followed by an expression.

Sources/SwiftParser/TokenSpecSet.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ enum AwaitTryMove: TokenSpecSet {
482482
case _borrowKeyword
483483
case tryKeyword
484484
case consumeKeyword
485+
case copyKeyword
485486

486487
init?(lexeme: Lexer.Lexeme) {
487488
switch PrepareForKeywordMatch(lexeme) {
@@ -490,6 +491,7 @@ enum AwaitTryMove: TokenSpecSet {
490491
case TokenSpec(._borrow): self = ._borrowKeyword
491492
case TokenSpec(.try): self = .tryKeyword
492493
case TokenSpec(.consume): self = .consumeKeyword
494+
case TokenSpec(.copy): self = .copyKeyword
493495
default: return nil
494496
}
495497
}
@@ -500,6 +502,7 @@ enum AwaitTryMove: TokenSpecSet {
500502
case ._moveKeyword: return .keyword(._move)
501503
case ._borrowKeyword: return .keyword(._borrow)
502504
case .consumeKeyword: return .keyword(.consume)
505+
case .copyKeyword: return .keyword(.copy)
503506
case .tryKeyword: return .keyword(.try)
504507
}
505508
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 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+
// This test file has been translated from swift/test/Parse/copy_expr.swift
14+
15+
import XCTest
16+
17+
final class CopyExprTests: XCTestCase {
18+
func testGlobal() {
19+
assertParse(
20+
"""
21+
var global: Int = 5
22+
func testGlobal() {
23+
let _ = copy global
24+
}
25+
"""
26+
)
27+
}
28+
29+
func testLet() {
30+
assertParse(
31+
"""
32+
func testLet() {
33+
let t = String()
34+
let _ = copy t
35+
}
36+
"""
37+
)
38+
}
39+
40+
func testVar() {
41+
assertParse(
42+
"""
43+
func testVar() {
44+
var t = String()
45+
t = String()
46+
let _ = copy t
47+
}
48+
"""
49+
)
50+
}
51+
52+
func testStillAbleToCallFunctionCalledCopy() {
53+
assertParse(
54+
"""
55+
func copy() {}
56+
func copy(_: String) {}
57+
func copy(_: String, _: Int) {}
58+
func copy(x: String, y: Int) {}
59+
60+
func useCopyFunc() {
61+
var s = String()
62+
var i = global
63+
64+
copy()
65+
copy(s)
66+
copy(i) // expected-error{{cannot convert value of type 'Int' to expected argument type 'String'}}
67+
copy(s, i)
68+
copy(i, s) // expected-error{{unnamed argument #2 must precede unnamed argument #1}}
69+
copy(x: s, y: i)
70+
copy(y: i, x: s) // expected-error{{argument 'x' must precede argument 'y'}}
71+
}
72+
"""
73+
)
74+
}
75+
76+
// Ensure we can still parse a variable named copy.
77+
func testUseCopyVariable() {
78+
assertParse(
79+
"""
80+
func useCopyVar(copy: inout String) {
81+
let s = copy
82+
copy = s
83+
84+
// We can copy from a variable named `copy`
85+
let t = copy copy
86+
copy = t
87+
88+
// We can do member access and subscript a variable named `copy`
89+
let i = copy.startIndex
90+
let _ = copy[i]
91+
}
92+
"""
93+
)
94+
}
95+
96+
func testPropertyWrapperWithCopy() {
97+
assertParse(
98+
"""
99+
@propertyWrapper
100+
struct FooWrapper<T> {
101+
var value: T
102+
103+
init(wrappedValue: T) { value = wrappedValue }
104+
105+
var wrappedValue: T {
106+
get { value }
107+
nonmutating set {}
108+
}
109+
var projectedValue: T {
110+
get { value }
111+
nonmutating set {}
112+
}
113+
}
114+
115+
struct Foo {
116+
@FooWrapper var wrapperTest: String
117+
118+
func copySelf() {
119+
_ = copy self
120+
}
121+
122+
func copyPropertyWrapper() {
123+
// should still parse, even if it doesn't semantically work out
124+
_ = copy wrapperTest // expected-error{{can only be applied to lvalues}}
125+
_ = copy _wrapperTest // expected-error{{can only be applied to lvalues}}
126+
_ = copy $wrapperTest // expected-error{{can only be applied to lvalues}}
127+
}
128+
}
129+
"""
130+
)
131+
}
132+
133+
func testAsCaseStmt() {
134+
assertParse(
135+
"""
136+
class ParentKlass {}
137+
class SubKlass : ParentKlass {}
138+
139+
func test(_ s: SubKlass) {
140+
switch s {
141+
case let copy as ParentKlass:
142+
fallthrough
143+
}
144+
}
145+
"""
146+
)
147+
}
148+
149+
func testParseCanCopyClosureDollarIdentifier() {
150+
assertParse(
151+
"""
152+
class Klass {}
153+
let f: (Klass) -> () = {
154+
let _ = copy $0
155+
}
156+
"""
157+
)
158+
}
159+
160+
func testForLoop() {
161+
assertParse(
162+
"""
163+
func test() {
164+
for copy in 1..<1024 {
165+
}
166+
}
167+
"""
168+
)
169+
}
170+
171+
func testCopySelf() {
172+
assertParse(
173+
"""
174+
class Klass {
175+
func test() {
176+
let _ = copy self
177+
}
178+
}
179+
"""
180+
)
181+
}
182+
}

0 commit comments

Comments
 (0)