Skip to content

Commit

Permalink
Transform EditorPlaceholderExpr into trap if executed in playground m…
Browse files Browse the repository at this point in the history
…ode.

Allow untyped placeholder to take arbitrary type, but default to Void.
Add _undefined<T>() function, which is like fatalError() but has
arbitrary return type. In playground mode, merely warn about outstanding
placeholders instead of erroring out, and transform placeholders into
calls to _undefined(). This way, code with outstanding placeholders will
only crash when it attempts to evaluate such placeholders.

<rdar://problem/21167372> transform EditorPlaceholderExpr into fatalError()

Swift SVN r31481
  • Loading branch information
cwillmor committed Aug 26, 2015
1 parent 6c944a6 commit 0addd80
Show file tree
Hide file tree
Showing 17 changed files with 135 additions and 7 deletions.
3 changes: 3 additions & 0 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,9 @@ class ASTContext {
/// Retrieve the declaration of Swift._unimplemented_initializer.
FuncDecl *getUnimplementedInitializerDecl(LazyResolver *resolver) const;

/// Retrieve the declaration of Swift._undefined.
FuncDecl *getUndefinedDecl(LazyResolver *resolver) const;

// Retrieve the declaration of Swift._stdlib_isOSVersionAtLeast.
FuncDecl *getIsOSVersionAtLeastDecl(LazyResolver *resolver) const;

Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ ERROR(lex_unary_postfix_dot_is_reserved,lexing,none,
"postfix '.' is reserved", ())
ERROR(lex_editor_placeholder,lexing,none,
"editor placeholder in source file", ())
WARNING(lex_editor_placeholder_in_playground,lexing,none,
"editor placeholder in source file", ())

//------------------------------------------------------------------------------
// Declaration parsing diagnostics
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,8 @@ NOTE(note_no_in_class_init_3plus,sema_tcd,none,
(Identifier, Identifier, Identifier, bool))
ERROR(missing_unimplemented_init_runtime,sema_tcd,none,
"standard library error: missing _unimplemented_initializer", ())
ERROR(missing_undefined_runtime,sema_tcd,none,
"standard library error: missing _undefined", ())
WARNING(unsupported_synthesize_init_variadic,sema_tcd,none,
"synthesizing a variadic inherited initializer for subclass %0 is "
"unsupported",
Expand Down
7 changes: 6 additions & 1 deletion include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -3678,6 +3678,7 @@ class EditorPlaceholderExpr : public Expr {
SourceLoc Loc;
TypeLoc PlaceholderTy;
TypeRepr *ExpansionTyR;
Expr *SemanticExpr;

public:
EditorPlaceholderExpr(Identifier Placeholder, SourceLoc Loc,
Expand All @@ -3686,7 +3687,8 @@ class EditorPlaceholderExpr : public Expr {
: Expr(ExprKind::EditorPlaceholder, /*Implicit=*/false),
Placeholder(Placeholder), Loc(Loc),
PlaceholderTy(PlaceholderTy),
ExpansionTyR(ExpansionTyR) {
ExpansionTyR(ExpansionTyR),
SemanticExpr(nullptr) {
}

Identifier getPlaceholder() const { return Placeholder; }
Expand All @@ -3700,6 +3702,9 @@ class EditorPlaceholderExpr : public Expr {
static bool classof(const Expr *E) {
return E->getKind() == ExprKind::EditorPlaceholder;
}

Expr *getSemanticExpr() const { return SemanticExpr; }
void setSemanticExpr(Expr *SE) { SemanticExpr = SE; }
};

#undef SWIFT_FORWARD_SOURCE_LOCS_TO
Expand Down
2 changes: 1 addition & 1 deletion include/swift/AST/ExprNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ EXPR(If, Expr)
EXPR(Assign, Expr)
EXPR(DefaultValue, Expr)
UNCHECKED_EXPR(UnresolvedPattern, Expr)
UNCHECKED_EXPR(EditorPlaceholder, Expr)
EXPR(EditorPlaceholder, Expr)

#undef EXPR_RANGE
#undef UNCHECKED_EXPR
Expand Down
18 changes: 18 additions & 0 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ struct ASTContext::Implementation {
/// func _unimplemented_initializer(className: StaticString).
FuncDecl *UnimplementedInitializerDecl = nullptr;

/// func _undefined<T>(msg: StaticString, file: StaticString, line: UInt) -> T
FuncDecl *UndefinedDecl = nullptr;

/// func _stdlib_isOSVersionAtLeast(Builtin.Word,Builtin.Word, Builtin.word)
// -> Builtin.Int1
FuncDecl *IsOSVersionAtLeastDecl = nullptr;
Expand Down Expand Up @@ -917,6 +920,21 @@ ASTContext::getUnimplementedInitializerDecl(LazyResolver *resolver) const {
return decl;
}

FuncDecl *
ASTContext::getUndefinedDecl(LazyResolver *resolver) const {
if (Impl.UndefinedDecl)
return Impl.UndefinedDecl;

// Look for the function.
CanType input, output;
auto decl = findLibraryIntrinsic(*this, "_undefined", resolver);
if (!decl)
return nullptr;

Impl.UndefinedDecl = decl;
return decl;
}

FuncDecl *ASTContext::getIsOSVersionAtLeastDecl(LazyResolver *resolver) const {
if (Impl.IsOSVersionAtLeastDecl)
return Impl.IsOSVersionAtLeastDecl;
Expand Down
1 change: 1 addition & 0 deletions lib/AST/ASTWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,7 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,
}

Expr *visitEditorPlaceholderExpr(EditorPlaceholderExpr *E) {
HANDLE_SEMANTIC_EXPR(E);
return E;
}

Expand Down
10 changes: 7 additions & 3 deletions lib/Parse/Lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1236,9 +1236,13 @@ void Lexer::tryLexEditorPlaceholder() {
if (Ptr[0] == '<' && Ptr[1] == '#')
break;
if (Ptr[0] == '#' && Ptr[1] == '>') {
// Found it. Flag it as error for the rest of the compiler pipeline and
// lex it as an identifier.
diagnose(TokStart, diag::lex_editor_placeholder);
// Found it. Flag it as error (or warning, if in playground mode) for the
// rest of the compiler pipeline and lex it as an identifier.
if (LangOpts.Playground) {
diagnose(TokStart, diag::lex_editor_placeholder_in_playground);
} else {
diagnose(TokStart, diag::lex_editor_placeholder);
}
CurPtr = Ptr+2;
formToken(tok::identifier, TokStart);
return;
Expand Down
6 changes: 6 additions & 0 deletions lib/SILGen/SILGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ namespace {
#ifdef SWIFT_ENABLE_OBJECT_LITERALS
RValue visitObjectLiteralExpr(ObjectLiteralExpr *E, SGFContext C);
#endif // SWIFT_ENABLE_OBJECT_LITERALS
RValue visitEditorPlaceholderExpr(EditorPlaceholderExpr *E, SGFContext C);
RValue visitMagicIdentifierLiteralExpr(MagicIdentifierLiteralExpr *E,
SGFContext C);
RValue visitCollectionExpr(CollectionExpr *E, SGFContext C);
Expand Down Expand Up @@ -2074,6 +2075,11 @@ visitObjectLiteralExpr(ObjectLiteralExpr *E, SGFContext C) {
}
#endif // SWIFT_ENABLE_OBJECT_LITERALS

RValue RValueEmitter::
visitEditorPlaceholderExpr(EditorPlaceholderExpr *E, SGFContext C) {
return visit(E->getSemanticExpr(), C);
}

static StringRef
getMagicFunctionString(SILGenFunction &gen) {
assert(gen.MagicFunctionName
Expand Down
23 changes: 23 additions & 0 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3300,6 +3300,29 @@ namespace {
}

Expr *visitEditorPlaceholderExpr(EditorPlaceholderExpr *E) {
Type valueType = simplifyType(E->getType());
E->setType(valueType);

auto &tc = cs.getTypeChecker();
auto &ctx = tc.Context;
// Synthesize a call to _undefined() of appropriate type.
FuncDecl *undefinedDecl = ctx.getUndefinedDecl(&tc);
if (!undefinedDecl) {
tc.diagnose(E->getLoc(), diag::missing_undefined_runtime);
return nullptr;
}
DeclRefExpr *fnRef = new (ctx) DeclRefExpr(undefinedDecl, SourceLoc(),
/*Implicit=*/true);
StringRef msg = "attempt to evaluate editor placeholder";
Expr *argExpr = new (ctx) StringLiteralExpr(msg, E->getLoc(),
/*implicit*/true);
argExpr = new (ctx) ParenExpr(E->getLoc(), argExpr, E->getLoc(),
/*hasTrailingClosure*/false);
Expr *callExpr = new (ctx) CallExpr(fnRef, argExpr, /*implicit*/true);
bool invalid = tc.typeCheckExpression(callExpr, cs.DC, valueType,
CTP_CannotFail);
assert(!invalid && "conversion cannot fail");
E->setSemanticExpr(callExpr);
return E;
}

Expand Down
13 changes: 12 additions & 1 deletion lib/Sema/CSGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2475,9 +2475,20 @@ namespace {
}

Type visitEditorPlaceholderExpr(EditorPlaceholderExpr *E) {
if (E->getTypeLoc().isNull()) {
auto locator = CS.getConstraintLocator(E);
auto placeholderTy = CS.createTypeVariable(locator, /*options*/0);
// A placeholder may have any type, but default to Void type if
// otherwise unconstrained.
CS.addConstraint(ConstraintKind::Defaultable,
placeholderTy, TupleType::getEmpty(CS.getASTContext()),
locator);
E->setType(placeholderTy);
}
// NOTE: The type loc may be there but have failed to validate, in which
// case we return the null type.
return E->getType();
}

};

/// \brief AST walker that "sanitizes" an expression for the
Expand Down
3 changes: 3 additions & 0 deletions lib/Sema/TypeCheckConstraints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1381,6 +1381,9 @@ bool TypeChecker::typeCheckBinding(Pattern *&pattern, Expr *&initializer,
TypeResolutionOptions options;
options |= TR_OverrideType;
options |= TR_InExpression;
if (isa<EditorPlaceholderExpr>(expr->getSemanticsProvidingExpr())) {
options |= TR_EditorPlaceholder;
}
if (tc.coercePatternToType(pattern, DC, patternType, options)) {
return nullptr;
}
Expand Down
3 changes: 2 additions & 1 deletion lib/Sema/TypeCheckPattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,8 @@ bool TypeChecker::coercePatternToType(Pattern *&P, DeclContext *dc, Type type,

if (shouldRequireType &&
!(options & TR_FromNonInferredPattern) &&
!(options & TR_EnumerationVariable)) {
!(options & TR_EnumerationVariable) &&
!(options & TR_EditorPlaceholder)) {
diagnose(NP->getLoc(), diag::type_inferred_to_undesirable_type,
NP->getDecl()->getName(), type, NP->getDecl()->isLet());
diagnose(NP->getLoc(), diag::add_explicit_type_annotation_to_silence);
Expand Down
3 changes: 3 additions & 0 deletions lib/Sema/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,9 @@ enum TypeResolutionFlags {
/// Whether we are in the inheritance clause of a nominal type declaration
/// or extension.
TR_InheritanceClause = 0x80000,

/// Whether this is the type of an editor placeholder.
TR_EditorPlaceholder = 0x100000,
};

/// Option set describing how type resolution should work.
Expand Down
9 changes: 9 additions & 0 deletions stdlib/public/core/AssertCommon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,12 @@ func _unimplemented_initializer(className: StaticString,

Builtin.int_trap()
}

@transparent @noreturn
public // COMPILER_INTRINSIC
func _undefined<T>(
@autoclosure message: () -> String = String(),
file: StaticString = __FILE__, line: UInt = __LINE__
) -> T {
_assertionFailed("fatal error", message(), file, line)
}
23 changes: 23 additions & 0 deletions test/PlaygroundTransform/placeholder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// RUN: rm -rf %t
// RUN: mkdir %t
// RUN: cp %s %t/main.swift
// RUN: %target-build-swift -Xfrontend -playground -Xfrontend -disable-playground-transform -o %t/main %t/main.swift
// RUN: %target-run %t/main | FileCheck %s
// RUN: not --crash %target-run %t/main --crash 2>&1 | FileCheck -check-prefix=CRASH-CHECK %s
// REQUIRES: executable_test

func f(crash crash: Bool) -> Int {
if crash {
return <#T#>
// CRASH-CHECK: fatal error: attempt to evaluate editor placeholder: file {{.*}}/main.swift, line [[@LINE-1]]
} else {
return 42
}
}

if Process.arguments.last == "--crash" {
print("the value is \(f(crash: true))")
} else {
print("the value is \(f(crash: false))")
// CHECK: the value is 42
}
14 changes: 14 additions & 0 deletions test/Sema/editor_placeholders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,17 @@ if (true) {
}

foo(<#T##x: Undeclared##Undeclared#>) // expected-error {{editor placeholder}} expected-error {{use of undeclared type 'Undeclared'}}

func f(n: Int) {}
let a1 = <#T#> // expected-error{{editor placeholder in source file}}
f(a1) // expected-error{{cannot convert value of type '()' to expected argument type 'Int'}}
let a2 = <#T##Int#> // expected-error{{editor placeholder in source file}}
f(a2)
<#T#> // expected-error{{editor placeholder in source file}}

// FIXME: <rdar://problem/22432828> Lexer yields "editor placeholder in source file" error twice when placeholder is first token
<#T##Int#> // expected-error 2{{editor placeholder in source file}}

f(<#T#> + 1) // expected-error{{editor placeholder in source file}}
f(<#T##Int#>) // expected-error{{editor placeholder in source file}}
f(<#T##String#>) // expected-error{{editor placeholder in source file}} expected-error{{cannot convert value of type 'String' to expected argument type 'Int'}}

0 comments on commit 0addd80

Please sign in to comment.