Skip to content

Commit 1060d1a

Browse files
author
Nathan Hawes
authored
Merge pull request #24073 from nathawes/inherit-default-values
[ParseableInterfaces] Support inheriting default arguments in module interfaces via '= super'
2 parents 778f0f8 + 32497c7 commit 1060d1a

File tree

11 files changed

+289
-14
lines changed

11 files changed

+289
-14
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,19 @@ WARNING(expr_dynamic_lookup_swift3_objc_inference,none,
12831283
"deprecated in Swift 4",
12841284
(DescriptiveDeclKind, DeclName, Identifier))
12851285

1286+
ERROR(inherited_default_value_not_in_designated_constructor,none,
1287+
"default value inheritance via 'super' is only valid on the parameters of "
1288+
"designated initializers", ())
1289+
ERROR(inherited_default_value_used_in_non_overriding_constructor,none,
1290+
"default value inheritance via 'super' can only be used when "
1291+
"overriding a designated initializer", ())
1292+
ERROR(corresponding_param_not_defaulted,none,
1293+
"default value inheritance via 'super' requires that the corresponding "
1294+
"parameter of the overridden designated initializer has a default value",
1295+
())
1296+
NOTE(inherited_default_param_here,none,
1297+
"corresponding parameter declared here", ())
1298+
12861299
// Alignment attribute
12871300
ERROR(alignment_not_power_of_two,none,
12881301
"alignment value must be a power of two", ())

include/swift/Parse/Parser.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,9 @@ class Parser {
11441144

11451145
/// The default argument for this parameter.
11461146
Expr *DefaultArg = nullptr;
1147+
1148+
/// True if this parameter inherits a default argument via '= super'
1149+
bool hasInheritedDefaultArg = false;
11471150

11481151
/// True if we emitted a parse error about this parameter.
11491152
bool isInvalid = false;

lib/AST/Decl.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5443,10 +5443,7 @@ ParamDecl::getDefaultValueStringRepresentation(
54435443
var->getParentInitializer(),
54445444
scratch);
54455445
}
5446-
case DefaultArgumentKind::Inherited:
5447-
// FIXME: This needs /some/ kind of textual representation, but this isn't
5448-
// a great one.
5449-
return "super";
5446+
case DefaultArgumentKind::Inherited: return "super";
54505447
case DefaultArgumentKind::File: return "#file";
54515448
case DefaultArgumentKind::Line: return "#line";
54525449
case DefaultArgumentKind::Column: return "#column";

lib/Parse/ParsePattern.cpp

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,30 @@ void Parser::DefaultArgumentInfo::setFunctionContext(
6666

6767
static ParserStatus parseDefaultArgument(
6868
Parser &P, Parser::DefaultArgumentInfo *defaultArgs, unsigned argIndex,
69-
Expr *&init, Parser::ParameterContextKind paramContext) {
69+
Expr *&init, bool &hasInheritedDefaultArg,
70+
Parser::ParameterContextKind paramContext) {
7071
SyntaxParsingContext DefaultArgContext(P.SyntaxContext,
7172
SyntaxKind::InitializerClause);
7273
SourceLoc equalLoc = P.consumeToken(tok::equal);
7374

75+
if (P.SF.Kind == SourceFileKind::Interface) {
76+
// Swift module interfaces don't synthesize inherited intializers and
77+
// instead include them explicitly in subclasses. Since the
78+
// \c DefaultArgumentKind of these initializers is \c Inherited, this is
79+
// represented textually as `= super` in the interface.
80+
81+
// If we're in a module interface and the default argument is exactly
82+
// `super` (i.e. the token after that is `,` or `)` which end a parameter)
83+
// report an inherited default argument to the caller and return.
84+
if (P.Tok.is(tok::kw_super) && P.peekToken().isAny(tok::comma, tok::r_paren)) {
85+
hasInheritedDefaultArg = true;
86+
P.consumeToken(tok::kw_super);
87+
P.SyntaxContext->createNodeInPlace(SyntaxKind::SuperRefExpr);
88+
defaultArgs->HasDefaultArgument = true;
89+
return ParserStatus();
90+
}
91+
}
92+
7493
// Enter a fresh default-argument context with a meaningless parent.
7594
// We'll change the parent to the function later after we've created
7695
// that declaration.
@@ -365,7 +384,9 @@ Parser::parseParameterClause(SourceLoc &leftParenLoc,
365384
if (Tok.is(tok::equal)) {
366385
SourceLoc EqualLoc = Tok.getLoc();
367386
status |= parseDefaultArgument(*this, defaultArgs, defaultArgIndex,
368-
param.DefaultArg, paramContext);
387+
param.DefaultArg,
388+
param.hasInheritedDefaultArg,
389+
paramContext);
369390

370391
if (param.EllipsisLoc.isValid() && param.DefaultArg) {
371392
// The range of the complete default argument.
@@ -603,16 +624,21 @@ mapParsedParameters(Parser &parser,
603624
}
604625
}
605626

627+
assert (((!param.DefaultArg &&
628+
!param.hasInheritedDefaultArg) ||
629+
paramContext == Parser::ParameterContextKind::Function ||
630+
paramContext == Parser::ParameterContextKind::Operator ||
631+
paramContext == Parser::ParameterContextKind::Initializer ||
632+
paramContext == Parser::ParameterContextKind::EnumElement ||
633+
paramContext == Parser::ParameterContextKind::Subscript) &&
634+
"Default arguments are only permitted on the first param clause");
635+
606636
if (param.DefaultArg) {
607-
assert((paramContext == Parser::ParameterContextKind::Function ||
608-
paramContext == Parser::ParameterContextKind::Operator ||
609-
paramContext == Parser::ParameterContextKind::Initializer ||
610-
paramContext == Parser::ParameterContextKind::EnumElement ||
611-
paramContext == Parser::ParameterContextKind::Subscript) &&
612-
"Default arguments are only permitted on the first param clause");
613637
DefaultArgumentKind kind = getDefaultArgKind(param.DefaultArg);
614638
result->setDefaultArgumentKind(kind);
615639
result->setDefaultValue(param.DefaultArg);
640+
} else if (param.hasInheritedDefaultArg) {
641+
result->setDefaultArgumentKind(DefaultArgumentKind::Inherited);
616642
}
617643

618644
elements.push_back(result);

lib/Sema/TypeCheckStmt.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,10 +1766,58 @@ Stmt *StmtChecker::visitBraceStmt(BraceStmt *BS) {
17661766
return BS;
17671767
}
17681768

1769+
static Optional<unsigned>
1770+
getParamIndex(const ParameterList *paramList, const ParamDecl *decl) {
1771+
ArrayRef<ParamDecl *> params = paramList->getArray();
1772+
for (unsigned i = 0; i < params.size(); ++i) {
1773+
if (params[i] == decl) return i;
1774+
}
1775+
return None;
1776+
}
1777+
1778+
static void
1779+
checkInheritedDefaultValueRestrictions(TypeChecker &TC, ParamDecl *PD) {
1780+
if (PD->getDefaultArgumentKind() != DefaultArgumentKind::Inherited)
1781+
return;
1782+
1783+
auto *DC = PD->getInnermostDeclContext();
1784+
const SourceFile *SF = DC->getParentSourceFile();
1785+
assert((SF && SF->Kind == SourceFileKind::Interface || PD->isImplicit()) &&
1786+
"explicit inherited default argument outside of a module interface?");
1787+
1788+
// The containing decl should be a designated initializer.
1789+
auto ctor = dyn_cast<ConstructorDecl>(DC);
1790+
if (!ctor || ctor->isConvenienceInit()) {
1791+
TC.diagnose(
1792+
PD, diag::inherited_default_value_not_in_designated_constructor);
1793+
return;
1794+
}
1795+
1796+
// The decl it overrides should also be a designated initializer.
1797+
auto overridden = ctor->getOverriddenDecl();
1798+
if (!overridden || overridden->isConvenienceInit()) {
1799+
TC.diagnose(
1800+
PD, diag::inherited_default_value_used_in_non_overriding_constructor);
1801+
if (overridden)
1802+
TC.diagnose(overridden, diag::overridden_here);
1803+
return;
1804+
}
1805+
1806+
// The corresponding parameter should have a default value.
1807+
Optional<unsigned> idx = getParamIndex(ctor->getParameters(), PD);
1808+
assert(idx && "containing decl does not contain param?");
1809+
ParamDecl *equivalentParam = overridden->getParameters()->get(*idx);
1810+
if (equivalentParam->getDefaultArgumentKind() == DefaultArgumentKind::None) {
1811+
TC.diagnose(PD, diag::corresponding_param_not_defaulted);
1812+
TC.diagnose(equivalentParam, diag::inherited_default_param_here);
1813+
}
1814+
}
1815+
17691816
/// Check the default arguments that occur within this pattern.
17701817
void TypeChecker::checkDefaultArguments(ParameterList *params,
17711818
ValueDecl *VD) {
17721819
for (auto *param : *params) {
1820+
checkInheritedDefaultValueRestrictions(*this, param);
17731821
if (!param->getDefaultValue() ||
17741822
!param->hasInterfaceType() ||
17751823
param->getInterfaceType()->hasError())

test/ParseableInterface/default-args.swift

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,34 @@
33
// RUN: %target-swift-frontend -merge-modules -emit-module -o %t/Test.swiftmodule %t/Test~partial.swiftmodule
44
// RUN: %target-swift-ide-test -print-module -module-to-print=Test -source-filename=x -I %t | %FileCheck %s
55

6-
// RUN: %target-swift-frontend -typecheck -emit-parseable-module-interface-path %t.swiftinterface -enable-library-evolution %s
7-
// RUN: %FileCheck %s < %t.swiftinterface
6+
// RUN: %target-swift-frontend -typecheck -emit-parseable-module-interface-path %t/Test.swiftinterface -enable-library-evolution %s
7+
// RUN: rm %t/Test.swiftmodule
8+
// RUN: echo "import Test" > %t/test-client.swift
9+
// RUN: %target-swift-frontend -typecheck -I%t %t/test-client.swift
10+
// RUN: %FileCheck %s < %t/Test.swiftinterface
11+
12+
// CHECK: class Base {
13+
public class Base {
14+
// CHECK: init(x: Int = 3)
15+
public init(x: Int = 3) {}
16+
public convenience init(convInit: Int) {
17+
self.init(x: convInit)
18+
}
19+
// CHECK: foo(y: Int = 42)
20+
public func foo(y: Int = 42) {}
21+
}
22+
23+
// CHECK: class Derived : Base {
24+
public class Derived: Base {
25+
// CHECK: init(y: Int)
26+
public convenience init(y: Int) {
27+
self.init()
28+
}
29+
30+
// CHECK-NOT: init(convInit: Int = super)
31+
// CHECK: override {{(public )?}}init(x: Int = super)
32+
// CHECK-NOT: init(convInit: Int = super)
33+
}
834

935
public enum Enum {
1036
// CHECK: case pie(filling: String = "apple")
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name InheritedDefaults
3+
4+
// RUN: %empty-directory(%t)
5+
// RUN: %target-typecheck-verify-swift
6+
7+
import Swift
8+
9+
public class Bar {
10+
// note associated with the expected error in (F) below
11+
public init(x: Int = 24, y: Int, z: Int = 42) // expected-note {{corresponding parameter declared here}}
12+
13+
public init(a: Int, b: Int = 99)
14+
public convenience init(convInit: Int = 45) {}
15+
16+
// note associated with the expected error in (D) below
17+
public convenience init(first: Int, second: Int = 88, third: Int, fourth: Int) // expected-note {{overridden declaration is here}}
18+
}
19+
20+
public class Foo: Bar {
21+
22+
// A) designated overriding designated - valid
23+
public override init(x: Int = super, y: Int, z: Int = super)
24+
25+
// B) convenience shadowing convenience
26+
public convenience init(convInit: Int = super) // expected-error {{default value inheritance via 'super' is only valid on the parameters of designated initializers}}
27+
28+
// C) convenience overriding designated
29+
public override convenience init(a: Int, b: Int = super) // expected-error {{default value inheritance via 'super' is only valid on the parameters of designated initializers}}
30+
31+
// D) designated shadowing convenience
32+
public init(first: Int, second: Int = super, third: Int, fourth: Int) // expected-error {{default value inheritance via 'super' can only be used when overriding a designated initializer}}
33+
34+
// E) not in initializer
35+
public subscript(k: Int = super) -> Int { get } // expected-error {{default value inheritance via 'super' is only valid on the parameters of designated initializers}}
36+
public func foo(z: Int = super) // expected-error {{default value inheritance via 'super' is only valid on the parameters of designated initializers}}
37+
}
38+
39+
public class Baz: Bar {
40+
41+
// F) Matching param not defaulted
42+
public override init(x: Int = super, y: Int = super, z: Int = super) // expected-error {{default value inheritance via 'super' requires that the corresponding parameter of the overridden designated initializer has a default value}}
43+
}
44+
45+
public class Direct: Bar {
46+
public override init(x: Int = super, y: Int, z: Int = super)
47+
48+
// G) Doesn't override anything
49+
public override init(other: Int = super, value: Int) // expected-error {{argument labels for initializer 'init(other:value:)' do not match those of overridden initializer 'init(a:b:)'}}
50+
// expected-error@-1 {{default value inheritance via 'super' can only be used when overriding a designated initializer}}
51+
}
52+
53+
public class Indirect: Direct {
54+
55+
// H) Chain of inherited defaults - valid all the way down
56+
public override init(x: Int = super, y: Int, z: Int = super)
57+
58+
// I) Chain of inherited defaults - invalid further down (and diagnosed there)
59+
public override init(other: Int = super, value: Int)
60+
}
61+
62+
public enum Bob {
63+
case bar(p: Int)
64+
public init(foo: String = super /*comment*/) // expected-error {{default value inheritance via 'super' can only be used when overriding a designated initializer}}
65+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// REQUIRES: executable_test
2+
// RUN: %empty-directory(%t)
3+
4+
// 1) Build the 'Inherited' library and its interface from this file
5+
//
6+
// RUN: %target-build-swift-dylib(%t/%target-library-name(Inherited)) -emit-module-path %t/Inherited.swiftmodule -emit-parseable-module-interface-path %t/Inherited.swiftinterface -module-name Inherited %s
7+
// RUN: rm %t/Inherited.swiftmodule
8+
9+
// 2) Check the interface includes the synthesized initializers of the base
10+
// class in the derived class explicitly and uses the '= super' syntax to
11+
// inherit its default arguments.
12+
//
13+
// RUN: cat %t/Inherited.swiftinterface | %FileCheck --check-prefix=INTERFACE %s
14+
//
15+
// INTERFACE: public class Base {
16+
// INTERFACE: public init(x: Swift.Int = 45, y: Swift.Int = 98)
17+
// INTERFACE: }
18+
// INTERFACE: public class Derived : Inherited.Base {
19+
// INTERFACE: override public init(x: Swift.Int = super, y: Swift.Int = super)
20+
// INTERFACE: }
21+
22+
// 4) Generate a main.swift file that uses the 'Inherited' library and makes use
23+
// of the inherited default arguments
24+
//
25+
// RUN: echo "import Inherited" > %t/main.swift
26+
// RUN: echo "print(Derived().x)" >> %t/main.swift
27+
// RUN: echo "print(Derived().y)" >> %t/main.swift
28+
29+
// 5) Build and run the executable, checking the defaulted arguments resulted in
30+
// the correct values being stored
31+
//
32+
// RUN: %target-build-swift -I%t -L%t -lInherited -o %t/main %target-rpath(%t) %t/main.swift -swift-version 5
33+
// RUN: %target-codesign %t/main %t/%target-library-name(Inherited)
34+
// RUN: %target-run %t/main | %FileCheck --check-prefix=OUTPUT %s
35+
//
36+
// OUTPUT: 45
37+
// OUTPUT-NEXT: 98
38+
39+
public class Base {
40+
public let x: Int
41+
public let y: Int
42+
public init(x: Int = 45, y: Int = 98) {
43+
self.x = x
44+
self.y = y
45+
}
46+
}
47+
public class Derived: Base {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<ImportDecl>// RUN: rm -rf %t
2+
// RUN: %swift-syntax-test -input-source-filename %s -parse-gen > %t
3+
// RUN: diff -u %s %t
4+
// RUN: %swift-syntax-test -input-source-filename %s -parse-gen -print-node-kind > %t.withkinds
5+
// RUN: diff -u %S/Outputs/round_trip_module_interface.swiftinterface.withkinds %t.withkinds
6+
// RUN: %swift-syntax-test -input-source-filename %s -eof > %t
7+
// RUN: diff -u %s %t
8+
// RUN: %swift-syntax-test -serialize-raw-tree -input-source-filename %s > %t.dump
9+
// RUN: %swift-syntax-test -deserialize-raw-tree -input-source-filename %t.dump -output-filename %t
10+
// RUN: diff -u %s %t
11+
12+
import <AccessPathComponent>Swift</AccessPathComponent></ImportDecl><ClassDecl><DeclModifier>
13+
14+
public </DeclModifier>class Bar <MemberDeclBlock>{<MemberDeclListItem><InitializerDecl><DeclModifier>
15+
public </DeclModifier>init<ParameterClause>(<FunctionParameter>x: <SimpleTypeIdentifier>Int </SimpleTypeIdentifier><InitializerClause>= <IntegerLiteralExpr>24</IntegerLiteralExpr></InitializerClause>, </FunctionParameter><FunctionParameter>y: <SimpleTypeIdentifier>Int</SimpleTypeIdentifier></FunctionParameter>)</ParameterClause></InitializerDecl></MemberDeclListItem>
16+
}</MemberDeclBlock></ClassDecl><ClassDecl><DeclModifier>
17+
18+
public </DeclModifier>class Foo<TypeInheritanceClause>: <InheritedType><SimpleTypeIdentifier>Bar </SimpleTypeIdentifier></InheritedType></TypeInheritanceClause><MemberDeclBlock>{<MemberDeclListItem><InitializerDecl><DeclModifier>
19+
public </DeclModifier><DeclModifier>override </DeclModifier>init<ParameterClause>(<FunctionParameter>x: <SimpleTypeIdentifier>Int </SimpleTypeIdentifier><InitializerClause>= <SuperRefExpr>super</SuperRefExpr></InitializerClause>, </FunctionParameter><FunctionParameter>y: <SimpleTypeIdentifier>Int</SimpleTypeIdentifier></FunctionParameter>)</ParameterClause></InitializerDecl></MemberDeclListItem><MemberDeclListItem><SubscriptDecl><DeclModifier>
20+
public </DeclModifier>subscript<ParameterClause>(<FunctionParameter>k: <SimpleTypeIdentifier>Int </SimpleTypeIdentifier><InitializerClause>= <SuperRefExpr>super</SuperRefExpr></InitializerClause></FunctionParameter>) </ParameterClause><ReturnClause>-> <SimpleTypeIdentifier>Int </SimpleTypeIdentifier></ReturnClause><AccessorBlock>{ <AccessorDecl>get </AccessorDecl>}</AccessorBlock></SubscriptDecl></MemberDeclListItem><MemberDeclListItem><FunctionDecl><DeclModifier>
21+
public </DeclModifier>func foo<FunctionSignature><ParameterClause>(<FunctionParameter>x: <SimpleTypeIdentifier>Int </SimpleTypeIdentifier><InitializerClause>= <SuperRefExpr>super</SuperRefExpr></InitializerClause></FunctionParameter>)</ParameterClause></FunctionSignature></FunctionDecl></MemberDeclListItem><MemberDeclListItem><FunctionDecl><DeclModifier>
22+
public </DeclModifier>func foo<FunctionSignature><ParameterClause>(<FunctionParameter>y: <SimpleTypeIdentifier>Int </SimpleTypeIdentifier><InitializerClause>= <MemberAccessExpr><SuperRefExpr>super</SuperRefExpr>.init</MemberAccessExpr></InitializerClause></FunctionParameter>)</ParameterClause></FunctionSignature></FunctionDecl></MemberDeclListItem><MemberDeclListItem><FunctionDecl><DeclModifier>
23+
public </DeclModifier>func foo<FunctionSignature><ParameterClause>(<FunctionParameter>z: <SimpleTypeIdentifier>Int </SimpleTypeIdentifier><InitializerClause>= <SubscriptExpr><SuperRefExpr>super</SuperRefExpr>[<FunctionCallArgument><IntegerLiteralExpr>1</IntegerLiteralExpr></FunctionCallArgument>]</SubscriptExpr></InitializerClause></FunctionParameter>)</ParameterClause></FunctionSignature></FunctionDecl></MemberDeclListItem>
24+
}</MemberDeclBlock></ClassDecl>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// RUN: rm -rf %t
2+
// RUN: %swift-syntax-test -input-source-filename %s -parse-gen > %t
3+
// RUN: diff -u %s %t
4+
// RUN: %swift-syntax-test -input-source-filename %s -parse-gen -print-node-kind > %t.withkinds
5+
// RUN: diff -u %S/Outputs/round_trip_module_interface.swiftinterface.withkinds %t.withkinds
6+
// RUN: %swift-syntax-test -input-source-filename %s -eof > %t
7+
// RUN: diff -u %s %t
8+
// RUN: %swift-syntax-test -serialize-raw-tree -input-source-filename %s > %t.dump
9+
// RUN: %swift-syntax-test -deserialize-raw-tree -input-source-filename %t.dump -output-filename %t
10+
// RUN: diff -u %s %t
11+
12+
import Swift
13+
14+
public class Bar {
15+
public init(x: Int = 24, y: Int)
16+
}
17+
18+
public class Foo: Bar {
19+
public override init(x: Int = super, y: Int)
20+
public subscript(k: Int = super) -> Int { get }
21+
public func foo(x: Int = super)
22+
public func foo(y: Int = super.init)
23+
public func foo(z: Int = super[1])
24+
}

tools/swift-syntax-test/swift-syntax-test.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,8 @@ int parseFile(
600600
Invocation.getLangOptions().VerifySyntaxTree = options::VerifySyntaxTree;
601601
Invocation.getLangOptions().RequestEvaluatorGraphVizPath = options::GraphVisPath;
602602
Invocation.getFrontendOptions().InputsAndOutputs.addInputFile(InputFileName);
603+
if (InputFileName.endswith(".swiftinterface"))
604+
Invocation.setInputKind(InputFileKind::SwiftModuleInterface);
603605
Invocation.setMainExecutablePath(
604606
llvm::sys::fs::getMainExecutable(MainExecutablePath,
605607
reinterpret_cast<void *>(&anchorForGetMainExecutable)));

0 commit comments

Comments
 (0)