Skip to content

Commit 0cfda87

Browse files
committed
Suggest stub stored properties where possible
When generating a stub fix-it for a protocol conformance or implementation extension, Swift will now evaluate whether the context allows the declaration of stored properties and, if so, will suggest one. It will also use the `let` keyword instead of `var` if the property has no setter.
1 parent 93188f8 commit 0cfda87

11 files changed

+66
-23
lines changed

include/swift/AST/PrintOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ struct PrintOptions {
143143
/// Whether to print *any* accessors on properties.
144144
bool PrintPropertyAccessors = true;
145145

146+
/// Use \c let for a read-only computed property.
147+
bool InferPropertyIntroducerFromAccessors = false;
148+
146149
/// Whether to print *any* accessors on subscript.
147150
bool PrintSubscriptAccessors = true;
148151

lib/AST/ASTPrinter.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3940,11 +3940,18 @@ void PrintAST::visitVarDecl(VarDecl *decl) {
39403940
if (decl->isStatic() && Options.PrintStaticKeyword)
39413941
printStaticKeyword(decl->getCorrectStaticSpelling());
39423942
if (decl->getKind() == DeclKind::Var || Options.PrintParameterSpecifiers) {
3943+
// If InferPropertyIntroducerFromAccessors is set, turn all read-only
3944+
// properties to `let`.
3945+
auto introducer = decl->getIntroducer();
3946+
if (Options.InferPropertyIntroducerFromAccessors &&
3947+
!cast<VarDecl>(decl)->isSettable(nullptr))
3948+
introducer = VarDecl::Introducer::Let;
3949+
39433950
// Map all non-let specifiers to 'var'. This is not correct, but
39443951
// SourceKit relies on this for info about parameter decls.
39453952

39463953
Printer.printIntroducerKeyword(
3947-
decl->getIntroducer() == VarDecl::Introducer::Let ? "let" : "var",
3954+
introducer == VarDecl::Introducer::Let ? "let" : "var",
39483955
Options, " ");
39493956
}
39503957
printContextIfNeeded(decl);

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3706,7 +3706,10 @@ bool swift::
37063706
printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
37073707
Type AdopterTy, SourceLoc TypeLoc, raw_ostream &OS,
37083708
bool withExplicitObjCAttr) {
3709-
if (isa<ConstructorDecl>(Requirement)) {
3709+
// We sometimes use this for @implementation extensions too.
3710+
bool forProtocol = isa<ProtocolDecl>(Requirement->getDeclContext());
3711+
3712+
if (isa<ConstructorDecl>(Requirement) && forProtocol) {
37103713
if (auto CD = Adopter->getSelfClassDecl()) {
37113714
if (!CD->isSemanticallyFinal() && isa<ExtensionDecl>(Adopter)) {
37123715
// In this case, user should mark class as 'final' or define
@@ -3774,7 +3777,7 @@ printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
37743777

37753778
Printer << "\n";
37763779
} else {
3777-
if (isa<ConstructorDecl>(Requirement)) {
3780+
if (isa<ConstructorDecl>(Requirement) && forProtocol) {
37783781
if (auto CD = Adopter->getSelfClassDecl()) {
37793782
if (!CD->isFinal()) {
37803783
Printer << "required ";
@@ -3809,9 +3812,12 @@ printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
38093812
};
38103813
Options.setBaseType(AdopterTy);
38113814
Options.CurrentModule = Adopter->getParentModule();
3812-
if (isa<NominalTypeDecl>(Adopter)) {
3813-
// Create a variable declaration instead of a computed property in
3814-
// nominal types...
3815+
3816+
// Can the conforming declaration declare a stored property?
3817+
auto ImplementedAdopter = Adopter->getImplementedObjCContext();
3818+
if (isa<NominalTypeDecl>(ImplementedAdopter) &&
3819+
(!isa<EnumDecl>(ImplementedAdopter) || Requirement->isStatic())) {
3820+
// Create a variable declaration instead of a computed property...
38153821
Options.PrintPropertyAccessors = false;
38163822

38173823
// ...but a non-mutating setter requirement will force us into a
@@ -3822,6 +3828,11 @@ printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
38223828
if (const auto Set = VD->getOpaqueAccessor(AccessorKind::Set))
38233829
if (Set->getAttrs().hasAttribute<NonMutatingAttr>())
38243830
Options.PrintPropertyAccessors = true;
3831+
3832+
// If we're not printing the accessors, make them affect the introducer
3833+
// instead.
3834+
Options.InferPropertyIntroducerFromAccessors =
3835+
!Options.PrintPropertyAccessors;
38253836
}
38263837
Requirement->print(Printer, Options);
38273838
Printer << "\n";

test/Sema/fixits-derived-conformances.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ extension GenericStruct: @retroactive Equatable { }
1717

1818
extension Enum: @retroactive CaseIterable { }
1919
// expected-error@-1 {{extension outside of file declaring enum 'Enum' prevents automatic synthesis of 'allCases' for protocol 'CaseIterable'}}
20-
// expected-note@-2 {{add stubs for conformance}}{{44-44=\n public static var allCases: [Enum]\n}}
20+
// expected-note@-2 {{add stubs for conformance}}{{44-44=\n public static let allCases: [Enum]\n}}
2121

test/decl/ext/Inputs/objc_implementation.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@
8585
@property int categoryPropertyFromHeader2;
8686
@property int categoryPropertyFromHeader3;
8787
@property int categoryPropertyFromHeader4;
88+
@property int categoryPropertyFromHeader5;
89+
90+
@property (readonly) int categoryReadonlyPropertyFromHeader1;
91+
8892

8993
@end
9094

test/decl/ext/objc_implementation.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ protocol EmptySwiftProto {}
1717
// FIXME: give better diagnostic expected-note@-8 {{missing instance method 'method(fromHeader3:)'}} {{none}}
1818
// expected-note@-9 {{missing instance method 'extensionMethod(fromHeader2:)'}} {{none}}
1919
// expected-note@-10 {{missing property 'readonlyPropertyFromHeader7'}}
20-
// expected-note@-11 {{add stubs for missing '@implementation' requirements}} {{77-77=\n @objc(methodFromHeader3:)\n open func method(fromHeader3 param: Int32) {\n <#code#>\n \}\n\n @objc(methodFromHeader4:)\n open func method(fromHeader4 param: Int32) {\n <#code#>\n \}\n\n @objc(propertyFromHeader7)\n open var propertyFromHeader7: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(propertyFromHeader8)\n open var propertyFromHeader8: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(propertyFromHeader9)\n open var propertyFromHeader9: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(readonlyPropertyFromHeader7)\n open var readonlyPropertyFromHeader7: Int32 {\n <#code#>\n \}\n\n @objc(extensionMethodFromHeader2:)\n open func extensionMethod(fromHeader2 param: Int32) {\n <#code#>\n \}\n}}
20+
// expected-note@-11 {{add stubs for missing '@implementation' requirements}} {{77-77=\n @objc(methodFromHeader3:)\n open func method(fromHeader3 param: Int32) {\n <#code#>\n \}\n\n @objc(methodFromHeader4:)\n open func method(fromHeader4 param: Int32) {\n <#code#>\n \}\n\n @objc(propertyFromHeader7)\n open var propertyFromHeader7: Int32\n\n @objc(propertyFromHeader8)\n open var propertyFromHeader8: Int32\n\n @objc(propertyFromHeader9)\n open var propertyFromHeader9: Int32\n\n @objc(readonlyPropertyFromHeader7)\n open let readonlyPropertyFromHeader7: Int32\n\n @objc(extensionMethodFromHeader2:)\n open func extensionMethod(fromHeader2 param: Int32) {\n <#code#>\n \}\n}}
2121

2222
func method(fromHeader1: CInt) {
2323
// OK, provides an implementation for the header's method.
@@ -241,7 +241,9 @@ protocol EmptySwiftProto {}
241241
// expected-error@-1 {{extension for category 'PresentAdditions' does not provide all required implementations}}
242242
// expected-note@-2 {{missing instance method 'categoryMethod(fromHeader4:)'}} {{none}}
243243
// FIXME: give better diagnostic expected-note@-3 {{missing instance method 'categoryMethod(fromHeader3:)'}} {{none}}
244-
// expected-note@-4 {{add stubs for missing '@implementation' requirements}} {{62-62=\n @objc(categoryMethodFromHeader3:)\n open func categoryMethod(fromHeader3 param: Int32) {\n <#code#>\n \}\n\n @objc(categoryMethodFromHeader4:)\n open func categoryMethod(fromHeader4 param: Int32) {\n <#code#>\n \}\n}}
244+
// expected-note@-4 {{missing property 'categoryPropertyFromHeader5'}} {{none}}
245+
// expected-note@-5 {{missing property 'categoryReadonlyPropertyFromHeader1'}} {{none}}
246+
// expected-note@-6 {{add stubs for missing '@implementation' requirements}} {{62-62=\n @objc(categoryMethodFromHeader3:)\n open func categoryMethod(fromHeader3 param: Int32) {\n <#code#>\n \}\n\n @objc(categoryMethodFromHeader4:)\n open func categoryMethod(fromHeader4 param: Int32) {\n <#code#>\n \}\n\n @objc(categoryPropertyFromHeader5)\n open var categoryPropertyFromHeader5: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(categoryReadonlyPropertyFromHeader1)\n open var categoryReadonlyPropertyFromHeader1: Int32 {\n <#code#>\n \}\n}}
245247

246248
func method(fromHeader3: CInt) {
247249
// FIXME: should emit expected-DISABLED-error@-1 {{instance method 'method(fromHeader3:)' should be implemented in extension for main class interface, not category 'PresentAdditions'}}

test/decl/ext/objc_implementation_class_extension.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// expected-note@-5 {{missing property 'otherModuleExtensionPropertyFromHeader2'}} {{none}}
1212
// expected-note@-6 {{missing instance method 'extensionMethod(fromHeader2:)'}} {{none}}
1313
// expected-note@-7 {{missing property 'extensionPropertyFromHeader2'}} {{none}}
14-
// expected-note@-8 {{add stubs for missing '@implementation' requirements}} {{43-43=\n @objc(methodFromHeader2:)\n open func method(fromHeader2 param: Int32) {\n <#code#>\n \}\n\n @objc(propertyFromHeader2)\n open var propertyFromHeader2: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(otherModuleExtensionMethodFromHeader2:)\n open func otherModuleExtensionMethod(fromHeader2 param: Int32) {\n <#code#>\n \}\n\n @objc(otherModuleExtensionPropertyFromHeader2)\n open var otherModuleExtensionPropertyFromHeader2: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(extensionMethodFromHeader2:)\n open func extensionMethod(fromHeader2 param: Int32) {\n <#code#>\n \}\n\n @objc(extensionPropertyFromHeader2)\n open var extensionPropertyFromHeader2: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n}}
14+
// expected-note@-8 {{add stubs for missing '@implementation' requirements}} {{43-43=\n @objc(methodFromHeader2:)\n open func method(fromHeader2 param: Int32) {\n <#code#>\n \}\n\n @objc(propertyFromHeader2)\n open var propertyFromHeader2: Int32\n\n @objc(otherModuleExtensionMethodFromHeader2:)\n open func otherModuleExtensionMethod(fromHeader2 param: Int32) {\n <#code#>\n \}\n\n @objc(otherModuleExtensionPropertyFromHeader2)\n open var otherModuleExtensionPropertyFromHeader2: Int32\n\n @objc(extensionMethodFromHeader2:)\n open func extensionMethod(fromHeader2 param: Int32) {\n <#code#>\n \}\n\n @objc(extensionPropertyFromHeader2)\n open var extensionPropertyFromHeader2: Int32\n}}
1515

1616
@objc func method(fromHeader1: CInt) {}
1717
@objc private func method(fromHeader2: CInt) {}

test/decl/ext/objc_implementation_conflicts.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,17 @@ import objc_implementation_private
231231
get { return 1 }
232232
set {}
233233
}
234+
235+
@objc var categoryPropertyFromHeader5: CInt {
236+
// OK, provides an implementation with a computed property
237+
get { return 1 }
238+
set {}
239+
}
240+
241+
@objc var categoryReadonlyPropertyFromHeader1: CInt {
242+
// OK, provides an implementation with a computed property
243+
get { return 1 }
244+
}
234245
}
235246

236247
@objc class SwiftClass {}

test/decl/ext/objc_implementation_early_adopter.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ protocol EmptySwiftProto {}
1717
// FIXME: give better diagnostic expected-note@-8 {{missing instance method 'method(fromHeader3:)'}} {{none}}
1818
// expected-note@-9 {{missing instance method 'extensionMethod(fromHeader2:)'}} {{none}}
1919
// expected-note@-10 {{missing property 'readonlyPropertyFromHeader7'}}
20-
// expected-note@-11 {{add stubs for missing '@implementation' requirements}} {{76-76=\n @objc(methodFromHeader3:)\n open func method(fromHeader3 param: Int32) {\n <#code#>\n \}\n\n @objc(methodFromHeader4:)\n open func method(fromHeader4 param: Int32) {\n <#code#>\n \}\n\n @objc(propertyFromHeader7)\n open var propertyFromHeader7: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(propertyFromHeader8)\n open var propertyFromHeader8: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(propertyFromHeader9)\n open var propertyFromHeader9: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(readonlyPropertyFromHeader7)\n open var readonlyPropertyFromHeader7: Int32 {\n <#code#>\n \}\n\n @objc(extensionMethodFromHeader2:)\n open func extensionMethod(fromHeader2 param: Int32) {\n <#code#>\n \}\n}}
20+
// expected-note@-11 {{add stubs for missing '@implementation' requirements}} {{76-76=\n @objc(methodFromHeader3:)\n open func method(fromHeader3 param: Int32) {\n <#code#>\n \}\n\n @objc(methodFromHeader4:)\n open func method(fromHeader4 param: Int32) {\n <#code#>\n \}\n\n @objc(propertyFromHeader7)\n open var propertyFromHeader7: Int32\n\n @objc(propertyFromHeader8)\n open var propertyFromHeader8: Int32\n\n @objc(propertyFromHeader9)\n open var propertyFromHeader9: Int32\n\n @objc(readonlyPropertyFromHeader7)\n open let readonlyPropertyFromHeader7: Int32\n\n @objc(extensionMethodFromHeader2:)\n open func extensionMethod(fromHeader2 param: Int32) {\n <#code#>\n \}\n}}
2121

2222
func method(fromHeader1: CInt) {
2323
// OK, provides an implementation for the header's method.
@@ -241,7 +241,9 @@ protocol EmptySwiftProto {}
241241
// expected-warning@-1 {{extension for category 'PresentAdditions' does not provide all required implementations; this will become an error after adopting '@implementation'}}
242242
// expected-note@-2 {{missing instance method 'categoryMethod(fromHeader4:)'}} {{none}}
243243
// FIXME: give better diagnostic expected-note@-3 {{missing instance method 'categoryMethod(fromHeader3:)'}} {{none}}
244-
// expected-note@-4 {{add stubs for missing '@implementation' requirements}} {{61-61=\n @objc(categoryMethodFromHeader3:)\n open func categoryMethod(fromHeader3 param: Int32) {\n <#code#>\n \}\n\n @objc(categoryMethodFromHeader4:)\n open func categoryMethod(fromHeader4 param: Int32) {\n <#code#>\n \}\n}}
244+
// expected-note@-4 {{missing property 'categoryPropertyFromHeader5'}} {{none}}
245+
// expected-note@-5 {{missing property 'categoryReadonlyPropertyFromHeader1'}} {{none}}
246+
// expected-note@-6 {{add stubs for missing '@implementation' requirements}} {{61-61=\n @objc(categoryMethodFromHeader3:)\n open func categoryMethod(fromHeader3 param: Int32) {\n <#code#>\n \}\n\n @objc(categoryMethodFromHeader4:)\n open func categoryMethod(fromHeader4 param: Int32) {\n <#code#>\n \}\n\n @objc(categoryPropertyFromHeader5)\n open var categoryPropertyFromHeader5: Int32 {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n @objc(categoryReadonlyPropertyFromHeader1)\n open var categoryReadonlyPropertyFromHeader1: Int32 {\n <#code#>\n \}\n}}
245247

246248
func method(fromHeader3: CInt) {
247249
// FIXME: should emit expected-DISABLED-error@-1 {{instance method 'method(fromHeader3:)' should be implemented in extension for main class interface, not category 'PresentAdditions'}}

test/decl/protocol/conforms/fixit_stub.swift

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
// RUN: %target-typecheck-verify-swift
22

33
protocol Protocol1 {
4-
func foo(arg1: Int, arg2: String) -> String // expected-note{{protocol requires function 'foo(arg1:arg2:)' with type '(Int, String) -> String'}}
5-
func bar() throws -> String // expected-note{{protocol requires function 'bar()' with type '() throws -> String'}}
6-
func generic<T>(t: T) // expected-note{{protocol requires function 'generic(t:)' with type '<T> (t: T) -> ()'}}
7-
init(arg: Int) // expected-note{{protocol requires initializer 'init(arg:)' with type '(arg: Int)'}}
8-
var baz: Int { get } // expected-note{{protocol requires property 'baz' with type 'Int'}}
9-
var baz2: Int { get set } // expected-note{{protocol requires property 'baz2' with type 'Int'}}
10-
subscript(arg: Int) -> String { get } //expected-note{{rotocol requires subscript with type '(Int) -> String'}}
11-
subscript(arg1: Int, arg2: Int) -> String { get set } //expected-note{{protocol requires subscript with type '(Int, Int) -> String'}}
4+
func foo(arg1: Int, arg2: String) -> String // expected-note 2{{protocol requires function 'foo(arg1:arg2:)' with type '(Int, String) -> String'}}
5+
func bar() throws -> String // expected-note 2{{protocol requires function 'bar()' with type '() throws -> String'}}
6+
func generic<T>(t: T) // expected-note 2{{protocol requires function 'generic(t:)' with type '<T> (t: T) -> ()'}}
7+
init(arg: Int) // expected-note 2{{protocol requires initializer 'init(arg:)' with type '(arg: Int)'}}
8+
var baz: Int { get } // expected-note 2{{protocol requires property 'baz' with type 'Int'}}
9+
var baz2: Int { get set } // expected-note 2{{protocol requires property 'baz2' with type 'Int'}}
10+
subscript(arg: Int) -> String { get } //expected-note 2{{rotocol requires subscript with type '(Int) -> String'}}
11+
subscript(arg1: Int, arg2: Int) -> String { get set } //expected-note 2{{protocol requires subscript with type '(Int, Int) -> String'}}
12+
}
13+
14+
class Adopter: Protocol1 { // expected-error{{type 'Adopter' does not conform to protocol 'Protocol1'}} expected-note {{add stubs for conformance}} {{27-27=\n func foo(arg1: Int, arg2: String) -> String {\n <#code#>\n \}\n\n func bar() throws -> String {\n <#code#>\n \}\n\n func generic<T>(t: T) {\n <#code#>\n \}\n\n required init(arg: Int) {\n <#code#>\n \}\n\n let baz: Int\n\n var baz2: Int\n\n subscript(arg: Int) -> String {\n <#code#>\n \}\n\n subscript(arg1: Int, arg2: Int) -> String {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n}}
1215
}
1316

14-
class Adopter: Protocol1 { // expected-error{{type 'Adopter' does not conform to protocol 'Protocol1'}} expected-note {{add stubs for conformance}} {{27-27=\n func foo(arg1: Int, arg2: String) -> String {\n <#code#>\n \}\n\n func bar() throws -> String {\n <#code#>\n \}\n\n func generic<T>(t: T) {\n <#code#>\n \}\n\n required init(arg: Int) {\n <#code#>\n \}\n\n var baz: Int\n\n var baz2: Int\n\n subscript(arg: Int) -> String {\n <#code#>\n \}\n\n subscript(arg1: Int, arg2: Int) -> String {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n}}
17+
enum EnumAdopter: Protocol1 { // expected-error{{type 'EnumAdopter' does not conform to protocol 'Protocol1'}} expected-note {{add stubs for conformance}} {{30-30=\n func foo(arg1: Int, arg2: String) -> String {\n <#code#>\n \}\n\n func bar() throws -> String {\n <#code#>\n \}\n\n func generic<T>(t: T) {\n <#code#>\n \}\n\n init(arg: Int) {\n <#code#>\n \}\n\n var baz: Int {\n <#code#>\n \}\n\n var baz2: Int {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n\n subscript(arg: Int) -> String {\n <#code#>\n \}\n\n subscript(arg1: Int, arg2: Int) -> String {\n get {\n <#code#>\n \}\n set {\n <#code#>\n \}\n \}\n}}
1518
}
1619

1720

test/decl/protocol/fixits_missing_protocols_in_context.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class C {
1313
func assign() {
1414
p = self
1515
// expected-error@-1 {{cannot assign value of type 'C' to type '(any P)?'}}
16-
// expected-note@-2 {{add missing conformance to 'P' to class 'C'}} {{-4:8-8=: P}} {{-4:10-10=\n func method() {\n <#code#>\n \}\n\n var property: Int\n}}
16+
// expected-note@-2 {{add missing conformance to 'P' to class 'C'}} {{-4:8-8=: P}} {{-4:10-10=\n func method() {\n <#code#>\n \}\n\n let property: Int\n}}
1717
}
1818
}
1919

@@ -25,7 +25,7 @@ class C1 {
2525
func assign() {
2626
p = self
2727
// expected-error@-1 {{cannot assign value of type 'C1' to type '(any P)?'}}
28-
// expected-note@-2 {{add missing conformance to 'P' to class 'C1'}} {{-4:9-9=: P}} {{-4:11-11=\n var property: Int\n}}
28+
// expected-note@-2 {{add missing conformance to 'P' to class 'C1'}} {{-4:9-9=: P}} {{-4:11-11=\n let property: Int\n}}
2929
}
3030

3131
func method() {}

0 commit comments

Comments
 (0)