Skip to content

Commit d42eba4

Browse files
author
Marc Rasi
committed
parsing, typechecking, and SILGen for #assert
`#assert` is a new static assertion statement that will let us write tests for the new constant evaluation infrastructure that we are working on. `#assert` works by lowering to a `Builtin.poundAssert` SIL instruction. The constant evaluation infrastructure will look for these SIL instructions, const-evaluate their conditions, and emit errors if the conditions are non-constant or false. This commit implements parsing, typechecking and SILGen for `#assert`.
1 parent 80419c7 commit d42eba4

31 files changed

+306
-40
lines changed

include/swift/AST/Builtins.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,9 @@ BUILTIN_MISC_OPERATION(Swift3ImplicitObjCEntrypoint, "swift3ImplicitObjCEntrypoi
536536
/// willThrow: Error -> ()
537537
BUILTIN_MISC_OPERATION(WillThrow, "willThrow", "", Special)
538538

539+
/// poundAssert has type (Builtin.Int1, Builtin.RawPointer) -> ().
540+
BUILTIN_MISC_OPERATION(PoundAssert, "poundAssert", "", Special)
541+
539542
#undef BUILTIN_MISC_OPERATION
540543

541544
/// Builtins for instrumentation added by sanitizers during SILGen.

include/swift/AST/DiagnosticsParse.def

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ WARNING(escaped_parameter_name,none,
9999
"keyword '%0' does not need to be escaped in argument list",
100100
(StringRef))
101101

102+
ERROR(forbidden_interpolated_string,none,
103+
"%0 cannot be an interpolated string literal", (StringRef))
104+
ERROR(forbidden_extended_escaping_string,none,
105+
"%0 cannot be an extended escaping string literal", (StringRef))
106+
102107
//------------------------------------------------------------------------------
103108
// MARK: Lexer diagnostics
104109
//------------------------------------------------------------------------------
@@ -1249,6 +1254,17 @@ ERROR(expr_typeof_expected_rparen,PointsToFirstBadToken,
12491254
ERROR(expr_dynamictype_deprecated,PointsToFirstBadToken,
12501255
"'.dynamicType' is deprecated. Use 'type(of: ...)' instead", ())
12511256

1257+
ERROR(pound_assert_disabled,PointsToFirstBadToken,
1258+
"#assert is an experimental feature that is currently disabled", ())
1259+
ERROR(pound_assert_expected_lparen,PointsToFirstBadToken,
1260+
"expected '(' in #assert directive", ())
1261+
ERROR(pound_assert_expected_rparen,PointsToFirstBadToken,
1262+
"expected ')' in #assert directive", ())
1263+
ERROR(pound_assert_expected_expression,PointsToFirstBadToken,
1264+
"expected a condition expression", ())
1265+
ERROR(pound_assert_expected_string_literal,PointsToFirstBadToken,
1266+
"expected a string literal", ())
1267+
12521268
//------------------------------------------------------------------------------
12531269
// MARK: Attribute-parsing diagnostics
12541270
//------------------------------------------------------------------------------
@@ -1297,11 +1313,6 @@ ERROR(alignment_must_be_positive_integer,none,
12971313
ERROR(swift_native_objc_runtime_base_must_be_identifier,none,
12981314
"@_swift_native_objc_runtime_base class name must be an identifier", ())
12991315

1300-
ERROR(attr_interpolated_string,none,
1301-
"'%0' cannot be an interpolated string literal", (StringRef))
1302-
ERROR(attr_extended_escaping_string,none,
1303-
"'%0' cannot be an extended escaping string literal", (StringRef))
1304-
13051316
ERROR(attr_only_at_non_local_scope, none,
13061317
"attribute '%0' can only be used in a non-local scope", (StringRef))
13071318

include/swift/AST/Stmt.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,31 @@ class ThrowStmt : public Stmt {
12251225
}
12261226
};
12271227

1228+
/// PoundAssertStmt - Asserts that a condition is true, at compile time.
1229+
class PoundAssertStmt : public Stmt {
1230+
SourceRange Range;
1231+
Expr *Condition;
1232+
StringRef Message;
1233+
1234+
public:
1235+
PoundAssertStmt(SourceRange Range, Expr *condition, StringRef message)
1236+
: Stmt(StmtKind::PoundAssert, /*Implicit=*/false),
1237+
Range(Range),
1238+
Condition(condition),
1239+
Message(message) {}
1240+
1241+
SourceRange getSourceRange() const { return Range; }
1242+
1243+
Expr *getCondition() const { return Condition; }
1244+
StringRef getMessage() const { return Message; }
1245+
1246+
void setCondition(Expr *condition) { Condition = condition; }
1247+
1248+
static bool classof(const Stmt *S) {
1249+
return S->getKind() == StmtKind::PoundAssert;
1250+
}
1251+
};
1252+
12281253
} // end namespace swift
12291254

12301255
#endif // SWIFT_AST_STMT_H

include/swift/AST/StmtNodes.def

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ STMT(Continue, Stmt)
6767
STMT(Fallthrough, Stmt)
6868
STMT(Fail, Stmt)
6969
STMT(Throw, Stmt)
70-
LAST_STMT(Throw)
70+
STMT(PoundAssert, Stmt)
71+
LAST_STMT(PoundAssert)
7172

7273
#undef STMT_RANGE
7374
#undef LABELED_STMT

include/swift/Basic/LangOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ namespace swift {
207207
/// optimized custom allocator, so that memory debugging tools can be used.
208208
bool UseMalloc = false;
209209

210+
/// \brief Enable experimental #assert feature.
211+
bool EnableExperimentalStaticAssert = false;
212+
210213
/// \brief Enable experimental property behavior feature.
211214
bool EnableExperimentalPropertyBehaviors = false;
212215

include/swift/Option/FrontendOptions.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@ def enable_sil_opaque_values : Flag<["-"], "enable-sil-opaque-values">,
319319
def enable_large_loadable_types : Flag<["-"], "enable-large-loadable-types">,
320320
HelpText<"Enable Large Loadable types IRGen pass">;
321321

322+
def enable_experimental_static_assert :
323+
Flag<["-"], "enable-experimental-static-assert">,
324+
HelpText<"Enable experimental #assert">;
325+
322326
def enable_experimental_property_behaviors :
323327
Flag<["-"], "enable-experimental-property-behaviors">,
324328
HelpText<"Enable experimental property behaviors">;

include/swift/Parse/Parser.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,16 @@ class Parser {
555555

556556
/// Parse an #endif.
557557
bool parseEndIfDirective(SourceLoc &Loc);
558-
558+
559+
/// Given that the current token is a string literal,
560+
/// - if it is not interpolated, returns the contents;
561+
/// - otherwise, diagnoses and returns None.
562+
///
563+
/// \param Loc where to diagnose.
564+
/// \param DiagText name for the string literal in the diagnostic.
565+
Optional<StringRef>
566+
getStringLiteralIfNotInterpolated(SourceLoc Loc, StringRef DiagText);
567+
559568
public:
560569
InFlightDiagnostic diagnose(SourceLoc Loc, Diagnostic Diag) {
561570
if (Diags.isDiagnosticPointsToFirstBadToken(Diag.getID()) &&
@@ -1320,6 +1329,7 @@ class Parser {
13201329
ParserResult<Expr> parseExprCollection();
13211330
ParserResult<Expr> parseExprArray(SourceLoc LSquareLoc);
13221331
ParserResult<Expr> parseExprDictionary(SourceLoc LSquareLoc);
1332+
ParserResult<Expr> parseExprPoundAssert();
13231333
ParserResult<Expr> parseExprPoundUnknown(SourceLoc LSquareLoc);
13241334
ParserResult<Expr>
13251335
parseExprPoundCodeCompletion(Optional<StmtKind> ParentKind);
@@ -1358,6 +1368,7 @@ class Parser {
13581368
ParserResult<Stmt> parseStmtSwitch(LabeledStmtInfo LabelInfo);
13591369
ParserStatus parseStmtCases(SmallVectorImpl<ASTNode> &cases, bool IsActive);
13601370
ParserResult<CaseStmt> parseStmtCase(bool IsActive);
1371+
ParserResult<Stmt> parseStmtPoundAssert();
13611372

13621373
//===--------------------------------------------------------------------===//
13631374
// Generics Parsing

lib/AST/ASTDumper.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,6 +1649,13 @@ class PrintStmt : public StmtVisitor<PrintStmt> {
16491649
PrintWithColorRAII(OS, ParenthesisColor) << ')';
16501650
}
16511651

1652+
void visitPoundAssertStmt(PoundAssertStmt *S) {
1653+
printCommon(S, "pound_assert");
1654+
OS << " message=" << QuotedString(S->getMessage()) << "\n";
1655+
printRec(S->getCondition());
1656+
OS << ")";
1657+
}
1658+
16521659
void visitDoCatchStmt(DoCatchStmt *S) {
16531660
printCommon(S, "do_catch_stmt") << '\n';
16541661
printRec(S->getBody());

lib/AST/ASTPrinter.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3030,6 +3030,11 @@ void PrintAST::visitThrowStmt(ThrowStmt *stmt) {
30303030
// FIXME: print expression.
30313031
}
30323032

3033+
void PrintAST::visitPoundAssertStmt(PoundAssertStmt *stmt) {
3034+
Printer << tok::pound_assert << " ";
3035+
// FIXME: print expression.
3036+
}
3037+
30333038
void PrintAST::visitDeferStmt(DeferStmt *stmt) {
30343039
Printer << tok::kw_defer << " ";
30353040
visit(stmt->getBodyAsWritten());

lib/AST/ASTScope.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,7 @@ ASTScope *ASTScope::createIfNeeded(const ASTScope *parent, Stmt *stmt) {
10891089
case StmtKind::Fallthrough:
10901090
case StmtKind::Fail:
10911091
case StmtKind::Throw:
1092+
case StmtKind::PoundAssert:
10921093
// Nothing to do for these statements.
10931094
return nullptr;
10941095
}

lib/AST/ASTWalker.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,6 +1277,14 @@ Stmt *Traversal::visitThrowStmt(ThrowStmt *TS) {
12771277
return nullptr;
12781278
}
12791279

1280+
Stmt *Traversal::visitPoundAssertStmt(PoundAssertStmt *S) {
1281+
if (auto *condition = doIt(S->getCondition())) {
1282+
S->setCondition(condition);
1283+
} else {
1284+
return nullptr;
1285+
}
1286+
return S;
1287+
}
12801288

12811289
Stmt *Traversal::visitBraceStmt(BraceStmt *BS) {
12821290
for (auto &Elem : BS->getElements()) {

lib/AST/Builtins.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,14 @@ static ValueDecl *getGetObjCTypeEncodingOperation(ASTContext &Context,
946946
return builder.build(Id);
947947
}
948948

949+
static ValueDecl *getPoundAssert(ASTContext &Context, Identifier Id) {
950+
auto int1Type = BuiltinIntegerType::get(1, Context);
951+
auto optionalRawPointerType = BoundGenericEnumType::get(
952+
Context.getOptionalDecl(), Type(), {Context.TheRawPointerType});
953+
return getBuiltinFunction(Id, {int1Type, optionalRawPointerType},
954+
Context.TheEmptyTupleType);
955+
}
956+
949957
static ValueDecl *getTSanInoutAccess(ASTContext &Context, Identifier Id) {
950958
// <T> T -> ()
951959
BuiltinGenericSignatureBuilder builder(Context);
@@ -1863,6 +1871,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
18631871
case BuiltinValueKind::GetObjCTypeEncoding:
18641872
return getGetObjCTypeEncodingOperation(Context, Id);
18651873

1874+
case BuiltinValueKind::PoundAssert:
1875+
return getPoundAssert(Context, Id);
1876+
18661877
case BuiltinValueKind::TSanInoutAccess:
18671878
return getTSanInoutAccess(Context, Id);
18681879

lib/AST/NameLookupImpl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class FindLocalVal : public StmtVisitor<FindLocalVal> {
117117
void visitReturnStmt(ReturnStmt *) {}
118118
void visitYieldStmt(YieldStmt *) {}
119119
void visitThrowStmt(ThrowStmt *) {}
120+
void visitPoundAssertStmt(PoundAssertStmt *) {}
120121
void visitDeferStmt(DeferStmt *DS) {
121122
// Nothing in the defer is visible.
122123
}

lib/Frontend/CompilerInvocation.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
206206
Opts.DiagnosticsEditorMode |= Args.hasArg(OPT_diagnostics_editor_mode,
207207
OPT_serialize_diagnostics_path);
208208

209+
Opts.EnableExperimentalStaticAssert |=
210+
Args.hasArg(OPT_enable_experimental_static_assert);
211+
209212
Opts.EnableExperimentalPropertyBehaviors |=
210213
Args.hasArg(OPT_enable_experimental_property_behaviors);
211214

lib/Parse/ParseDecl.cpp

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -307,27 +307,6 @@ bool Parser::parseTopLevel() {
307307
return FoundTopLevelCodeToExecute;
308308
}
309309

310-
static Optional<StringRef>
311-
getStringLiteralIfNotInterpolated(Parser &P, SourceLoc Loc, const Token &Tok,
312-
StringRef DiagText) {
313-
// FIXME: Support extended escaping string literal.
314-
if (Tok.getCustomDelimiterLen()) {
315-
P.diagnose(Loc, diag::attr_extended_escaping_string, DiagText);
316-
return None;
317-
}
318-
319-
SmallVector<Lexer::StringSegment, 1> Segments;
320-
P.L->getStringLiteralSegments(Tok, Segments);
321-
if (Segments.size() != 1 ||
322-
Segments.front().Kind == Lexer::StringSegment::Expr) {
323-
P.diagnose(Loc, diag::attr_interpolated_string, DiagText);
324-
return None;
325-
}
326-
327-
return P.SourceMgr.extractText(CharSourceRange(Segments.front().Loc,
328-
Segments.front().Length));
329-
}
330-
331310
ParserResult<AvailableAttr> Parser::parseExtendedAvailabilitySpecList(
332311
SourceLoc AtLoc, SourceLoc AttrLoc, StringRef AttrName) {
333312
// Check 'Tok', return false if ':' or '=' cannot be found.
@@ -439,8 +418,8 @@ ParserResult<AvailableAttr> Parser::parseExtendedAvailabilitySpecList(
439418
break;
440419
}
441420

442-
auto Value = getStringLiteralIfNotInterpolated(*this, AttrLoc, Tok,
443-
ArgumentKindStr);
421+
auto Value = getStringLiteralIfNotInterpolated(
422+
AttrLoc, ("'" + ArgumentKindStr + "'").str());
444423
consumeToken();
445424
if (!Value) {
446425
AnyArgumentInvalid = true;
@@ -1155,8 +1134,8 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
11551134
return false;
11561135
}
11571136

1158-
Optional<StringRef> AsmName =
1159-
getStringLiteralIfNotInterpolated(*this, Loc, Tok, AttrName);
1137+
Optional<StringRef> AsmName = getStringLiteralIfNotInterpolated(
1138+
Loc, ("'" + AttrName + "'").str());
11601139

11611140
consumeToken(tok::string_literal);
11621141

@@ -1269,7 +1248,8 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
12691248
return false;
12701249
}
12711250

1272-
auto Value = getStringLiteralIfNotInterpolated(*this, Loc, Tok, AttrName);
1251+
auto Value = getStringLiteralIfNotInterpolated(
1252+
Loc, ("'" + AttrName + "'").str());
12731253

12741254
consumeToken(tok::string_literal);
12751255

@@ -3607,7 +3587,7 @@ ParserStatus Parser::parseLineDirective(bool isLine) {
36073587
}
36083588

36093589
Filename =
3610-
getStringLiteralIfNotInterpolated(*this, Loc, Tok, "#sourceLocation");
3590+
getStringLiteralIfNotInterpolated(Loc, "'#sourceLocation'");
36113591
if (!Filename.hasValue())
36123592
return makeParserError();
36133593
consumeToken(tok::string_literal);
@@ -3668,8 +3648,7 @@ ParserStatus Parser::parseLineDirective(bool isLine) {
36683648
return makeParserError();
36693649
}
36703650

3671-
Filename = getStringLiteralIfNotInterpolated(*this, Loc, Tok,
3672-
"#line");
3651+
Filename = getStringLiteralIfNotInterpolated(Loc, "'#line'");
36733652
if (!Filename.hasValue())
36743653
return makeParserError();
36753654
}

lib/Parse/ParseStmt.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ bool Parser::isStartOfStmt() {
5353
case tok::kw_case:
5454
case tok::kw_default:
5555
case tok::kw_yield:
56+
case tok::pound_assert:
5657
case tok::pound_if:
5758
case tok::pound_warning:
5859
case tok::pound_error:
@@ -636,6 +637,10 @@ ParserResult<Stmt> Parser::parseStmt() {
636637
return makeParserResult(
637638
new (Context) FallthroughStmt(consumeToken(tok::kw_fallthrough)));
638639
}
640+
case tok::pound_assert:
641+
if (LabelInfo) diagnose(LabelInfo.Loc, diag::invalid_label_on_stmt);
642+
if (tryLoc.isValid()) diagnose(tryLoc, diag::try_on_stmt, Tok.getText());
643+
return parseStmtPoundAssert();
639644
}
640645
}
641646

@@ -2400,3 +2405,53 @@ ParserResult<CaseStmt> Parser::parseStmtCase(bool IsActive) {
24002405
!BoundDecls.empty(), UnknownAttrLoc, ColonLoc,
24012406
Body));
24022407
}
2408+
2409+
/// stmt-pound-assert:
2410+
/// '#assert' '(' expr (',' string_literal)? ')'
2411+
ParserResult<Stmt> Parser::parseStmtPoundAssert() {
2412+
SyntaxContext->setCreateSyntax(SyntaxKind::PoundAssertStmt);
2413+
2414+
SourceLoc startLoc = consumeToken(tok::pound_assert);
2415+
SourceLoc endLoc;
2416+
2417+
if (Tok.isNot(tok::l_paren)) {
2418+
diagnose(Tok, diag::pound_assert_expected_lparen);
2419+
return makeParserError();
2420+
}
2421+
SourceLoc LBLoc = consumeToken(tok::l_paren);
2422+
2423+
auto conditionExprResult = parseExpr(diag::pound_assert_expected_expression);
2424+
if (conditionExprResult.isParseError())
2425+
return ParserStatus(conditionExprResult);
2426+
2427+
StringRef message;
2428+
if (consumeIf(tok::comma)) {
2429+
if (Tok.isNot(tok::string_literal)) {
2430+
diagnose(Tok.getLoc(), diag::pound_assert_expected_string_literal);
2431+
return makeParserError();
2432+
}
2433+
2434+
auto messageOpt = getStringLiteralIfNotInterpolated(Tok.getLoc(),
2435+
"'#assert' message");
2436+
consumeToken();
2437+
if (!messageOpt)
2438+
return makeParserError();
2439+
2440+
message = *messageOpt;
2441+
}
2442+
2443+
if (parseMatchingToken(tok::r_paren, endLoc,
2444+
diag::pound_assert_expected_rparen, LBLoc)) {
2445+
return makeParserError();
2446+
}
2447+
2448+
// We check this after consuming everything, so that the SyntaxContext
2449+
// understands this statement even when the feature is disabled.
2450+
if (!Context.LangOpts.EnableExperimentalStaticAssert) {
2451+
diagnose(startLoc, diag::pound_assert_disabled);
2452+
return makeParserError();
2453+
}
2454+
2455+
return makeParserResult<Stmt>(new (Context) PoundAssertStmt(
2456+
SourceRange(startLoc, endLoc), conditionExprResult.get(), message));
2457+
}

0 commit comments

Comments
 (0)