Skip to content

Commit 81c0a0e

Browse files
committed
[Property wrappers] Allow init(wrappedValue:) initializers with defaulted args
Generalize the type checking when searching for "wrappedValue" initializers to also allow initializers that have other, defaulted parameters as well.
1 parent 5431efa commit 81c0a0e

File tree

7 files changed

+174
-105
lines changed

7 files changed

+174
-105
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4528,8 +4528,6 @@ ERROR(property_wrapper_wrong_initial_value_init, none,
45284528
(DeclName, Type, Type))
45294529
ERROR(property_wrapper_failable_init, none,
45304530
"%0 cannot be failable", (DeclName))
4531-
ERROR(property_wrapper_ambiguous_initial_value_init, none,
4532-
"property wrapper type %0 has multiple initial-value initializers", (Type))
45334531
ERROR(property_wrapper_ambiguous_default_value_init, none,
45344532
"property wrapper type %0 has multiple default-value initializers", (Type))
45354533
ERROR(property_wrapper_type_requirement_not_accessible,none,

include/swift/AST/PropertyWrappers.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ struct PropertyWrapperTypeInfo {
3535
/// directed.
3636
VarDecl *valueVar = nullptr;
3737

38-
/// The initializer init(wrappedValue:) that will be called when the
38+
/// Whether there is an init(wrappedValue:) that will be called when the
3939
/// initializing the property wrapper type from a value of the property type.
40-
///
41-
/// This initializer is optional, but if present will be used for the `=`
42-
/// initialization syntax.
43-
ConstructorDecl *wrappedValueInit = nullptr;
40+
enum {
41+
NoWrappedValueInit = 0,
42+
HasWrappedValueInit,
43+
HasInitialValueInit
44+
} wrappedValueInit = NoWrappedValueInit;
4445

4546
/// The initializer `init()` that will be called to default-initialize a
4647
/// value with an attached property wrapper.

lib/AST/TypeCheckRequests.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -564,11 +564,6 @@ void swift::simple_display(
564564
out << propertyWrapper.valueVar->printRef();
565565
else
566566
out << "null";
567-
out << ", ";
568-
if (propertyWrapper.wrappedValueInit)
569-
out << propertyWrapper.wrappedValueInit->printRef();
570-
else
571-
out << "null";
572567
out << " }";
573568
}
574569

lib/Sema/TypeCheckPropertyWrapper.cpp

Lines changed: 124 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -81,88 +81,121 @@ static VarDecl *findValueProperty(ASTContext &ctx, NominalTypeDecl *nominal,
8181

8282
/// Determine whether we have a suitable wrapped-value initializer within
8383
/// a property wrapper type.
84-
static ConstructorDecl *findInitialValueInit(ASTContext &ctx,
85-
NominalTypeDecl *nominal,
86-
VarDecl *valueVar,
87-
Identifier argumentLabel) {
84+
static ConstructorDecl *findInitialValueInit(
85+
ASTContext &ctx,
86+
NominalTypeDecl *nominal,
87+
VarDecl *valueVar,
88+
Identifier argumentLabel) {
89+
// Retrieve the type of the 'value' property.
90+
Type valueVarType = valueVar->getValueInterfaceType();
91+
92+
enum class NonViableReason {
93+
Failable,
94+
ParameterTypeMismatch,
95+
Inaccessible,
96+
};
97+
SmallVector<std::tuple<ConstructorDecl*, NonViableReason, Type>, 2> nonviable;
8898
SmallVector<ConstructorDecl *, 2> initialValueInitializers;
89-
DeclName initName(ctx, DeclBaseName::createConstructor(), {argumentLabel});
99+
90100
SmallVector<ValueDecl *, 2> decls;
91-
nominal->lookupQualified(nominal, initName, NL_QualifiedDefault, decls);
101+
nominal->lookupQualified(nominal, DeclBaseName::createConstructor(),
102+
NL_QualifiedDefault, decls);
92103
for (const auto &decl : decls) {
93104
auto init = dyn_cast<ConstructorDecl>(decl);
94-
if (!init || init->getDeclContext() != nominal)
105+
if (!init || init->getDeclContext() != nominal || init->getGenericParams())
95106
continue;
96107

97-
initialValueInitializers.push_back(init);
98-
}
99-
100-
switch (initialValueInitializers.size()) {
101-
case 0:
102-
return nullptr;
108+
// Check whether every parameter meets one of the following criteria:
109+
// (1) The parameter has a default argument, or
110+
// (2) The parameter has the given argument label.
111+
ParamDecl *wrappedValueParam = nullptr;
112+
for (auto param : *init->getParameters()) {
113+
// Recognize the first parameter with the requested argument label.
114+
if (param->getArgumentName() == argumentLabel && !wrappedValueParam) {
115+
wrappedValueParam = param;
116+
continue;
117+
}
103118

104-
case 1:
105-
break;
119+
if (param->getDefaultArgumentKind() != DefaultArgumentKind::None)
120+
continue;
106121

107-
default:
108-
// Diagnose ambiguous initializers.
109-
nominal->diagnose(diag::property_wrapper_ambiguous_initial_value_init,
110-
nominal->getDeclaredType());
111-
for (auto init : initialValueInitializers) {
112-
init->diagnose(diag::kind_declname_declared_here,
113-
init->getDescriptiveKind(), init->getFullName());
122+
// Forget we had a match.
123+
wrappedValueParam = nullptr;
124+
break;
114125
}
115-
return nullptr;
116-
}
117126

118-
// The initializer must be as accessible as the nominal type.
119-
auto init = initialValueInitializers.front();
120-
if (init->getFormalAccess() < nominal->getFormalAccess()) {
121-
init->diagnose(diag::property_wrapper_type_requirement_not_accessible,
122-
init->getFormalAccess(), init->getDescriptiveKind(),
123-
init->getFullName(), nominal->getDeclaredType(),
124-
nominal->getFormalAccess());
125-
return nullptr;
126-
}
127+
if (!wrappedValueParam)
128+
continue;
127129

128-
// Retrieve the type of the 'value' property.
129-
Type valueVarType = valueVar->getValueInterfaceType();
130+
// Failable initializers cannot be used.
131+
if (init->isFailable()) {
132+
nonviable.push_back(
133+
std::make_tuple(init, NonViableReason::Failable, Type()));
134+
continue;
135+
}
130136

131-
// Retrieve the parameter type of the initializer.
132-
Type paramType;
133-
if (auto *curriedInitType =
134-
init->getInterfaceType()->getAs<AnyFunctionType>()) {
135-
if (auto *initType =
136-
curriedInitType->getResult()->getAs<AnyFunctionType>()) {
137-
if (initType->getParams().size() == 1) {
138-
const auto &param = initType->getParams()[0];
139-
if (!param.isInOut() && !param.isVariadic()) {
140-
paramType = param.getPlainType();
141-
if (param.isAutoClosure()) {
142-
if (auto *fnType = paramType->getAs<FunctionType>())
143-
paramType = fnType->getResult();
144-
}
145-
}
137+
// Check accessibility.
138+
if (init->getFormalAccess() < nominal->getFormalAccess()) {
139+
nonviable.push_back(
140+
std::make_tuple(init, NonViableReason::Inaccessible, Type()));
141+
continue;
142+
}
143+
144+
Type paramType;
145+
if (!wrappedValueParam->isInOut() && !wrappedValueParam->isVariadic()) {
146+
paramType = wrappedValueParam->getInterfaceType();
147+
if (wrappedValueParam->isAutoClosure()) {
148+
if (auto *fnType = paramType->getAs<FunctionType>())
149+
paramType = fnType->getResult();
146150
}
147151
}
148-
}
152+
153+
if (!paramType)
154+
continue;
149155

150-
// The parameter type must be the same as the type of `valueVar` or an
151-
// autoclosure thereof.
152-
if (!paramType->isEqual(valueVarType)) {
153-
init->diagnose(diag::property_wrapper_wrong_initial_value_init, initName,
154-
paramType, valueVarType);
155-
valueVar->diagnose(diag::decl_declared_here, valueVar->getFullName());
156-
return nullptr;
156+
// The parameter type must be the same as the type of `valueVar` or an
157+
// autoclosure thereof.
158+
if (!paramType->isEqual(valueVarType)) {
159+
nonviable.push_back(
160+
std::make_tuple(init, NonViableReason::ParameterTypeMismatch,
161+
paramType));
162+
continue;
163+
}
164+
165+
// Check the type
166+
initialValueInitializers.push_back(init);
157167
}
158168

159-
// The initializer must not be failable.
160-
if (init->isFailable()) {
161-
init->diagnose(diag::property_wrapper_failable_init, initName);
162-
return nullptr;
169+
// If we found some nonviable candidates but no viable ones, complain.
170+
if (initialValueInitializers.empty() && !nonviable.empty()) {
171+
for (const auto &candidate : nonviable) {
172+
auto init = std::get<0>(candidate);
173+
auto reason = std::get<1>(candidate);
174+
auto paramType = std::get<2>(candidate);
175+
switch (reason) {
176+
case NonViableReason::Failable:
177+
init->diagnose(diag::property_wrapper_failable_init,
178+
init->getFullName());
179+
break;
180+
181+
case NonViableReason::Inaccessible:
182+
init->diagnose(diag::property_wrapper_type_requirement_not_accessible,
183+
init->getFormalAccess(), init->getDescriptiveKind(),
184+
init->getFullName(), nominal->getDeclaredType(),
185+
nominal->getFormalAccess());
186+
break;
187+
188+
case NonViableReason::ParameterTypeMismatch:
189+
init->diagnose(diag::property_wrapper_wrong_initial_value_init,
190+
init->getFullName(), paramType, valueVarType);
191+
valueVar->diagnose(diag::decl_declared_here, valueVar->getFullName());
192+
break;
193+
}
194+
}
163195
}
164196

165-
return init;
197+
return initialValueInitializers.empty() ? nullptr
198+
: initialValueInitializers.front();
166199
}
167200

168201
/// Determine whether we have a suitable init() within a property
@@ -302,22 +335,23 @@ PropertyWrapperTypeInfoRequest::evaluate(
302335

303336
PropertyWrapperTypeInfo result;
304337
result.valueVar = valueVar;
305-
result.wrappedValueInit =
306-
findInitialValueInit(ctx, nominal, valueVar, ctx.Id_wrappedValue);
307-
308-
if (!result.wrappedValueInit) {
309-
// Look for the older name init(initialValue:).
310-
result.wrappedValueInit =
311-
findInitialValueInit(ctx, nominal, valueVar, ctx.Id_initialValue);
312-
if (result.wrappedValueInit &&
313-
result.wrappedValueInit->getLoc().isValid()) {
314-
auto diag = result.wrappedValueInit->diagnose(
315-
diag::property_wrapper_init_initialValue);
316-
auto param = result.wrappedValueInit->getParameters()->get(0);
317-
if (param->getArgumentNameLoc().isValid())
318-
diag.fixItReplace(param->getArgumentNameLoc(), "wrappedValue");
319-
else
320-
diag.fixItInsert(param->getLoc(), "wrappedValue ");
338+
if (findInitialValueInit(ctx, nominal, valueVar, ctx.Id_wrappedValue))
339+
result.wrappedValueInit = PropertyWrapperTypeInfo::HasWrappedValueInit;
340+
else if (auto init = findInitialValueInit(
341+
ctx, nominal, valueVar, ctx.Id_initialValue)) {
342+
result.wrappedValueInit = PropertyWrapperTypeInfo::HasInitialValueInit;
343+
344+
if (init->getLoc().isValid()) {
345+
auto diag = init->diagnose(diag::property_wrapper_init_initialValue);
346+
for (auto param : *init->getParameters()) {
347+
if (param->getArgumentName() == ctx.Id_initialValue) {
348+
if (param->getArgumentNameLoc().isValid())
349+
diag.fixItReplace(param->getArgumentNameLoc(), "wrappedValue");
350+
else
351+
diag.fixItInsert(param->getLoc(), "wrappedValue ");
352+
break;
353+
}
354+
}
321355
}
322356
}
323357

@@ -647,12 +681,18 @@ Expr *swift::buildPropertyWrapperInitialValueCall(
647681
// call `init(wrappedValue:)` directly.
648682
auto attr = wrapperAttrs[i];
649683
if (!attr->getArg() || ignoreAttributeArgs) {
650-
Identifier argName = ctx.Id_wrappedValue;
651-
if (auto init
652-
= var->getAttachedPropertyWrapperTypeInfo(i).wrappedValueInit) {
653-
argName = init->getFullName().getArgumentNames()[0];
684+
Identifier argName;
685+
switch (var->getAttachedPropertyWrapperTypeInfo(i).wrappedValueInit) {
686+
case PropertyWrapperTypeInfo::HasInitialValueInit:
687+
argName = ctx.Id_initialValue;
688+
break;
689+
690+
case PropertyWrapperTypeInfo::HasWrappedValueInit:
691+
case PropertyWrapperTypeInfo::NoWrappedValueInit:
692+
argName = ctx.Id_wrappedValue;
693+
break;
654694
}
655-
695+
656696
auto endLoc = initializer->getEndLoc();
657697
if (endLoc.isInvalid() && startLoc.isValid())
658698
endLoc = wrapperAttrs[i]->getTypeLoc().getSourceRange().End;

test/SILGen/property_wrappers.swift

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,13 +453,48 @@ public class TestClass<T> {
453453
@WrapperWithInitialValue var value: T
454454

455455
// CHECK-LABEL: sil hidden [ossa] @$s17property_wrappers9TestClassC5value8protocolACyxGx_qd__tcAA0C8ProtocolRd__lufc
456-
// CHECK: metatype $@thin WrapperWithInitialValue<T>.Type
457-
// CHECK: function_ref @$s17property_wrappers23WrapperWithInitialValueV07wrappedF0ACyxGx_tcfCTc
456+
// CHECK: [[BACKING_INIT:%.*]] = function_ref @$s17property_wrappers9TestClassC5valuexvpfP : $@convention(thin) <τ_0_0> (@in τ_0_0) -> @out WrapperWithInitialValue<τ_0_0>
457+
// CHECK-NEXT: partial_apply [callee_guaranteed] [[BACKING_INIT]]<T>()
458458
init<U: TestProtocol>(value: T, protocol: U) {
459459
self.value = value
460460
}
461461
}
462462

463+
// Composition with wrappedValue initializers that have default values.
464+
@propertyWrapper
465+
struct Outer<Value> {
466+
var wrappedValue: Value
467+
468+
init(a: Int = 17, wrappedValue: Value, s: String = "hello") {
469+
self.wrappedValue = wrappedValue
470+
}
471+
}
472+
473+
474+
@propertyWrapper
475+
struct Inner<Value> {
476+
var wrappedValue: Value
477+
478+
init(wrappedValue: @autoclosure @escaping () -> Value, d: Double = 3.14159) {
479+
self.wrappedValue = wrappedValue()
480+
}
481+
}
482+
483+
struct ComposedInit {
484+
@Outer @Inner var value: Int
485+
486+
// CHECK-LABEL: sil hidden [ossa] @$s17property_wrappers12ComposedInitV5valueSivpfP : $@convention(thin) (Int) -> Outer<Inner<Int>> {
487+
// CHECK: function_ref @$s17property_wrappers12ComposedInitV6_value33_F728088E0028E14D18C6A10CF68512E8LLAA5OuterVyAA5InnerVySiGGvpfiSiycfu_
488+
// CHECK: function_ref @$s17property_wrappers5InnerV12wrappedValue1dACyxGxyXA_SdtcfcfA0_
489+
// CHECK: function_ref @$s17property_wrappers5InnerV12wrappedValue1dACyxGxyXA_SdtcfC
490+
// CHECK: function_ref @$s17property_wrappers5OuterV1a12wrappedValue1sACyxGSi_xSStcfcfA_
491+
// CHECK: function_ref @$s17property_wrappers5OuterV1a12wrappedValue1sACyxGSi_xSStcfcfA1_
492+
// CHECK: function_ref @$s17property_wrappers5OuterV1a12wrappedValue1sACyxGSi_xSStcfC
493+
init() {
494+
self.value = 17
495+
}
496+
}
497+
463498

464499
// CHECK-LABEL: sil_vtable ClassUsingWrapper {
465500
// CHECK-NEXT: #ClassUsingWrapper.x!getter.1: (ClassUsingWrapper) -> () -> Int : @$s17property_wrappers17ClassUsingWrapperC1xSivg // ClassUsingWrapper.x.getter

test/SILOptimizer/di_property_wrappers.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,8 @@ struct Wrapper2<T> {
388388
}
389389
}
390390

391-
init(wrappedValue initialValue: T) {
392-
print(" .. secondInit \(initialValue)")
391+
init(before: Int = -10, wrappedValue initialValue: T, after: String = "end") {
392+
print(" .. secondInit \(before), \(initialValue), \(after)")
393393
self.wrappedValue = initialValue
394394
}
395395
}
@@ -407,7 +407,7 @@ func testComposed() {
407407
print("\n## Composed")
408408
_ = HasComposed()
409409

410-
// CHECK-NEXT: .. secondInit 17
410+
// CHECK-NEXT: .. secondInit -10, 17, end
411411
// CHECK-NEXT: .. init Wrapper2<Int>(wrappedValue: 17)
412412
}
413413

test/decl/var/property_wrappers.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,13 @@ struct InitialValueTypeMismatch<Value> {
9999
}
100100

101101
@propertyWrapper
102-
struct MultipleInitialValues<Value> { // expected-error{{property wrapper type 'MultipleInitialValues' has multiple initial-value initializers}}
103-
var wrappedValue: Value? = nil
102+
struct MultipleInitialValues<Value> {
103+
var wrappedValue: Value? = nil // expected-note 2{{'wrappedValue' declared here}}
104104

105-
init(wrappedValue initialValue: Int) { // expected-note{{initializer 'init(wrappedValue:)' declared here}}
105+
init(wrappedValue initialValue: Int) { // expected-error{{'init(wrappedValue:)' parameter type ('Int') must be the same as its 'wrappedValue' property type ('Value?') or an @autoclosure thereof}}
106106
}
107107

108-
init(wrappedValue initialValue: Double) { // expected-note{{initializer 'init(wrappedValue:)' declared here}}
108+
init(wrappedValue initialValue: Double) { // expected-error{{'init(wrappedValue:)' parameter type ('Double') must be the same as its 'wrappedValue' property type ('Value?') or an @autoclosure thereof}}
109109
}
110110
}
111111

0 commit comments

Comments
 (0)