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:
Joe Groff
2016-02-18 11:25:30 -08:00
parent 12b8a92e9d
commit c6bfac7281
8 changed files with 412 additions and 91 deletions

View File

@@ -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

View File

@@ -41,6 +41,7 @@ IDENTIFIER(generate)
IDENTIFIER(Generator)
IDENTIFIER(hashValue)
IDENTIFIER(init)
IDENTIFIER(initialValue)
IDENTIFIER(initStorage)
IDENTIFIER(load)
IDENTIFIER(next)

View File

@@ -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());
}

View File

@@ -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();

View File

@@ -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,

View File

@@ -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()

View File

@@ -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

View File

@@ -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}}