Skip to content

Commit bfa690e

Browse files
authored
Merge pull request swiftlang#36403 from slavapestov/remove-synthesized-decls
Sema: Remove SourceFile::SynthesizedDecls
2 parents e87505c + 8464afd commit bfa690e

14 files changed

+87
-152
lines changed

include/swift/AST/SourceFile.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,6 @@ class SourceFile final : public FileUnit {
217217
/// The list of local type declarations in the source file.
218218
llvm::SetVector<TypeDecl *> LocalTypeDecls;
219219

220-
/// A set of synthesized declarations that need to be type checked.
221-
llvm::SmallVector<Decl *, 8> SynthesizedDecls;
222-
223220
/// The list of functions defined in this file whose bodies have yet to be
224221
/// typechecked. They must be held in this list instead of eagerly validated
225222
/// because their bodies may force us to perform semantic checks of arbitrary
@@ -228,11 +225,6 @@ class SourceFile final : public FileUnit {
228225
/// unless the entire conformance has been evaluated.
229226
std::vector<AbstractFunctionDecl *> DelayedFunctions;
230227

231-
/// We might perform type checking on the same source file more than once,
232-
/// if its the main file or a REPL instance, so keep track of the last
233-
/// checked synthesized declaration to avoid duplicating work.
234-
unsigned LastCheckedSynthesizedDecl = 0;
235-
236228
/// A mapping from Objective-C selectors to the methods that have
237229
/// those selectors.
238230
llvm::DenseMap<ObjCSelector, llvm::TinyPtrVector<AbstractFunctionDecl *>>

lib/SILGen/SILGenType.cpp

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,16 +1034,13 @@ class SILGenType : public TypeMemberVisitor<SILGenType> {
10341034
void emitType() {
10351035
SGM.emitLazyConformancesForType(theType);
10361036

1037+
for (Decl *member : theType->getABIMembers())
1038+
visit(member);
1039+
10371040
// Build a vtable if this is a class.
10381041
if (auto theClass = dyn_cast<ClassDecl>(theType)) {
1039-
for (Decl *member : theClass->getABIMembers())
1040-
visit(member);
1041-
10421042
SILGenVTable genVTable(SGM, theClass);
10431043
genVTable.emitVTable();
1044-
} else {
1045-
for (Decl *member : theType->getMembers())
1046-
visit(member);
10471044
}
10481045

10491046
// Build a default witness table if this is a protocol that needs one.
@@ -1063,10 +1060,9 @@ class SILGenType : public TypeMemberVisitor<SILGenType> {
10631060
// are existential and do not have witness tables.
10641061
for (auto *conformance : theType->getLocalConformances(
10651062
ConformanceLookupKind::NonInherited)) {
1066-
if (conformance->isComplete()) {
1067-
if (auto *normal = dyn_cast<NormalProtocolConformance>(conformance))
1068-
SGM.getWitnessTable(normal);
1069-
}
1063+
assert(conformance->isComplete());
1064+
if (auto *normal = dyn_cast<NormalProtocolConformance>(conformance))
1065+
SGM.getWitnessTable(normal);
10701066
}
10711067
}
10721068

@@ -1178,18 +1174,17 @@ class SILGenExtension : public TypeMemberVisitor<SILGenExtension> {
11781174

11791175
/// Emit SIL functions for all the members of the extension.
11801176
void emitExtension(ExtensionDecl *e) {
1181-
for (Decl *member : e->getMembers())
1177+
for (Decl *member : e->getABIMembers())
11821178
visit(member);
11831179

11841180
if (!isa<ProtocolDecl>(e->getExtendedNominal())) {
11851181
// Emit witness tables for protocol conformances introduced by the
11861182
// extension.
11871183
for (auto *conformance : e->getLocalConformances(
11881184
ConformanceLookupKind::All)) {
1189-
if (conformance->isComplete()) {
1190-
if (auto *normal =dyn_cast<NormalProtocolConformance>(conformance))
1191-
SGM.getWitnessTable(normal);
1192-
}
1185+
assert(conformance->isComplete());
1186+
if (auto *normal =dyn_cast<NormalProtocolConformance>(conformance))
1187+
SGM.getWitnessTable(normal);
11931188
}
11941189
}
11951190
}

lib/Sema/CodeSynthesis.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,10 +1312,6 @@ ResolveEffectiveMemberwiseInitRequest::evaluate(Evaluator &evaluator,
13121312
continue;
13131313
storedProperties.push_back(vd);
13141314
}
1315-
// Return false if initializer does not have interface type set. It is not
1316-
// possible to determine whether it is a memberwise initializer.
1317-
if (!initDecl->hasInterfaceType())
1318-
return false;
13191315
auto initDeclType =
13201316
initDecl->getMethodInterfaceType()->getAs<AnyFunctionType>();
13211317
// Return false if initializer does not have a valid interface type.

lib/Sema/DerivedConformanceAdditiveArithmetic.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,12 @@ static ValueDecl *deriveMathOperator(DerivedConformance &derived,
207207
operatorDecl->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/ true);
208208

209209
derived.addMembersToConformanceContext({operatorDecl});
210+
211+
// For the effective memberwise initializer before we force the body,
212+
// so that it becomes part of the emitted ABI members even if we don't
213+
// emit the body.
214+
(void) nominal->getEffectiveMemberwiseInitializer();
215+
210216
return operatorDecl;
211217
}
212218

@@ -311,6 +317,12 @@ static ValueDecl *deriveAdditiveArithmetic_zero(DerivedConformance &derived) {
311317
getterDecl->setBodySynthesizer(deriveBodyAdditiveArithmetic_zero, nullptr);
312318

313319
derived.addMembersToConformanceContext({propDecl, pbDecl});
320+
321+
// For the effective memberwise initializer before we force the body,
322+
// so that it becomes part of the emitted ABI members even if we don't
323+
// emit the body.
324+
(void) nominal->getEffectiveMemberwiseInitializer();
325+
314326
return propDecl;
315327
}
316328

lib/Sema/DerivedConformanceDifferentiable.cpp

Lines changed: 12 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -337,51 +337,6 @@ static ValueDecl *deriveDifferentiable_move(DerivedConformance &derived) {
337337
C.TheEmptyTupleType, {deriveBodyDifferentiable_move, nullptr});
338338
}
339339

340-
/// Pushes all the protocols inherited, directly or transitively, by `decl` to `protos`.
341-
///
342-
/// Precondition: `decl` is a nominal type decl or an extension decl.
343-
void getInheritedProtocols(Decl *decl, SmallPtrSetImpl<ProtocolDecl *> &protos) {
344-
ArrayRef<TypeLoc> inheritedTypeLocs;
345-
if (auto *nominalDecl = dyn_cast<NominalTypeDecl>(decl))
346-
inheritedTypeLocs = nominalDecl->getInherited();
347-
else if (auto *extDecl = dyn_cast<ExtensionDecl>(decl))
348-
inheritedTypeLocs = extDecl->getInherited();
349-
else
350-
llvm_unreachable("conformance is not a nominal or an extension");
351-
352-
std::function<void(Type)> handleInheritedType;
353-
354-
auto handleProto = [&](ProtocolType *proto) -> void {
355-
proto->getDecl()->walkInheritedProtocols([&](ProtocolDecl *p) -> TypeWalker::Action {
356-
protos.insert(p);
357-
return TypeWalker::Action::Continue;
358-
});
359-
};
360-
361-
auto handleProtoComp = [&](ProtocolCompositionType *comp) -> void {
362-
for (auto ty : comp->getMembers())
363-
handleInheritedType(ty);
364-
};
365-
366-
handleInheritedType = [&](Type ty) -> void {
367-
if (auto *proto = ty->getAs<ProtocolType>())
368-
handleProto(proto);
369-
else if (auto *comp = ty->getAs<ProtocolCompositionType>())
370-
handleProtoComp(comp);
371-
};
372-
373-
for (auto loc : inheritedTypeLocs) {
374-
if (loc.getTypeRepr())
375-
handleInheritedType(
376-
TypeResolution::forStructural(cast<DeclContext>(decl), None,
377-
/*unboundTyOpener*/ nullptr,
378-
/*placeholderHandler*/ nullptr)
379-
.resolveType(loc.getTypeRepr()));
380-
else
381-
handleInheritedType(loc.getType());
382-
}
383-
}
384-
385340
/// Return associated `TangentVector` struct for a nominal type, if it exists.
386341
/// If not, synthesize the struct.
387342
static StructDecl *
@@ -409,12 +364,14 @@ getOrSynthesizeTangentVectorStruct(DerivedConformance &derived, Identifier id) {
409364
// Note that, for example, this will always find `AdditiveArithmetic` and `Differentiable` because
410365
// the `Differentiable` protocol itself requires that its `TangentVector` conforms to
411366
// `AdditiveArithmetic` and `Differentiable`.
412-
llvm::SmallPtrSet<ProtocolDecl *, 4> tvDesiredProtos;
413-
llvm::SmallPtrSet<ProtocolDecl *, 4> conformanceInheritedProtos;
414-
getInheritedProtocols(derived.ConformanceDecl, conformanceInheritedProtos);
367+
llvm::SmallSetVector<ProtocolDecl *, 4> tvDesiredProtos;
368+
415369
auto *diffableProto = C.getProtocol(KnownProtocolKind::Differentiable);
416370
auto *tvAssocType = diffableProto->getAssociatedType(C.Id_TangentVector);
417-
for (auto proto : conformanceInheritedProtos) {
371+
372+
auto localProtos = cast<IterableDeclContext>(derived.ConformanceDecl)
373+
->getLocalProtocols();
374+
for (auto proto : localProtos) {
418375
for (auto req : proto->getRequirementSignature()) {
419376
if (req.getKind() != RequirementKind::Conformance)
420377
continue;
@@ -516,19 +473,13 @@ getOrSynthesizeTangentVectorStruct(DerivedConformance &derived, Identifier id) {
516473
auto *tangentEqualsSelfAlias = new (C) TypeAliasDecl(
517474
SourceLoc(), SourceLoc(), C.Id_TangentVector, SourceLoc(),
518475
/*GenericParams*/ nullptr, structDecl);
519-
tangentEqualsSelfAlias->setUnderlyingType(structDecl->getSelfTypeInContext());
520-
tangentEqualsSelfAlias->setAccess(structDecl->getFormalAccess());
476+
tangentEqualsSelfAlias->setUnderlyingType(structDecl->getDeclaredInterfaceType());
477+
tangentEqualsSelfAlias->copyFormalAccessFrom(structDecl,
478+
/*sourceIsParentContext*/ true);
521479
tangentEqualsSelfAlias->setImplicit();
522480
tangentEqualsSelfAlias->setSynthesized();
523481
structDecl->addMember(tangentEqualsSelfAlias);
524482

525-
// If nominal type is `@usableFromInline`, also mark `TangentVector` struct.
526-
if (nominal->getAttrs().hasAttribute<UsableFromInlineAttr>()) {
527-
structDecl->getAttrs().add(new (C) UsableFromInlineAttr(/*implicit*/ true));
528-
tangentEqualsSelfAlias->getAttrs().add(
529-
new (C) UsableFromInlineAttr(/*implicit*/ true));
530-
}
531-
532483
// The implicit memberwise constructor must be explicitly created so that it
533484
// can called in `AdditiveArithmetic` and `Differentiable` methods. Normally,
534485
// the memberwise constructor is synthesized during SILGen, which is too late.
@@ -539,6 +490,9 @@ getOrSynthesizeTangentVectorStruct(DerivedConformance &derived, Identifier id) {
539490
member->setImplicit();
540491

541492
derived.addMembersToConformanceContext({structDecl});
493+
494+
TypeChecker::checkConformancesInContext(structDecl);
495+
542496
return structDecl;
543497
}
544498

lib/Sema/DerivedConformances.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,8 @@ DeclContext *DerivedConformance::getConformanceContext() const {
4343
void DerivedConformance::addMembersToConformanceContext(
4444
ArrayRef<Decl *> children) {
4545
auto IDC = cast<IterableDeclContext>(ConformanceDecl);
46-
auto *SF = ConformanceDecl->getDeclContext()->getParentSourceFile();
47-
for (auto child : children) {
46+
for (auto child : children)
4847
IDC->addMember(child);
49-
if (SF)
50-
SF->SynthesizedDecls.push_back(child);
51-
}
5248
}
5349

5450
Type DerivedConformance::getProtocolType() const {

lib/Sema/TypeChecker.cpp

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -247,28 +247,13 @@ void swift::bindExtensions(ModuleDecl &mod) {
247247

248248
static void typeCheckDelayedFunctions(SourceFile &SF) {
249249
unsigned currentFunctionIdx = 0;
250-
unsigned currentSynthesizedDecl = SF.LastCheckedSynthesizedDecl;
251-
do {
252-
// Type check the body of each of the function in turn. Note that outside
253-
// functions must be visited before nested functions for type-checking to
254-
// work correctly.
255-
for (unsigned n = SF.DelayedFunctions.size(); currentFunctionIdx != n;
256-
++currentFunctionIdx) {
257-
auto *AFD = SF.DelayedFunctions[currentFunctionIdx];
258-
assert(!AFD->getDeclContext()->isLocalContext());
259-
(void)AFD->getTypecheckedBody();
260-
}
261250

262-
// Type check synthesized functions and their bodies.
263-
for (unsigned n = SF.SynthesizedDecls.size();
264-
currentSynthesizedDecl != n;
265-
++currentSynthesizedDecl) {
266-
auto decl = SF.SynthesizedDecls[currentSynthesizedDecl];
267-
TypeChecker::typeCheckDecl(decl);
268-
}
269-
270-
} while (currentFunctionIdx < SF.DelayedFunctions.size() ||
271-
currentSynthesizedDecl < SF.SynthesizedDecls.size());
251+
while (currentFunctionIdx < SF.DelayedFunctions.size()) {
252+
auto *AFD = SF.DelayedFunctions[currentFunctionIdx];
253+
assert(!AFD->getDeclContext()->isLocalContext());
254+
(void) AFD->getTypecheckedBody();
255+
++currentFunctionIdx;
256+
}
272257

273258
SF.DelayedFunctions.clear();
274259
}

test/IRGen/enum_derived.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,6 @@ enum E {
2222
// CHECK: %2 = icmp eq i8 %0, %1
2323
// CHECK: ret i1 %2
2424

25-
// Check for the presence of the hashValue getter, calling Hasher.init() and
26-
// Hasher.finalize().
27-
28-
// CHECK-NORMAL-LABEL:define hidden swiftcc i{{.*}} @"$s12enum_derived1EO9hashValueSivg"(i8 %0)
29-
// CHECK-TESTABLE-LABEL:define{{( dllexport)?}}{{( protected)?}} swiftcc i{{.*}} @"$s12enum_derived1EO9hashValueSivg"(i8 %0)
30-
// CHECK: call swiftcc void @"$ss6HasherV5_seedABSi_tcfC"(%Ts6HasherV* {{.*}})
31-
// CHECK: call swiftcc i{{[0-9]+}} @"$ss6HasherV9_finalizeSiyF"(%Ts6HasherV* {{.*}})
32-
// CHECK: ret i{{[0-9]+}} %{{[0-9]+}}
33-
3425
// Check if the hash(into:) method can be compiled to a simple zext instruction
3526
// followed by a call to Hasher._combine(_:).
3627

@@ -40,6 +31,15 @@ enum E {
4031
// CHECK: tail call swiftcc void @"$ss6HasherV8_combineyySuF"(i{{.*}} [[V]], %Ts6HasherV*
4132
// CHECK: ret void
4233

34+
// Check for the presence of the hashValue getter, calling Hasher.init() and
35+
// Hasher.finalize().
36+
37+
// CHECK-NORMAL-LABEL:define hidden swiftcc i{{.*}} @"$s12enum_derived1EO9hashValueSivg"(i8 %0)
38+
// CHECK-TESTABLE-LABEL:define{{( dllexport)?}}{{( protected)?}} swiftcc i{{.*}} @"$s12enum_derived1EO9hashValueSivg"(i8 %0)
39+
// CHECK: call swiftcc void @"$ss6HasherV5_seedABSi_tcfC"(%Ts6HasherV* {{.*}})
40+
// CHECK: call swiftcc i{{[0-9]+}} @"$ss6HasherV9_finalizeSiyF"(%Ts6HasherV* {{.*}})
41+
// CHECK: ret i{{[0-9]+}} %{{[0-9]+}}
42+
4343
// Derived conformances from extensions
4444
// The actual enums are in Inputs/def_enum.swift
4545

test/SILGen/property_wrappers.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,6 @@ func forceHasMemberwiseInit() {
8484
// CHECK: function_ref @$sSb22_builtinBooleanLiteralSbBi1__tcfC : $@convention(method) (Builtin.Int1, @thin Bool.Type) -> Bool
8585
// CHECK: return {{%.*}} : $Bool
8686

87-
// default argument 0 of HasMemberwiseInit.init(x:y:z:)
88-
// CHECK: sil hidden [ossa] @$s17property_wrappers17HasMemberwiseInitV1x1y1zACyxGAA7WrapperVySbG_xAA0F16WithInitialValueVySiGtcfcfA_ : $@convention(thin) <T where T : DefaultInit> () -> Wrapper<Bool>
89-
90-
// default argument 1 of HasMemberwiseInit.init(x:y:z:)
91-
// CHECK: sil hidden [ossa] @$s17property_wrappers17HasMemberwiseInitV1x1y1zACyxGAA7WrapperVySbG_xAA0F16WithInitialValueVySiGtcfcfA0_ : $@convention(thin) <T where T : DefaultInit> () -> @out T {
92-
93-
// default argument 2 of HasMemberwiseInit.init(x:y:z:)
94-
// CHECK: sil hidden [ossa] @$s17property_wrappers17HasMemberwiseInitV1x1y1zACyxGAA7WrapperVySbG_xAA0F16WithInitialValueVySiGtcfcfA1_ : $@convention(thin) <T where T : DefaultInit> () -> WrapperWithInitialValue<Int> {
95-
96-
9787
// HasMemberwiseInit.init()
9888
// CHECK-LABEL: sil hidden [ossa] @$s17property_wrappers17HasMemberwiseInitVACyxGycfC : $@convention(method) <T where T : DefaultInit> (@thin HasMemberwiseInit<T>.Type) -> @out HasMemberwiseInit<T> {
9989

@@ -119,6 +109,17 @@ func forceHasMemberwiseInit() {
119109

120110
// CHECK: return
121111

112+
113+
// default argument 0 of HasMemberwiseInit.init(x:y:z:)
114+
// CHECK-LABEL: sil hidden [ossa] @$s17property_wrappers17HasMemberwiseInitV1x1y1zACyxGAA7WrapperVySbG_xAA0F16WithInitialValueVySiGtcfcfA_ : $@convention(thin) <T where T : DefaultInit> () -> Wrapper<Bool>
115+
116+
// default argument 1 of HasMemberwiseInit.init(x:y:z:)
117+
// CHECK-LABEL: sil hidden [ossa] @$s17property_wrappers17HasMemberwiseInitV1x1y1zACyxGAA7WrapperVySbG_xAA0F16WithInitialValueVySiGtcfcfA0_ : $@convention(thin) <T where T : DefaultInit> () -> @out T {
118+
119+
// default argument 2 of HasMemberwiseInit.init(x:y:z:)
120+
// CHECK-LABEL: sil hidden [ossa] @$s17property_wrappers17HasMemberwiseInitV1x1y1zACyxGAA7WrapperVySbG_xAA0F16WithInitialValueVySiGtcfcfA1_ : $@convention(thin) <T where T : DefaultInit> () -> WrapperWithInitialValue<Int> {
121+
122+
122123
// Non-generic struct with private property wrapper
123124
struct HasMemberwiseInitWithPrivateWrapper {
124125
@WrapperWithInitialValue
@@ -412,7 +413,7 @@ struct CompositionMembers {
412413
// CHECK-LABEL: sil hidden [transparent] [ossa] @$s17property_wrappers18CompositionMembersV3_p233_{{.*}}8WrapperAVyAA0N1BVyAA0N1CVySSGGGvpfi : $@convention(thin) () -> @owned Optional<String> {
413414
// CHECK: %0 = string_literal utf8 "Hello"
414415

415-
// CHECK-LABEL: sil hidden [ossa] @$s17property_wrappers18CompositionMembersV2p12p2ACSiSg_SSSgtcfC : $@convention(method) (Optional<Int>, @owned Optional<String>, @thin CompositionMembers.Type) -> @owned CompositionMembers
416+
// CHECK-LABEL: sil hidden [ossa] @$s17property_wrappers18CompositionMembersV2p12p2ACSiSg_SSSgtcfcfA0_ : $@convention(thin) () -> @owned Optional<String> {
416417
// CHECK: s17property_wrappers18CompositionMembersV3_p233_{{.*}}8WrapperAVyAA0N1BVyAA0N1CVySSGGGvpfi
417418

418419
}

test/SILGen/synthesized_conformance_class.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ class Nonfinal<T> {
5555
// Make sure that CodingKeys members are actually emitted.
5656

5757
// CHECK-LABEL: sil private [ossa] @$s29synthesized_conformance_class5FinalC10CodingKeys{{.*}}21__derived_enum_equalsySbAFyx_G_AHtFZ : $@convention(method) <T> (Final<T>.CodingKeys, Final<T>.CodingKeys, @thin Final<T>.CodingKeys.Type) -> Bool {
58-
// CHECK-LABEL: sil private [ossa] @$s29synthesized_conformance_class5FinalC10CodingKeys{{.*}}9hashValueSivg : $@convention(method) <T> (Final<T>.CodingKeys) -> Int {
5958
// CHECK-LABEL: sil private [ossa] @$s29synthesized_conformance_class5FinalC10CodingKeys{{.*}}4hash4intoys6HasherVz_tF : $@convention(method) <T> (@inout Hasher, Final<T>.CodingKeys) -> () {
60-
// CHECK-LABEL: sil private [ossa] @$s29synthesized_conformance_class5FinalC10CodingKeys{{.*}}11stringValueSSvg : $@convention(method) <T> (Final<T>.CodingKeys) -> @owned String {
6159
// CHECK-LABEL: sil private [ossa] @$s29synthesized_conformance_class5FinalC10CodingKeys{{.*}}11stringValueAFyx_GSgSS_tcfC : $@convention(method) <T> (@owned String, @thin Final<T>.CodingKeys.Type) -> Optional<Final<T>.CodingKeys> {
62-
// CHECK-LABEL: sil private [ossa] @$s29synthesized_conformance_class5FinalC10CodingKeys{{.*}}8intValueSiSgvg : $@convention(method) <T> (Final<T>.CodingKeys) -> Optional<Int> {
6360
// CHECK-LABEL: sil private [ossa] @$s29synthesized_conformance_class5FinalC10CodingKeys{{.*}}8intValueAFyx_GSgSi_tcfC : $@convention(method) <T> (Int, @thin Final<T>.CodingKeys.Type) -> Optional<Final<T>.CodingKeys> {
61+
// CHECK-LABEL: sil private [ossa] @$s29synthesized_conformance_class5FinalC10CodingKeys{{.*}}9hashValueSivg : $@convention(method) <T> (Final<T>.CodingKeys) -> Int {
62+
// CHECK-LABEL: sil private [ossa] @$s29synthesized_conformance_class5FinalC10CodingKeys{{.*}}8intValueSiSgvg : $@convention(method) <T> (Final<T>.CodingKeys) -> Optional<Int> {
63+
// CHECK-LABEL: sil private [ossa] @$s29synthesized_conformance_class5FinalC10CodingKeys{{.*}}11stringValueSSvg : $@convention(method) <T> (Final<T>.CodingKeys) -> @owned String {
6464

6565
extension Final: Encodable where T: Encodable {}
6666
// CHECK-LABEL: // Final<A>.encode(to:)

0 commit comments

Comments
 (0)