mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Handle initial value requirements in property behaviors.
If a behavior protocol requires an `initialValue` static property, satisfy the requirement using the initial value expression from the property declaration. This lets us implement `lazy` as a property behavior.
This commit is contained in:
@@ -1554,16 +1554,30 @@ ERROR(property_behavior_with_self_requirement_not_in_type,none,
|
||||
"property behavior %0 can only be used on instance properties "
|
||||
"because it has 'Self' requirements",
|
||||
(Identifier))
|
||||
ERROR(property_behavior_with_storage_not_supported,none,
|
||||
"property behavior %0 with storage is not supported outside of "
|
||||
ERROR(property_behavior_with_feature_not_supported,none,
|
||||
"property behavior %0 with %1 is not supported outside of "
|
||||
"type contexts",
|
||||
(Identifier))
|
||||
(Identifier, StringRef))
|
||||
ERROR(property_behavior_value_type_doesnt_match,none,
|
||||
"property behavior %0 provides 'value' property implementation with "
|
||||
"type %1 that doesn't match type %2 of declared property %3",
|
||||
(Identifier, Type, Identifier, Type))
|
||||
NOTE(property_behavior_value_decl_here,none,
|
||||
"'value' property declared here", ())
|
||||
ERROR(property_behavior_invalid_initialValue_reqt,none,
|
||||
"property behavior %0 'initialValue' requirement must be static, "
|
||||
"get-only, and of type 'Self.Value'", (Identifier))
|
||||
ERROR(property_behavior_requires_initialValue,none,
|
||||
"property behavior %0 requires an initializer in the declaration of %1",
|
||||
(Identifier, Identifier))
|
||||
ERROR(property_behavior_requires_unique_initialValue,none,
|
||||
"property behavior %0 cannot destructure an initializer expression in a "
|
||||
"compound pattern",
|
||||
(Identifier))
|
||||
ERROR(property_behavior_invalid_initializer,none,
|
||||
"initializer expression provided, but property behavior %0 does not "
|
||||
"use it",
|
||||
(Identifier))
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Type Check Attributes
|
||||
|
||||
@@ -41,6 +41,7 @@ IDENTIFIER(generate)
|
||||
IDENTIFIER(Generator)
|
||||
IDENTIFIER(hashValue)
|
||||
IDENTIFIER(init)
|
||||
IDENTIFIER(initialValue)
|
||||
IDENTIFIER(initStorage)
|
||||
IDENTIFIER(load)
|
||||
IDENTIFIER(next)
|
||||
|
||||
@@ -1220,6 +1220,14 @@ void TypeChecker::completePropertyBehaviorStorage(VarDecl *VD,
|
||||
DC);
|
||||
Storage->setInterfaceType(SubstStorageInterfaceTy);
|
||||
Storage->setUserAccessible(false);
|
||||
// Mark the vardecl to be final, implicit, and private. In a class, this
|
||||
// prevents it from being dynamically dispatched.
|
||||
if (VD->getDeclContext()->getAsClassOrClassExtensionContext())
|
||||
makeFinal(Context, Storage);
|
||||
Storage->setImplicit();
|
||||
Storage->setAccessibility(Accessibility::Private);
|
||||
Storage->setSetterAccessibility(Accessibility::Private);
|
||||
|
||||
addMemberToContextIfNeeded(Storage, DC);
|
||||
|
||||
// Build the initializer expression, 'Self.initStorage()', using the
|
||||
@@ -1266,14 +1274,6 @@ void TypeChecker::completePropertyBehaviorStorage(VarDecl *VD,
|
||||
PBD->setInitializerChecked(0);
|
||||
addMemberToContextIfNeeded(PBD, VD->getDeclContext(), VD);
|
||||
|
||||
// Mark the vardecl to be final, implicit, and private. In a class, this
|
||||
// prevents it from being dynamically dispatched.
|
||||
if (VD->getDeclContext()->getAsClassOrClassExtensionContext())
|
||||
makeFinal(Context, Storage);
|
||||
Storage->setImplicit();
|
||||
Storage->setAccessibility(Accessibility::Private);
|
||||
Storage->setSetterAccessibility(Accessibility::Private);
|
||||
|
||||
// Add accessors to the storage, since we'll need them to satisfy the
|
||||
// conformance requirements.
|
||||
addTrivialAccessorsToStorage(Storage, *this);
|
||||
@@ -1294,6 +1294,89 @@ void TypeChecker::completePropertyBehaviorStorage(VarDecl *VD,
|
||||
ConcreteDeclRef(Context, Storage->getSetter(), MemberSubs));
|
||||
}
|
||||
|
||||
void TypeChecker::completePropertyBehaviorInitialValue(VarDecl *VD,
|
||||
VarDecl *BehaviorInitialValue,
|
||||
NormalProtocolConformance *BehaviorConformance,
|
||||
ArrayRef<Substitution> SelfInterfaceSubs,
|
||||
ArrayRef<Substitution> SelfContextSubs) {
|
||||
// Create a property to witness the requirement.
|
||||
auto DC = VD->getDeclContext();
|
||||
SmallString<64> NameBuf = VD->getName().str();
|
||||
NameBuf += ".initialValue";
|
||||
auto InitialValueName = Context.getIdentifier(NameBuf);
|
||||
// TODO: non-static initialValue
|
||||
assert(BehaviorInitialValue->isStatic());
|
||||
auto *InitialValue = new (Context) VarDecl(/*static*/ true,
|
||||
/*let*/ false,
|
||||
VD->getLoc(),
|
||||
InitialValueName,
|
||||
SelfContextSubs[1].getReplacement(),
|
||||
DC);
|
||||
InitialValue->setInterfaceType(SelfInterfaceSubs[1].getReplacement());
|
||||
InitialValue->setUserAccessible(false);
|
||||
// Mark the vardecl to be final, implicit, and private. In a class, this
|
||||
// prevents it from being dynamically dispatched.
|
||||
if (VD->getDeclContext()->getAsClassOrClassExtensionContext())
|
||||
makeFinal(Context, InitialValue);
|
||||
InitialValue->setImplicit();
|
||||
InitialValue->setAccessibility(Accessibility::Private);
|
||||
InitialValue->setIsBeingTypeChecked();
|
||||
|
||||
addMemberToContextIfNeeded(InitialValue, DC);
|
||||
|
||||
Pattern *InitialPBDPattern = new (Context) NamedPattern(InitialValue,
|
||||
/*implicit*/true);
|
||||
InitialPBDPattern = new (Context) TypedPattern(InitialPBDPattern,
|
||||
TypeLoc::withoutLoc(SelfContextSubs[1].getReplacement()),
|
||||
/*implicit*/ true);
|
||||
auto *InitialPBD = PatternBindingDecl::create(Context,
|
||||
/*staticloc*/SourceLoc(),
|
||||
StaticSpellingKind::KeywordStatic,
|
||||
VD->getLoc(),
|
||||
InitialPBDPattern, nullptr,
|
||||
VD->getDeclContext());
|
||||
InitialPBD->setImplicit();
|
||||
InitialPBD->setInitializerChecked(0);
|
||||
addMemberToContextIfNeeded(InitialPBD, VD->getDeclContext(), VD);
|
||||
|
||||
// Create a getter.
|
||||
auto Get = createGetterPrototype(InitialValue, *this);
|
||||
addMemberToContextIfNeeded(Get, DC);
|
||||
|
||||
// Take the initializer from the PatternBindingDecl for VD.
|
||||
auto *InitValue = VD->getParentInitializer();
|
||||
auto PBD = VD->getParentPatternBinding();
|
||||
unsigned entryIndex = PBD->getPatternEntryIndexForVarDecl(VD);
|
||||
PBD->setInit(entryIndex, nullptr);
|
||||
PBD->setInitializerChecked(entryIndex);
|
||||
|
||||
// Recontextualize any closure declcontexts nested in the initializer to
|
||||
// realize that they are in the getter function.
|
||||
InitValue->walk(RecontextualizeClosures(Get));
|
||||
|
||||
// Return the expression value.
|
||||
auto Ret = new (Context) ReturnStmt(SourceLoc(), InitValue,
|
||||
/*implicit*/ true);
|
||||
auto Body = BraceStmt::create(Context, SourceLoc(), ASTNode(Ret),
|
||||
SourceLoc(), /*implicit*/ true);
|
||||
Get->setBody(Body);
|
||||
|
||||
InitialValue->makeComputed(SourceLoc(), Get, nullptr, nullptr, SourceLoc());
|
||||
InitialValue->setIsBeingTypeChecked(false);
|
||||
|
||||
// Add the witnesses to the conformance.
|
||||
ArrayRef<Substitution> MemberSubs;
|
||||
if (DC->isGenericTypeContext()) {
|
||||
MemberSubs = DC->getGenericParamsOfContext()
|
||||
->getForwardingSubstitutions(Context);
|
||||
}
|
||||
|
||||
BehaviorConformance->setWitness(BehaviorInitialValue,
|
||||
ConcreteDeclRef(Context, InitialValue, MemberSubs));
|
||||
BehaviorConformance->setWitness(BehaviorInitialValue->getGetter(),
|
||||
ConcreteDeclRef(Context, Get, MemberSubs));
|
||||
}
|
||||
|
||||
void TypeChecker::completePropertyBehaviorAccessors(VarDecl *VD,
|
||||
VarDecl *ValueImpl,
|
||||
ArrayRef<Substitution> SelfInterfaceSubs,
|
||||
@@ -1559,20 +1642,26 @@ void swift::maybeAddAccessorsToVariable(VarDecl *var, TypeChecker &TC) {
|
||||
NormalProtocolConformance *conformance = nullptr;
|
||||
VarDecl *valueProp = nullptr;
|
||||
|
||||
bool mightBeMutating = dc->isTypeContext()
|
||||
&& !var->isStatic()
|
||||
&& !dc->getDeclaredInterfaceType()->getClassOrBoundGenericClass();
|
||||
|
||||
auto makeBehaviorAccessors = [&]{
|
||||
FuncDecl *getter;
|
||||
FuncDecl *setter = nullptr;
|
||||
if (valueProp && valueProp->getGetter()) {
|
||||
getter = createGetterPrototype(var, TC);
|
||||
// The getter is mutating if the behavior implementation is.
|
||||
getter->setMutating(valueProp->getGetter()->isMutating());
|
||||
// The getter is mutating if the behavior implementation is, unless
|
||||
// we're in a class or non-instance context.
|
||||
getter->setMutating(mightBeMutating &&
|
||||
valueProp->getGetter()->isMutating());
|
||||
getter->setAccessibility(var->getFormalAccess());
|
||||
|
||||
// Make a setter if the behavior property has one.
|
||||
if (auto valueSetter = valueProp->getSetter()) {
|
||||
ParamDecl *newValueParam = nullptr;
|
||||
setter = createSetterPrototype(var, newValueParam, TC);
|
||||
setter->setMutating(valueSetter->isMutating());
|
||||
setter->setMutating(mightBeMutating && valueSetter->isMutating());
|
||||
// TODO: max of property and implementation setter visibility?
|
||||
setter->setAccessibility(var->getFormalAccess());
|
||||
}
|
||||
|
||||
@@ -2697,6 +2697,7 @@ static void checkVarBehavior(VarDecl *decl, TypeChecker &TC) {
|
||||
|
||||
// First, satisfy any associated type requirements.
|
||||
Substitution valueSub;
|
||||
AssociatedTypeDecl *valueReqt = nullptr;
|
||||
for (auto requirementDecl : behaviorProto->getMembers()) {
|
||||
auto assocTy = dyn_cast<AssociatedTypeDecl>(requirementDecl);
|
||||
if (!assocTy)
|
||||
@@ -2708,6 +2709,8 @@ static void checkVarBehavior(VarDecl *decl, TypeChecker &TC) {
|
||||
continue;
|
||||
}
|
||||
|
||||
valueReqt = assocTy;
|
||||
|
||||
// Check for required protocol conformances.
|
||||
// TODO: Handle secondary 'where' constraints on the associated types.
|
||||
// TODO: Handle non-protocol constraints ('class', base class)
|
||||
@@ -2770,6 +2773,7 @@ static void checkVarBehavior(VarDecl *decl, TypeChecker &TC) {
|
||||
// Now that type witnesses are done, satisfy property and method requirements.
|
||||
conformance->setState(ProtocolConformanceState::Checking);
|
||||
|
||||
bool requiresInitialValue = false;
|
||||
for (auto requirementDecl : behaviorProto->getMembers()) {
|
||||
auto requirement = dyn_cast<ValueDecl>(requirementDecl);
|
||||
if (!requirement)
|
||||
@@ -2777,81 +2781,133 @@ static void checkVarBehavior(VarDecl *decl, TypeChecker &TC) {
|
||||
if (isa<AssociatedTypeDecl>(requirement))
|
||||
continue;
|
||||
|
||||
// Match a storage requirement.
|
||||
if (auto storageReqt = dyn_cast<VarDecl>(requirement)) {
|
||||
if (storageReqt->getName() != TC.Context.Id_storage) {
|
||||
unknownRequirement(requirement);
|
||||
continue;
|
||||
}
|
||||
auto storageTy = storageReqt->getInterfaceType();
|
||||
auto expectedInitStorageTy =
|
||||
FunctionType::get(TC.Context.TheEmptyTupleType, storageTy);
|
||||
if (auto varReqt = dyn_cast<VarDecl>(requirement)) {
|
||||
// Match a storage requirement.
|
||||
if (varReqt->getName() == TC.Context.Id_storage) {
|
||||
auto storageTy = varReqt->getInterfaceType();
|
||||
auto expectedInitStorageTy =
|
||||
FunctionType::get(TC.Context.TheEmptyTupleType, storageTy);
|
||||
|
||||
// We need an initStorage extension method to initialize this storage.
|
||||
auto lookup = TC.lookupMember(dc, behaviorProtoTy,
|
||||
TC.Context.Id_initStorage);
|
||||
FuncDecl *initStorageDecl = nullptr;
|
||||
for (auto found : lookup) {
|
||||
if (auto foundFunc = dyn_cast<FuncDecl>(found.Decl)) {
|
||||
// Should have the signature `static func initStorage() -> Storage`.
|
||||
// TODO: For out-of-line initialization, could also support
|
||||
// initStorage(Value) -> Storage.
|
||||
if (!foundFunc->isStatic())
|
||||
continue;
|
||||
auto methodTy = foundFunc->getInterfaceType()
|
||||
->castTo<AnyFunctionType>()
|
||||
->getResult();
|
||||
if (!methodTy->isEqual(expectedInitStorageTy))
|
||||
continue;
|
||||
|
||||
if (initStorageDecl) {
|
||||
TC.diagnose(behavior->getLoc(),
|
||||
diag::property_behavior_protocol_reqt_ambiguous,
|
||||
TC.Context.Id_initStorage);
|
||||
TC.diagnose(initStorageDecl->getLoc(),
|
||||
diag::property_behavior_protocol_reqt_here,
|
||||
TC.Context.Id_initStorage);
|
||||
TC.diagnose(foundFunc->getLoc(),
|
||||
diag::property_behavior_protocol_reqt_here,
|
||||
TC.Context.Id_initStorage);
|
||||
break;
|
||||
// We need an initStorage extension method to initialize this storage.
|
||||
auto lookup = TC.lookupMember(dc, behaviorProtoTy,
|
||||
TC.Context.Id_initStorage);
|
||||
FuncDecl *initStorageDecl = nullptr;
|
||||
for (auto found : lookup) {
|
||||
if (auto foundFunc = dyn_cast<FuncDecl>(found.Decl)) {
|
||||
// Should have the signature `static func initStorage() -> Storage`.
|
||||
// TODO: For out-of-line initialization, could also support
|
||||
// initStorage(Value) -> Storage.
|
||||
if (!foundFunc->isStatic())
|
||||
continue;
|
||||
auto methodTy = foundFunc->getInterfaceType()
|
||||
->castTo<AnyFunctionType>()
|
||||
->getResult();
|
||||
if (!methodTy->isEqual(expectedInitStorageTy))
|
||||
continue;
|
||||
|
||||
if (initStorageDecl) {
|
||||
TC.diagnose(behavior->getLoc(),
|
||||
diag::property_behavior_protocol_reqt_ambiguous,
|
||||
TC.Context.Id_initStorage);
|
||||
TC.diagnose(initStorageDecl->getLoc(),
|
||||
diag::property_behavior_protocol_reqt_here,
|
||||
TC.Context.Id_initStorage);
|
||||
TC.diagnose(foundFunc->getLoc(),
|
||||
diag::property_behavior_protocol_reqt_here,
|
||||
TC.Context.Id_initStorage);
|
||||
break;
|
||||
}
|
||||
|
||||
initStorageDecl = foundFunc;
|
||||
}
|
||||
|
||||
initStorageDecl = foundFunc;
|
||||
}
|
||||
}
|
||||
|
||||
if (!initStorageDecl) {
|
||||
TC.diagnose(behavior->getLoc(),
|
||||
diag::property_behavior_protocol_no_initStorage,
|
||||
expectedInitStorageTy);
|
||||
for (auto found : lookup)
|
||||
TC.diagnose(found.Decl->getLoc(),
|
||||
diag::found_candidate);
|
||||
|
||||
if (!initStorageDecl) {
|
||||
TC.diagnose(behavior->getLoc(),
|
||||
diag::property_behavior_protocol_no_initStorage,
|
||||
expectedInitStorageTy);
|
||||
for (auto found : lookup)
|
||||
TC.diagnose(found.Decl->getLoc(),
|
||||
diag::found_candidate);
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Support storage-backed behaviors in non-type contexts.
|
||||
if (!dc->isTypeContext()) {
|
||||
TC.diagnose(behavior->getLoc(),
|
||||
diag::property_behavior_with_feature_not_supported,
|
||||
behaviorProto->getName(), "storage");
|
||||
conformance->setInvalid();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Instantiate the storage next to us in the enclosing scope.
|
||||
TC.completePropertyBehaviorStorage(decl, varReqt,
|
||||
initStorageDecl,
|
||||
behaviorSelf,
|
||||
storageTy,
|
||||
conformance,
|
||||
interfaceSubs,
|
||||
contextSubs);
|
||||
continue;
|
||||
// Handle an initialValue requirement.
|
||||
} else if (varReqt->getName() == TC.Context.Id_initialValue) {
|
||||
requiresInitialValue = true;
|
||||
|
||||
// The requirement should be static, get-only, and have the type of the
|
||||
// Value.
|
||||
// TODO: A "deferred" initialization would be an instance requirement.
|
||||
Type valueTy = GenericTypeParamType::get(0, 0, TC.Context);
|
||||
assert(valueReqt && "no Value associated type?!");
|
||||
valueTy = DependentMemberType::get(valueTy, valueReqt, TC.Context);
|
||||
|
||||
if (!varReqt->isStatic()
|
||||
|| varReqt->isSettable(dc)
|
||||
|| !varReqt->getInterfaceType()->isEqual(valueTy)) {
|
||||
TC.diagnose(behavior->getLoc(),
|
||||
diag::property_behavior_invalid_initialValue_reqt,
|
||||
behaviorProto->getName());
|
||||
TC.diagnose(varReqt->getLoc(),
|
||||
diag::property_behavior_protocol_reqt_here,
|
||||
TC.Context.Id_initialValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The declaration must have an initializer expression.
|
||||
// FIXME: It should have the initializer to itself too, no 'var (a,b)=x'
|
||||
if (!decl->getParentInitializer()) {
|
||||
TC.diagnose(behavior->getLoc(),
|
||||
diag::property_behavior_requires_initialValue,
|
||||
behaviorProto->getName(),
|
||||
decl->getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isa<NamedPattern>(
|
||||
decl->getParentPattern()->getSemanticsProvidingPattern())) {
|
||||
TC.diagnose(decl->getParentInitializer()->getLoc(),
|
||||
diag::property_behavior_requires_unique_initialValue,
|
||||
behaviorProto->getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Support initialValue requirements in non-type contexts.
|
||||
if (!dc->isTypeContext()) {
|
||||
TC.diagnose(behavior->getLoc(),
|
||||
diag::property_behavior_with_feature_not_supported,
|
||||
behaviorProto->getName(), "initial value requirement");
|
||||
conformance->setInvalid();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Wrap the initializer expression in a get-only property to
|
||||
TC.completePropertyBehaviorInitialValue(decl, varReqt,
|
||||
conformance,
|
||||
interfaceSubs,
|
||||
contextSubs);
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Support storage-backed behaviors in non-type contexts.
|
||||
if (!dc->isTypeContext()) {
|
||||
TC.diagnose(behavior->getLoc(),
|
||||
diag::property_behavior_with_storage_not_supported,
|
||||
behaviorProto->getName());
|
||||
conformance->setInvalid();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Instantiate the storage next to us in the enclosing scope.
|
||||
TC.completePropertyBehaviorStorage(decl, storageReqt,
|
||||
initStorageDecl,
|
||||
behaviorSelf,
|
||||
storageTy,
|
||||
conformance,
|
||||
interfaceSubs,
|
||||
contextSubs);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto func = dyn_cast<FuncDecl>(requirement)) {
|
||||
} else if (auto func = dyn_cast<FuncDecl>(requirement)) {
|
||||
// Handle accessors as part of their property.
|
||||
if (func->isAccessor())
|
||||
continue;
|
||||
@@ -2860,6 +2916,16 @@ static void checkVarBehavior(VarDecl *decl, TypeChecker &TC) {
|
||||
unknownRequirement(requirement);
|
||||
}
|
||||
|
||||
// If the property was declared with an initializer, but the behavior didn't
|
||||
// use it, complain.
|
||||
// TODO: The initializer could eventually be consumed by DI-style
|
||||
// initialization.
|
||||
if (!requiresInitialValue && decl->getParentInitializer()) {
|
||||
TC.diagnose(decl->getParentInitializer()->getLoc(),
|
||||
diag::property_behavior_invalid_initializer,
|
||||
behaviorProto->getName());
|
||||
}
|
||||
|
||||
// Bail out if we didn't resolve method witnesses.
|
||||
if (conformance->isInvalid()) {
|
||||
decl->setInvalid();
|
||||
|
||||
@@ -1031,6 +1031,14 @@ public:
|
||||
ArrayRef<Substitution> SelfInterfaceSubs,
|
||||
ArrayRef<Substitution> SelfContextSubs);
|
||||
|
||||
/// Instantiate the initial value implementation for a behavior-backed
|
||||
/// property.
|
||||
void completePropertyBehaviorInitialValue(VarDecl *VD,
|
||||
VarDecl *BehaviorInitialValue,
|
||||
NormalProtocolConformance *BehaviorConformance,
|
||||
ArrayRef<Substitution> SelfInterfaceSubs,
|
||||
ArrayRef<Substitution> SelfContextSubs);
|
||||
|
||||
/// Instantiate the accessor implementations for a behavior-backed
|
||||
/// property.
|
||||
void completePropertyBehaviorAccessors(VarDecl *VD,
|
||||
|
||||
@@ -34,29 +34,91 @@ extension delayedImmutable {
|
||||
}
|
||||
}
|
||||
|
||||
class Foo {
|
||||
var [delayedImmutable] x: Int
|
||||
protocol lazy {
|
||||
associatedtype Value
|
||||
var storage: Value? { get set }
|
||||
static var initialValue: Value { get }
|
||||
}
|
||||
extension lazy {
|
||||
var value: Value {
|
||||
mutating get {
|
||||
if let existing = storage {
|
||||
return existing
|
||||
}
|
||||
let value = Self.initialValue
|
||||
storage = value
|
||||
return value
|
||||
}
|
||||
|
||||
set {
|
||||
storage = newValue
|
||||
}
|
||||
}
|
||||
|
||||
static func initStorage() -> Value? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var tests = TestSuite("DelayedImmutable")
|
||||
var lazyEvaluated = false
|
||||
func evaluateLazy() -> Int {
|
||||
lazyEvaluated = true
|
||||
return 1738
|
||||
}
|
||||
|
||||
tests.test("correct usage") {
|
||||
class Foo {
|
||||
var [delayedImmutable] x: Int
|
||||
var [lazy] y = evaluateLazy()
|
||||
}
|
||||
|
||||
var DelayedImmutable = TestSuite("DelayedImmutable")
|
||||
|
||||
DelayedImmutable.test("correct usage") {
|
||||
let foo = Foo()
|
||||
foo.x = 679
|
||||
expectEqual(foo.x, 679)
|
||||
}
|
||||
|
||||
tests.test("read before initialization") {
|
||||
DelayedImmutable.test("read before initialization") {
|
||||
let foo = Foo()
|
||||
expectCrashLater()
|
||||
_ = foo.x
|
||||
}
|
||||
|
||||
tests.test("write after initialization") {
|
||||
DelayedImmutable.test("write after initialization") {
|
||||
let foo = Foo()
|
||||
foo.x = 679
|
||||
expectCrashLater()
|
||||
foo.x = 680
|
||||
}
|
||||
|
||||
var Lazy = TestSuite("Lazy")
|
||||
|
||||
Lazy.test("usage") {
|
||||
let foo = Foo()
|
||||
|
||||
expectFalse(lazyEvaluated)
|
||||
expectEqual(foo.y, 1738)
|
||||
expectTrue(lazyEvaluated)
|
||||
|
||||
lazyEvaluated = false
|
||||
expectEqual(foo.y, 1738)
|
||||
expectFalse(lazyEvaluated)
|
||||
|
||||
foo.y = 36
|
||||
expectEqual(foo.y, 36)
|
||||
expectFalse(lazyEvaluated)
|
||||
|
||||
let foo2 = Foo()
|
||||
expectFalse(lazyEvaluated)
|
||||
foo2.y = 36
|
||||
expectEqual(foo2.y, 36)
|
||||
expectFalse(lazyEvaluated)
|
||||
|
||||
let foo3 = Foo()
|
||||
expectFalse(lazyEvaluated)
|
||||
expectEqual(foo3.y, 1738)
|
||||
expectTrue(lazyEvaluated)
|
||||
}
|
||||
|
||||
runAllTests()
|
||||
|
||||
@@ -96,7 +96,6 @@ extension withStorage {
|
||||
// TODO: storage behaviors in non-instance context
|
||||
struct S2<T> {
|
||||
var [withStorage] instance: T
|
||||
lazy var ninstance = 0
|
||||
}
|
||||
class C2<T> {
|
||||
var [withStorage] instance: T
|
||||
@@ -118,6 +117,46 @@ func exerciseStorage<T>(inout _ sx: S2<T>, inout _ sy: S2<Int>,
|
||||
cy.instance = zero
|
||||
}
|
||||
|
||||
protocol withInit {
|
||||
associatedtype Value
|
||||
var storage: Value? { get set }
|
||||
static var initialValue: Value { get }
|
||||
}
|
||||
extension withInit {
|
||||
var value: Value {
|
||||
get { }
|
||||
set { }
|
||||
}
|
||||
|
||||
static func initStorage() -> Value? { }
|
||||
}
|
||||
|
||||
// TODO: initialValue behaviors in non-instance context
|
||||
func any<T>() -> T { }
|
||||
|
||||
struct S3<T> {
|
||||
var [withInit] instance: T = any()
|
||||
}
|
||||
class C3<T> {
|
||||
var [withInit] instance: T = any()
|
||||
}
|
||||
|
||||
func exerciseStorage<T>(inout _ sx: S3<T>, inout _ sy: S3<Int>,
|
||||
_ cx: C3<T>, _ cy: C3<Int>,
|
||||
_ z: T) {
|
||||
_ = sx.instance
|
||||
sx.instance = z
|
||||
|
||||
_ = sy.instance
|
||||
sy.instance = zero
|
||||
|
||||
_ = cx.instance
|
||||
cx.instance = z
|
||||
|
||||
_ = cy.instance
|
||||
cy.instance = zero
|
||||
}
|
||||
|
||||
// FIXME: printing sil_witness_tables for behaviors should indicate what
|
||||
// var decl the behavior is attached to
|
||||
// CHECK-LABEL: sil_witness_table private (): behavior module property_behavior
|
||||
|
||||
@@ -188,3 +188,45 @@ struct S<T> {
|
||||
var [getset] testGetset: T
|
||||
static var [getset] testGetset: T
|
||||
}
|
||||
|
||||
protocol initialValue {
|
||||
associatedtype Value
|
||||
static var initialValue: Value { get }
|
||||
}
|
||||
extension initialValue {
|
||||
var value: Value {
|
||||
get { }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
||||
let compoundInitialValue = (0,0)
|
||||
|
||||
struct TestInitialValues {
|
||||
var [initialValue] hasInitialValue: Int = 0
|
||||
var [initialValue] (sharedInitialValue1, sharedInitialValue2): (Int,Int)
|
||||
= compoundInitialValue // expected-error * {{cannot destructure}}
|
||||
var [initialValue] missingInitialValue: Int // expected-error{{requires an initializer}}
|
||||
// TODO: "return expression" message is wrong
|
||||
var [initialValue] invalidInitialValue: Int = 5.5 // expected-error{{cannot convert return expression of type 'Double' to return type 'Int'}}
|
||||
}
|
||||
|
||||
// TODO
|
||||
var [initialValue] globalInitialValue: Int = 0 // expected-error{{not supported}}
|
||||
|
||||
protocol noInitialValue {
|
||||
associatedtype Value
|
||||
}
|
||||
extension noInitialValue {
|
||||
var value: Value {
|
||||
get { }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
||||
var [noInitialValue] hasNoInitialValue: Int
|
||||
var [noInitialValue] hasUnwantedInitialValue: Int = 0 // expected-error{{initializer expression provided, but property behavior 'noInitialValue' does not use it}}
|
||||
var [noInitialValue] (hasUnwantedSharedInitialValue1,
|
||||
hasUnwantedSharedInitialValue2): (Int, Int)
|
||||
= compoundInitialValue // expected-error * {{initializer expression provided, but property behavior 'noInitialValue' does not use it}}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user