Skip to content

Commit eb27bb6

Browse files
authored
Merge pull request #3602 from DougGregor/se-0091-operators-in-types
Implement SE-0091: Operators in types
2 parents 73106dd + 0f232b3 commit eb27bb6

File tree

70 files changed

+841
-583
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+841
-583
lines changed

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,33 @@ Note: This is in reverse chronological order, so newer entries are added to the
33
Swift 3.0
44
---------
55

6+
* [SE-0091](https://github.com/apple/swift-evolution/blob/master/proposals/0091-improving-operators-in-protocols.md):
7+
Operators can now be defined within types or extensions thereof. For example:
8+
9+
```swift
10+
struct Foo: Equatable {
11+
let value: Int
12+
13+
static func ==(lhs: Foo, rhs: Foo) -> Bool {
14+
return lhs.value == rhs.value
15+
}
16+
}
17+
```
18+
19+
Such operators must be declared as `static` (or, within a class, `class
20+
final`), and have the same signature as their global counterparts. As part of
21+
this change, operator requirements declared in protocols must also be
22+
explicitly declared `static`:
23+
24+
```swift
25+
protocol Equatable {
26+
static func ==(lhs: Self, rhs: Self) -> Bool
27+
}
28+
```
29+
30+
Note that the type checker performance optimization described by SE-0091 is
31+
not yet implemented.
32+
633
* [SE-0099](https://github.com/apple/swift-evolution/blob/master/proposals/0099-conditionclauses.md):
734
Condition clauses in `if`, `guard`, and `while` statements now use a more
835
regular syntax. Each pattern or optional binding must be prefixed with `case`

include/swift/AST/Decl.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4988,7 +4988,8 @@ class FuncDecl final : public AbstractFunctionDecl,
49884988
AccessorKeywordLoc(AccessorKeywordLoc),
49894989
OverriddenOrDerivedForOrBehaviorParamDecl(),
49904990
OperatorAndAddressorKind(nullptr, AddressorKind::NotAddressor) {
4991-
FuncDeclBits.IsStatic = StaticLoc.isValid() || getName().isOperator();
4991+
FuncDeclBits.IsStatic =
4992+
StaticLoc.isValid() || StaticSpelling != StaticSpellingKind::None;
49924993
FuncDeclBits.StaticSpelling = static_cast<unsigned>(StaticSpelling);
49934994
assert(NumParameterLists > 0 && "Must have at least an empty tuple arg");
49944995
setType(Ty);

include/swift/AST/DiagnosticsParse.def

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,15 +289,16 @@ ERROR(associated_type_generic_parameter_list,PointsToFirstBadToken,
289289

290290

291291
// Func
292-
ERROR(func_decl_nonglobal_operator,none,
293-
"operators are only allowed at global scope", ())
294292
ERROR(func_decl_without_paren,PointsToFirstBadToken,
295293
"expected '(' in argument list of function declaration", ())
296294
ERROR(static_func_decl_global_scope,none,
297295
"%select{ERROR|static methods|class methods}0 may only be declared on a type",
298296
(StaticSpellingKind))
299297
ERROR(func_decl_expected_arrow,none,
300298
"expected '->' after function parameter tuple", ())
299+
WARNING(operator_static_in_protocol,none,
300+
"operator '%0' declared in protocol must be 'static'",
301+
(StringRef))
301302

302303
// Enum
303304
ERROR(expected_lbrace_enum,PointsToFirstBadToken,

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,13 @@ NOTE(unary_operator_declaration_here,none,
616616
"%select{prefix|postfix}0 operator found here", (bool))
617617
ERROR(invalid_arg_count_for_operator,none,
618618
"operators must have one or two arguments", ())
619+
ERROR(operator_in_local_scope,none,
620+
"operator functions can only be declared at global or in type scope", ())
621+
ERROR(nonstatic_operator_in_type,none,
622+
"operator %0 declared in type %1 must be 'static'", (Identifier, Type))
623+
ERROR(nonfinal_operator_in_class,none,
624+
"operator %0 declared in non-final class %1 must be 'final'",
625+
(Identifier, Type))
619626

620627
//------------------------------------------------------------------------------
621628
// Type Check Coercions

lib/AST/ASTPrinter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2704,7 +2704,7 @@ void PrintAST::visitFuncDecl(FuncDecl *decl) {
27042704
printSourceRange(Range, Ctx);
27052705
} else {
27062706
if (!Options.SkipIntroducerKeywords) {
2707-
if (decl->isStatic() && !decl->isOperator())
2707+
if (decl->isStatic())
27082708
printStaticKeyword(decl->getCorrectStaticSpelling());
27092709
if (decl->isMutating() && !decl->getAttrs().hasAttribute<MutatingAttr>()) {
27102710
Printer.printKeyword("mutating");

lib/AST/Decl.cpp

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4377,22 +4377,18 @@ bool FuncDecl::isUnaryOperator() const {
43774377
if (!isOperator())
43784378
return false;
43794379

4380-
unsigned opArgIndex
4381-
= getDeclContext()->getAsProtocolOrProtocolExtensionContext() ? 1 : 0;
4382-
4383-
auto *params = getParameterList(opArgIndex);
4380+
auto *params = getParameterList(getDeclContext()->isTypeContext());
43844381
return params->size() == 1 && !params->get(0)->isVariadic();
43854382
}
43864383

43874384
bool FuncDecl::isBinaryOperator() const {
43884385
if (!isOperator())
43894386
return false;
43904387

4391-
unsigned opArgIndex
4392-
= getDeclContext()->getAsProtocolOrProtocolExtensionContext() ? 1 : 0;
4393-
4394-
auto *params = getParameterList(opArgIndex);
4395-
return params->size() == 2 && !params->get(1)->isVariadic();
4388+
auto *params = getParameterList(getDeclContext()->isTypeContext());
4389+
return params->size() == 2 &&
4390+
!params->get(0)->isVariadic() &&
4391+
!params->get(1)->isVariadic();
43964392
}
43974393

43984394
ConstructorDecl::ConstructorDecl(DeclName Name, SourceLoc ConstructorLoc,

lib/Parse/ParseDecl.cpp

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4392,9 +4392,12 @@ Parser::parseDeclFunc(SourceLoc StaticLoc, StaticSpellingKind StaticSpelling,
43924392
StaticLoc = SourceLoc();
43934393
} else if (Flags.contains(PD_InStruct) || Flags.contains(PD_InEnum) ||
43944394
Flags.contains(PD_InProtocol)) {
4395-
if (StaticSpelling == StaticSpellingKind::KeywordClass)
4395+
if (StaticSpelling == StaticSpellingKind::KeywordClass) {
43964396
diagnose(Tok, diag::class_func_not_in_class)
43974397
.fixItReplace(StaticLoc, "static");
4398+
4399+
StaticSpelling = StaticSpellingKind::KeywordStatic;
4400+
}
43984401
}
43994402
}
44004403

@@ -4407,15 +4410,23 @@ Parser::parseDeclFunc(SourceLoc StaticLoc, StaticSpellingKind StaticSpelling,
44074410
Identifier SimpleName;
44084411
Token NameTok = Tok;
44094412
SourceLoc NameLoc = Tok.getLoc();
4410-
Token NonglobalTok = Tok;
4411-
bool NonglobalError = false;
44124413

4413-
if (!(Flags & PD_AllowTopLevel) &&
4414-
!(Flags & PD_InProtocol) &&
4415-
Tok.isAnyOperator()) {
4416-
// Postpone complaining about this error till we see if the
4417-
// DCC wants to move it below.
4418-
NonglobalError = true;
4414+
// Within a protocol, recover from a missing 'static'.
4415+
if (Tok.isAnyOperator() && (Flags & PD_InProtocol)) {
4416+
switch (StaticSpelling) {
4417+
case StaticSpellingKind::None:
4418+
diagnose(NameLoc, diag::operator_static_in_protocol, NameTok.getText())
4419+
.fixItInsert(FuncLoc, "static ");
4420+
StaticSpelling = StaticSpellingKind::KeywordStatic;
4421+
break;
4422+
4423+
case StaticSpellingKind::KeywordStatic:
4424+
// Okay, this is correct.
4425+
break;
4426+
4427+
case StaticSpellingKind::KeywordClass:
4428+
llvm_unreachable("should have been fixed above");
4429+
}
44194430
}
44204431

44214432
if (parseAnyIdentifier(SimpleName, diag::expected_identifier_in_decl,
@@ -4458,12 +4469,6 @@ Parser::parseDeclFunc(SourceLoc StaticLoc, StaticSpellingKind StaticSpelling,
44584469

44594470
DebuggerContextChange DCC(*this, SimpleName, DeclKind::Func);
44604471

4461-
if (NonglobalError && !DCC.movedToTopLevel()) {
4462-
// FIXME: Recovery here is awful.
4463-
diagnose(NonglobalTok, diag::func_decl_nonglobal_operator);
4464-
return nullptr;
4465-
}
4466-
44674472
// Parse the generic-params, if present.
44684473
Optional<Scope> GenericsScope;
44694474
GenericsScope.emplace(this, ScopeKind::Generics);

lib/Sema/ConstraintSystem.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -809,12 +809,16 @@ ConstraintSystem::getTypeOfReference(ValueDecl *value,
809809
openedFnType = openedType->castTo<FunctionType>();
810810
}
811811

812-
// The 'Self' type must be bound to an archetype.
813-
// FIXME: We eventually want to loosen this constraint, to allow us
814-
// to find operator functions both in classes and in protocols to which
815-
// a class conforms (if there's a default implementation).
816-
addArchetypeConstraint(openedFnType->getInput()->getRValueInstanceType(),
817-
getConstraintLocator(locator));
812+
// FIXME: We shouldn't need this for operators in protocols, either, but
813+
// constraint application fails without this at the moment.
814+
if (isa<ProtocolDecl>(func->getDeclContext())) {
815+
// The 'Self' type must be bound to an archetype.
816+
// FIXME: We eventually want to loosen this constraint, to allow us
817+
// to find operator functions both in classes and in protocols to which
818+
// a class conforms (if there's a default implementation).
819+
addArchetypeConstraint(openedFnType->getInput()->getRValueInstanceType(),
820+
getConstraintLocator(locator));
821+
}
818822

819823
// If we opened up any type variables, record the replacements.
820824
recordOpenedTypes(locator, replacements);
@@ -1015,7 +1019,7 @@ void ConstraintSystem::openGeneric(
10151019
// Determine whether this is the protocol 'Self' constraint we should
10161020
// skip.
10171021
if (skipProtocolSelfConstraint &&
1018-
protoDecl == outerDC->getAsProtocolOrProtocolExtensionContext() &&
1022+
protoDecl == outerDC &&
10191023
(protoDecl->getSelfInterfaceType()->getCanonicalType() ==
10201024
req.getFirstType()->getCanonicalType())) {
10211025
break;

lib/Sema/TypeCheckDecl.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3898,6 +3898,41 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
38983898
void bindFuncDeclToOperator(FuncDecl *FD) {
38993899
OperatorDecl *op = nullptr;
39003900
auto operatorName = FD->getFullName().getBaseName();
3901+
3902+
// Check for static/final/class when we're in a type.
3903+
auto dc = FD->getDeclContext();
3904+
if (dc->isTypeContext()) {
3905+
// Within a class, operator functions must be 'static' or 'final'.
3906+
if (auto classDecl = dc->getAsClassOrClassExtensionContext()) {
3907+
// For a class, we also need the function or class to be 'final'.
3908+
if (!classDecl->isFinal() && !FD->isFinal() &&
3909+
FD->getStaticSpelling() != StaticSpellingKind::KeywordStatic) {
3910+
if (!FD->isStatic()) {
3911+
TC.diagnose(FD->getLoc(), diag::nonstatic_operator_in_type,
3912+
operatorName,
3913+
dc->getDeclaredInterfaceType())
3914+
.fixItInsert(FD->getAttributeInsertionLoc(/*forModifier=*/true),
3915+
"static ");
3916+
} else {
3917+
TC.diagnose(FD->getLoc(), diag::nonfinal_operator_in_class,
3918+
operatorName, dc->getDeclaredInterfaceType())
3919+
.fixItInsert(FD->getAttributeInsertionLoc(/*forModifier=*/true),
3920+
"final ");
3921+
FD->getAttrs().add(new (TC.Context) FinalAttr(/*IsImplicit=*/true));
3922+
}
3923+
}
3924+
} else if (!FD->isStatic()) {
3925+
// Operator functions must be static.
3926+
TC.diagnose(FD, diag::nonstatic_operator_in_type,
3927+
operatorName,
3928+
dc->getDeclaredInterfaceType())
3929+
.fixItInsert(FD->getAttributeInsertionLoc(/*forModifier=*/true),
3930+
"static ");
3931+
}
3932+
} else if (!dc->isModuleScopeContext()) {
3933+
TC.diagnose(FD, diag::operator_in_local_scope);
3934+
}
3935+
39013936
SourceFile &SF = *FD->getDeclContext()->getParentSourceFile();
39023937
if (FD->isUnaryOperator()) {
39033938
if (FD->getAttrs().hasAttribute<PrefixAttr>()) {

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,9 @@ matchWitness(TypeChecker &tc,
707707
auto funcWitness = cast<FuncDecl>(witness);
708708

709709
// Either both must be 'static' or neither.
710-
if (funcReq->isStatic() != funcWitness->isStatic())
710+
if (funcReq->isStatic() != funcWitness->isStatic() &&
711+
!(funcReq->isOperator() &&
712+
!funcWitness->getDeclContext()->isTypeContext()))
711713
return RequirementMatch(witness, MatchKind::StaticNonStaticConflict);
712714

713715
// If we require a prefix operator and the witness is not a prefix operator,

stdlib/public/SDK/Foundation/AffineTransform.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,13 @@ extension AffineTransform : ReferenceConvertible, Hashable, CustomStringConverti
254254
public var debugDescription: String {
255255
return description
256256
}
257-
}
258257

259-
public func ==(lhs: AffineTransform, rhs: AffineTransform) -> Bool {
260-
return lhs.m11 == rhs.m11 && lhs.m12 == rhs.m12 &&
261-
lhs.m21 == rhs.m21 && lhs.m22 == rhs.m22 &&
262-
lhs.tX == rhs.tX && lhs.tY == rhs.tY
258+
public static func ==(lhs: AffineTransform, rhs: AffineTransform) -> Bool {
259+
return lhs.m11 == rhs.m11 && lhs.m12 == rhs.m12 &&
260+
lhs.m21 == rhs.m21 && lhs.m22 == rhs.m22 &&
261+
lhs.tX == rhs.tX && lhs.tY == rhs.tY
262+
}
263+
263264
}
264265

265266
extension AffineTransform : _ObjectiveCBridgeable {

stdlib/public/SDK/Foundation/Calendar.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,19 +1065,20 @@ public struct Calendar : CustomStringConvertible, CustomDebugStringConvertible,
10651065
return identifierMap[identifier]!
10661066
}
10671067
}
1068-
}
10691068

1070-
public func ==(lhs: Calendar, rhs: Calendar) -> Bool {
1071-
if lhs._autoupdating || rhs._autoupdating {
1072-
return lhs._autoupdating == rhs._autoupdating
1073-
} else {
1074-
// NSCalendar's isEqual is broken (27019864) so we must implement this ourselves
1075-
return lhs.identifier == rhs.identifier &&
1076-
lhs.locale == rhs.locale &&
1077-
lhs.timeZone == rhs.timeZone &&
1078-
lhs.firstWeekday == rhs.firstWeekday &&
1079-
lhs.minimumDaysInFirstWeek == rhs.minimumDaysInFirstWeek
1069+
public static func ==(lhs: Calendar, rhs: Calendar) -> Bool {
1070+
if lhs._autoupdating || rhs._autoupdating {
1071+
return lhs._autoupdating == rhs._autoupdating
1072+
} else {
1073+
// NSCalendar's isEqual is broken (27019864) so we must implement this ourselves
1074+
return lhs.identifier == rhs.identifier &&
1075+
lhs.locale == rhs.locale &&
1076+
lhs.timeZone == rhs.timeZone &&
1077+
lhs.firstWeekday == rhs.firstWeekday &&
1078+
lhs.minimumDaysInFirstWeek == rhs.minimumDaysInFirstWeek
1079+
}
10801080
}
1081+
10811082
}
10821083

10831084
extension Calendar : _ObjectiveCBridgeable {

stdlib/public/SDK/Foundation/CharacterSet.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -424,11 +424,11 @@ public struct CharacterSet : ReferenceConvertible, Equatable, Hashable, SetAlgeb
424424
public func isSuperset(of other: CharacterSet) -> Bool {
425425
return _mapUnmanaged { $0.isSuperset(of: other) }
426426
}
427-
}
428427

429-
/// Returns true if the two `CharacterSet`s are equal.
430-
public func ==(lhs : CharacterSet, rhs: CharacterSet) -> Bool {
431-
return lhs._wrapped.isEqual(rhs as NSCharacterSet)
428+
/// Returns true if the two `CharacterSet`s are equal.
429+
public static func ==(lhs : CharacterSet, rhs: CharacterSet) -> Bool {
430+
return lhs._wrapped.isEqual(rhs as NSCharacterSet)
431+
}
432432
}
433433

434434

stdlib/public/SDK/Foundation/Data.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -644,11 +644,11 @@ public struct Data : ReferenceConvertible, CustomStringConvertible, Equatable, H
644644

645645
@available(*, unavailable, message: "use withUnsafeMutableBytes instead")
646646
public var mutableBytes: UnsafeMutablePointer<Void> { fatalError() }
647-
}
648647

649-
/// Returns `true` if the two `Data` arguments are equal.
650-
public func ==(d1 : Data, d2 : Data) -> Bool {
651-
return d1._wrapped.isEqual(to: d2)
648+
/// Returns `true` if the two `Data` arguments are equal.
649+
public static func ==(d1 : Data, d2 : Data) -> Bool {
650+
return d1._wrapped.isEqual(to: d2)
651+
}
652652
}
653653

654654
/// Provides bridging functionality for struct Data to class NSData and vice-versa.

0 commit comments

Comments
 (0)