diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 1c1e85b656832..3b7cfe609f65a 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -1421,6 +1421,12 @@ ERROR(corresponding_param_not_defaulted,none, NOTE(inherited_default_param_here,none, "corresponding parameter declared here", ()) +WARNING(option_set_zero_constant,none, + "static property %0 produces an empty option set", + (Identifier)) +NOTE(option_set_empty_set_init,none, + "use [] to silence this warning", ()) + // Alignment attribute ERROR(alignment_not_power_of_two,none, "alignment value must be a power of two", ()) diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index a6e48a0ede875..68a5a4f6893ce 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -484,6 +484,68 @@ static void checkInheritanceClause( } } +// Check for static properties that produce empty option sets +// using a rawValue initializer with a value of '0' +static void checkForEmptyOptionSet(const VarDecl *VD, TypeChecker &tc) { + // Check if property is a 'static let' + if (!VD->isStatic() || !VD->isLet()) + return; + + auto DC = VD->getDeclContext(); + + // Make sure property is of same type as the type it is declared in + if (!VD->getType()->isEqual(DC->getSelfTypeInContext())) + return; + + // Make sure this type conforms to OptionSet + auto *optionSetProto = tc.Context.getProtocol(KnownProtocolKind::OptionSet); + bool conformsToOptionSet = (bool)tc.containsProtocol( + DC->getSelfTypeInContext(), + optionSetProto, + DC, + /*Flags*/None); + + if (!conformsToOptionSet) + return; + + auto PBD = VD->getParentPatternBinding(); + if (!PBD) + return; + + auto initIndex = PBD->getPatternEntryIndexForVarDecl(VD); + auto init = PBD->getInit(initIndex); + + // Make sure property is being set with a constructor + auto ctor = dyn_cast_or_null(init); + if (!ctor) + return; + auto ctorCalledVal = ctor->getCalledValue(); + if (!ctorCalledVal) + return; + if (!isa(ctorCalledVal)) + return; + + // Make sure it is calling the rawValue constructor + if (ctor->getNumArguments() != 1) + return; + if (ctor->getArgumentLabels().front() != tc.Context.Id_rawValue) + return; + + // Make sure the rawValue parameter is a '0' integer literal + auto *args = cast(ctor->getArg()); + auto intArg = dyn_cast(args->getElement(0)); + if (!intArg) + return; + if (intArg->getValue() != 0) + return; + + auto loc = VD->getLoc(); + tc.diagnose(loc, diag::option_set_zero_constant, VD->getName()); + tc.diagnose(loc, diag::option_set_empty_set_init) + .fixItReplace(args->getSourceRange(), "([])"); +} + + /// Check the inheritance clauses generic parameters along with any /// requirements stored within the generic parameter list. static void checkGenericParams(GenericParamList *genericParams, @@ -2387,6 +2449,8 @@ class DeclChecker : public DeclVisitor { VD->diagnose(diag::dynamic_self_in_mutable_property); } } + + checkForEmptyOptionSet(VD, TC); // Under the Swift 3 inference rules, if we have @IBInspectable or // @GKInspectable but did not infer @objc, warn that the attribute is diff --git a/test/Sema/option-set-empty.swift b/test/Sema/option-set-empty.swift new file mode 100644 index 0000000000000..3416e15f1e269 --- /dev/null +++ b/test/Sema/option-set-empty.swift @@ -0,0 +1,32 @@ +// RUN: %target-typecheck-verify-swift + +struct SomeOptions: OptionSet { + var rawValue: Int + + static let some = MyOptions(rawValue: 4) + static let empty = SomeOptions(rawValue: 0) // expected-warning {{static property 'empty' produces an empty option set}} expected-note {{use [] to silence this warning}}{{35-48=([])}} + static var otherVal = SomeOptions(rawValue: 0) + + let someVal = MyOptions(rawValue: 6) + let option = MyOptions(float: Float.infinity) + let none = SomeOptions(rawValue: 0) // expected-error {{value type 'SomeOptions' cannot have a stored property that recursively contains it}} +} + +struct MyOptions: OptionSet { + let rawValue: Int + + init(rawValue: Int) { + self.rawValue = rawValue + } + + init(float: Float) { + self.rawValue = float.exponent + } + + static let none = MyOptions(rawValue: 0) // expected-warning {{static property 'none' produces an empty option set}} expected-note {{use [] to silence this warning}}{{32-45=([])}} + static var nothing = MyOptions(rawValue: 0) + static let nope = MyOptions() + static let other = SomeOptions(rawValue: 8) + static let piVal = MyOptions(float: Float.pi) + static let zero = MyOptions(float: 0.0) +}