Skip to content

[WIP] Property behaviors #23292

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4597,6 +4597,10 @@ class VarDecl : public AbstractStorageDecl {

Type typeInContext;

/// Property behavior information.
SourceLoc byLoc;
TypeLoc behaviorTypeLoc;

public:
VarDecl(bool IsStatic, Specifier Sp, bool IsCaptureList, SourceLoc NameLoc,
Identifier Name, DeclContext *DC)
Expand Down Expand Up @@ -4821,6 +4825,24 @@ class VarDecl : public AbstractStorageDecl {
Bits.VarDecl.IsREPLVar = IsREPLVar;
}

/// Whether this variable has a property behavior attached.
bool hasPropertyBehavior() const {
return !behaviorTypeLoc.isNull();
}

SourceLoc getPropertyBehaviorByLoc() const {
return byLoc;
}

TypeLoc &getPropertyBehaviorTypeLoc() {
return behaviorTypeLoc;
}

void setPropertyBehavior(SourceLoc byLoc, TypeLoc typeLoc) {
this->byLoc = byLoc;
this->behaviorTypeLoc = typeLoc;
}

/// Return the Objective-C runtime name for this property.
Identifier getObjCPropertyName() const;

Expand Down
8 changes: 8 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1620,6 +1620,14 @@ ERROR(pound_available_package_description_not_allowed, none,
ERROR(availability_query_repeated_platform, none,
"version for '%0' already specified", (StringRef))

//------------------------------------------------------------------------------
// MARK: property behavior diagnostics
//------------------------------------------------------------------------------
ERROR(property_behavior_not_named, none,
"property behavior can only by written on a single-variable pattern", ())
ERROR(expected_behavior_type_after_by,PointsToFirstBadToken,
"expected behavior type after 'by'", ())

//------------------------------------------------------------------------------
// MARK: syntax parsing diagnostics
//------------------------------------------------------------------------------
Expand Down
21 changes: 21 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4269,6 +4269,27 @@ WARNING(hashvalue_implementation,none,
"conform type %0 to 'Hashable' by implementing 'hash(into:)' instead",
(Type))

//------------------------------------------------------------------------------
// MARK: property behavior diagnostics
//------------------------------------------------------------------------------
ERROR(property_behavior_not_unbound, none,
"property behavior must name a generic type without any generic arguments", ())
ERROR(property_behavior_not_single_parameter, none,
"property behavior must refer to a single-parameter generic type", ())
ERROR(property_behavior_no_value_property, none,
"property behavior type %0 does not contain a non-static property "
"named 'value'", (Type))
ERROR(property_behavior_ambiguous_value_property, none,
"property behavior type %0 has multiple non-static properties "
"named 'value'", (Type))
ERROR(property_behavior_unwrap_not_declaration, none,
"subject of unwrap suppression '^' must refer to a property or variable",
())
ERROR(property_behavior_extra_suppress_unwrap, none,
"extraneous unwrap suppression operator '^'", ())
ERROR(property_behavior_cyclic, none,
"cyclic property behavior definition: %0", (StringRef))

#ifndef DIAG_NO_UNDEF
# if defined(DIAG)
# undef DIAG
Expand Down
36 changes: 34 additions & 2 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -1665,14 +1665,18 @@ class MemberRefExpr : public LookupExpr {
SourceLoc getLoc() const { return NameLoc.getBaseNameLoc(); }
SourceLoc getStartLoc() const {
SourceLoc BaseStartLoc = getBase()->getStartLoc();
if (BaseStartLoc.isInvalid() || NameLoc.isInvalid()) {
if (BaseStartLoc.isInvalid()) {
return NameLoc.getBaseNameLoc();
} else {
return BaseStartLoc;
}
}
SourceLoc getEndLoc() const {
return NameLoc.getSourceRange().End;
SourceLoc NameEndLoc = NameLoc.getSourceRange().End;
if (NameEndLoc.isValid())
return NameEndLoc;

return getBase()->getEndLoc();
}

static bool classof(const Expr *E) {
Expand Down Expand Up @@ -5344,6 +5348,34 @@ class KeyPathDotExpr : public Expr {
}
};

/// Tightly-binding prefix ^ operator that suppresses the implicit
/// "unwrapping" of a reference to a property that has an associated
/// property behavior.
class SuppressUnwrapExpr : public IdentityExpr {
SourceLoc CaretLoc;

public:
SuppressUnwrapExpr(SourceLoc caretLoc, Expr *subExpr, Type ty = Type())
: IdentityExpr(ExprKind::SuppressUnwrap, subExpr, ty),
CaretLoc(caretLoc) { }

SourceLoc getCaretLoc() const { return CaretLoc; }
SourceLoc getLoc() const { return getStartLoc(); }

SourceLoc getStartLoc() const {
if (CaretLoc.isValid())
return CaretLoc;

return getSubExpr()->getStartLoc();
}

SourceLoc getEndLoc() const { return getSubExpr()->getEndLoc(); }

static bool classof(const Expr *E) {
return E->getKind() == ExprKind::SuppressUnwrap;
}
};

inline bool Expr::isInfixOperator() const {
return isa<BinaryExpr>(this) || isa<IfExpr>(this) ||
isa<AssignExpr>(this) || isa<ExplicitCastExpr>(this);
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/ExprNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ UNCHECKED_EXPR(UnresolvedDot, Expr)
UNCHECKED_EXPR(Sequence, Expr)
ABSTRACT_EXPR(Identity, Expr)
EXPR(Paren, IdentityExpr)
EXPR(SuppressUnwrap, IdentityExpr)
EXPR(DotSelf, IdentityExpr)
EXPR_RANGE(Identity, Paren, DotSelf)
ABSTRACT_EXPR(AnyTry, Expr)
Expand Down
2 changes: 2 additions & 0 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,8 @@ class Parser {
bool periodHasKeyPathBehavior,
bool &hasBindOptional);
ParserResult<Expr> parseExprPostfix(Diag<> ID, bool isExprBasic);
ParserResult<Expr> parseExprPostfix(ParserResult<Expr> primary,
bool isExprBasic);
ParserResult<Expr> parseExprPrimary(Diag<> ID, bool isExprBasic);
ParserResult<Expr> parseExprUnary(Diag<> ID, bool isExprBasic);
ParserResult<Expr> parseExprKeyPathObjC();
Expand Down
6 changes: 6 additions & 0 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2693,6 +2693,12 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
printRec(E->getBody(), E->getVar()->getDeclContext()->getASTContext());
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}

void visitSuppressUnwrapExpr(SuppressUnwrapExpr *E) {
printCommon(E, "suppress_unwrap_expr") << '\n';
printRec(E->getSubExpr());
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}
};

} // end anonymous namespace
Expand Down
11 changes: 11 additions & 0 deletions lib/AST/ASTWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,17 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,
return E;
}

Expr *visitSuppressUnwrapExpr(SuppressUnwrapExpr *E) {
if (Expr *arg = E->getSubExpr()) {
if (Expr *arg2 = doIt(arg)) {
E->setSubExpr(arg2);
} else {
return nullptr;
}
}
return E;
}

//===--------------------------------------------------------------------===//
// Everything Else
//===--------------------------------------------------------------------===//
Expand Down
2 changes: 2 additions & 0 deletions lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ ConcreteDeclRef Expr::getReferencedDecl() const {
NO_REFERENCE(KeyPath);
NO_REFERENCE(KeyPathDot);
NO_REFERENCE(Tap);
PASS_THROUGH_REFERENCE(SuppressUnwrap, getSubExpr);

#undef SIMPLE_REFERENCE
#undef NO_REFERENCE
Expand Down Expand Up @@ -544,6 +545,7 @@ bool Expr::canAppendPostfixExpression(bool appendingPostfixOperator) const {
case ExprKind::MagicIdentifierLiteral:
case ExprKind::ObjCSelector:
case ExprKind::KeyPath:
case ExprKind::SuppressUnwrap:
return true;

case ExprKind::ObjectLiteral:
Expand Down
24 changes: 22 additions & 2 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3330,7 +3330,8 @@ parseIdentifierDeclName(Parser &P, Identifier &Result, SourceLoc &Loc,
// We parsed an identifier for the declaration. If we see another
// identifier, it might've been a single identifier that got broken by a
// space or newline accidentally.
if (P.Tok.isIdentifierOrUnderscore() && !P.Tok.isContextualDeclKeyword())
if (P.Tok.isIdentifierOrUnderscore() && !P.Tok.isContextualDeclKeyword() &&
!P.Tok.isContextualKeyword("by"))
P.diagnoseConsecutiveIDs(Result.str(), Loc, DeclKindName);

// Return success anyway
Expand Down Expand Up @@ -5215,7 +5216,26 @@ Parser::parseDeclVar(ParseDeclOptions Flags,

pattern = patternRes.get();
}


// Parse a property behavior.
if (Tok.isContextualKeyword("by")) {
SourceLoc byLoc = consumeToken();
ParserResult<TypeRepr> behaviorType =
parseType(diag::expected_behavior_type_after_by);
if (behaviorType.hasCodeCompletion())
return makeResult(makeParserCodeCompletionStatus());

if (behaviorType.isNonNull()) {
if (auto var = pattern->getSingleVar()) {
var->setPropertyBehavior(byLoc, behaviorType.get());
} else {
// FIXME: Support AnyPattern as well, somehow.
diagnose(byLoc, diag::property_behavior_not_named)
.highlight(pattern->getSourceRange());
}
}
}

// Configure all vars with attributes, 'static' and parent pattern.
pattern->forEachVariable([&](VarDecl *VD) {
VD->setStatic(StaticLoc.isValid());
Expand Down
39 changes: 33 additions & 6 deletions lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,8 @@ static Expr *formUnaryArgument(ASTContext &context, Expr *argument) {
/// expr-postfix(Mode)
/// operator-prefix expr-unary(Mode)
/// '&' expr-unary(Mode)
/// '^' expr-identifier
/// '^' expr-paren
///
ParserResult<Expr> Parser::parseExprUnary(Diag<> Message, bool isExprBasic) {
SyntaxParsingContext UnaryContext(SyntaxContext, SyntaxContextKind::Expr);
Expand Down Expand Up @@ -507,8 +509,28 @@ ParserResult<Expr> Parser::parseExprUnary(Diag<> Message, bool isExprBasic) {
// expression (and that may not always be an expression).
diagnose(Tok, diag::invalid_postfix_operator);
Tok.setKind(tok::oper_prefix);
LLVM_FALLTHROUGH;
Operator = parseExprOperator();
break;
case tok::oper_prefix:
/// Parse the unwrap suppression operator.
if (Tok.isContextualPunctuator("^") &&
peekToken().isAny(tok::identifier, tok::kw_self, tok::l_paren)) {
SourceLoc CaretLoc = consumeToken();

// Parse the subexpression.
ParserResult<Expr> SubExpr = parseExprPrimary(Message, isExprBasic);
ParserStatus Status = SubExpr;
if (SubExpr.isNull())
return Status;

// We are sure we can create a prefix operator expr now.
UnaryContext.setCreateSyntax(SyntaxKind::PrefixOperatorExpr);

auto Result = makeParserResult(
Status, new (Context) SuppressUnwrapExpr(CaretLoc, SubExpr.get()));
return parseExprPostfix(Result, isExprBasic);
}

Operator = parseExprOperator();
break;
case tok::oper_binary_spaced:
Expand Down Expand Up @@ -1396,20 +1418,25 @@ ParserResult<Expr> Parser::parseExprPostfix(Diag<> ID, bool isExprBasic) {
if (Result.isNull())
return Result;

return parseExprPostfix(Result, isExprBasic);
}

ParserResult<Expr> Parser::parseExprPostfix(ParserResult<Expr> primary,
bool isExprBasic) {
bool hasBindOptional = false;
Result = parseExprPostfixSuffix(Result, isExprBasic,
primary = parseExprPostfixSuffix(primary, isExprBasic,
/*periodHasKeyPathBehavior=*/InSwiftKeyPath,
hasBindOptional);
if (Result.isParseError() || Result.hasCodeCompletion())
return Result;
if (primary.isParseError() || primary.hasCodeCompletion())
return primary;

// If we had a ? suffix expression, bind the entire postfix chain
// within an OptionalEvaluationExpr.
if (hasBindOptional) {
Result = makeParserResult(new (Context) OptionalEvaluationExpr(Result.get()));
primary = makeParserResult(new (Context) OptionalEvaluationExpr(primary.get()));
}

return Result;
return primary;
}

/// parseExprPrimary
Expand Down
3 changes: 2 additions & 1 deletion lib/Parse/ParsePattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,8 @@ ParserResult<Pattern> Parser::parsePattern() {
PatternCtx.setCreateSyntax(SyntaxKind::IdentifierPattern);
Identifier name;
SourceLoc loc = consumeIdentifier(&name);
if (Tok.isIdentifierOrUnderscore() && !Tok.isContextualDeclKeyword())
if (Tok.isIdentifierOrUnderscore() && !Tok.isContextualDeclKeyword() &&
!Tok.isContextualKeyword("by"))
diagnoseConsecutiveIDs(name.str(), loc, isLet ? "constant" : "variable");

return makeParserResult(createBindingFromPattern(loc, name, specifier));
Expand Down
Loading