Skip to content
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

Add Double and Int Convenience Properties #234

Closed
wants to merge 6 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Changelog.md
Expand Up @@ -4,6 +4,8 @@ Note: This is in reverse chronological order, so newer entries are added to the

## Swift 5.3

* Introduced `integerValue` and `floatingValue` properties to `IntegerLiteralExprSyntax` and `FloatLiteralExprSyntax`, respectively. Converted their `digits` and `floatingDigits` setters, respectively, into throwing functions.

* Introduced `FunctionCallExprSyntax.additionalTrailingClosures` property with type `MultipleTrailingClosureElementListSyntax?` for supporting [SE-0279 Multiple Trailing Closures](https://github.com/apple/swift-evolution/blob/master/proposals/0279-multiple-trailing-closures.md).

* Introduced `syntaxNodeType` property for all types conforming to `SyntaxProtocol`, which returns the underlying syntax node type. It is primarily intended as a debugging aid during development.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -181,7 +181,7 @@ swift/utils/build-script --swiftsyntax --swiftpm --llbuild -t --skip-test-cmark
```
This command will build SwiftSyntax and all its dependencies, tell the build script to run tests, but skip all tests but the SwiftSyntax tests.

Note that it is not currently supported to SwiftSyntax while building the Swift compiler using Xcode.
Note that it is not currently supported to build SwiftSyntax while building the Swift compiler using Xcode.

### CI Testing

Expand Down
4 changes: 4 additions & 0 deletions Sources/SwiftSyntax/SyntaxBuilders.swift.gyb
Expand Up @@ -85,7 +85,11 @@ extension ${node.name} {
/// incrementally build the structure of the node.
/// - Returns: A `${node.name}` with all the fields populated in the builder
/// closure.
% if node.must_uphold_invariant:
public init?(_ build: (inout ${Builder}) -> Void) {
% else:
public init(_ build: (inout ${Builder}) -> Void) {
% end
var builder = ${Builder}()
build(&builder)
let data = builder.buildData()
Expand Down
80 changes: 80 additions & 0 deletions Sources/SwiftSyntax/SyntaxConvenienceMethods.swift
@@ -0,0 +1,80 @@
//===- SyntaxConvenienceMethods.swift - Convenience funcs for syntax nodes ===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

public extension FloatLiteralExprSyntax {
var floatingValue: Double {
return potentialFloatingValue!
}

fileprivate var potentialFloatingValue: Double? {
let floatingDigitsWithoutUnderscores = floatingDigits.text.filter {
$0 != "_"
}
return Double(floatingDigitsWithoutUnderscores)
}
}

public extension IntegerLiteralExprSyntax {
var integerValue: Int {
return potentialIntegerValue!
}

fileprivate var potentialIntegerValue: Int? {
let text = digits.text
let (prefixLength, radix) = IntegerLiteralExprSyntax.prefixLengthAndRadix(text: text)
let digitsStartIndex = text.index(text.startIndex, offsetBy: prefixLength)
let textWithoutPrefix = text.suffix(from: digitsStartIndex)

let textWithoutPrefixOrUnderscores = textWithoutPrefix.filter {
$0 != "_"
Copy link
Member

@rintaro rintaro Aug 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, _ at the start of each component is not valid. e.g. _12, 0x_12, 0._12 and 12e_3. But I think it's OK to accept them for now.
@akyrtzi What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine but adding a FIXME comment here would be good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rintaro I observe that _12 and 0x_2 don't parse as IntegerLiteralExprSyntax or FloatLiteralExprSyntax, respectively. My properties therefore shouldn't get those sorts of input. Should the properties nonetheless fatalError() if the _ is the first character after the base prefix (or is the first character in the case of base 10)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @akyrtzi said in another comment, we should prevent a case of creating syntax node with invalid literal text. If you do that, you don't need to check _ in this integerValue or floatingValue accessor.

Also, when you check them, you need to check not only for the integral part, but you also need to check the fractional part and the exponent part. That's not so simple to implement in this PR. So I suggest you to add a FIXME comment as @akyrtzi said.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that for now it’s sufficient if we uphold the invariant that the contents will of a integer / float literal are always parsable from the integerValue resp. floatingValue properties, especially since the implementation is nice and short so far!

That should already be an improvement over the current behaviour and if we add a FIXME, we can come back to it and improve it later.

}

return Int(textWithoutPrefixOrUnderscores, radix: radix)
}

private static func prefixLengthAndRadix(text: String) -> (Int, Int) {
let nonDecimalPrefixLength = 2

let binaryPrefix = "0b"
let octalPrefix = "0o"
let decimalPrefix = ""
let hexadecimalPrefix = "0x"

let binaryRadix = 2
let octalRadix = 8
let decimalRadix = 10
let hexadecimalRadix = 16

switch String(text.prefix(nonDecimalPrefixLength)) {
case binaryPrefix:
return (binaryPrefix.count, binaryRadix)
case octalPrefix:
return (octalPrefix.count, octalRadix)
case hexadecimalPrefix:
return (hexadecimalPrefix.count, hexadecimalRadix)
default:
return (decimalPrefix.count, decimalRadix)
}
}
}

public extension IntegerLiteralExprSyntax {
var isValid: Bool {
potentialIntegerValue != nil
}
}

public extension FloatLiteralExprSyntax {
var isValid: Bool {
potentialFloatingValue != nil
}
}
6 changes: 5 additions & 1 deletion Sources/SwiftSyntax/SyntaxFactory.swift.gyb
Expand Up @@ -57,7 +57,11 @@ public enum SyntaxFactory {
% param_type = param_type + "?"
% child_params.append("%s: %s" % (child.swift_name, param_type))
% child_params = ', '.join(child_params)
% if node.must_uphold_invariant:
public static func make${node.syntax_kind}(${child_params}) -> ${node.name}? {
% else:
public static func make${node.syntax_kind}(${child_params}) -> ${node.name} {
% end
let layout: [RawSyntax?] = [
% for child in node.children:
% if child.is_optional:
Expand All @@ -82,7 +86,7 @@ public enum SyntaxFactory {
}
% end

% if not node.is_base():
% if not node.is_base() and not node.must_uphold_invariant:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, I don’t think it’s correct to remove the makeBlank method if a node must uphold an invariant.

In the future we might add invariants to other nodes as well that could have a valid blank content. One example that jumps to my mind would be string literals. For consistency, I would rather have this method return an Optional and return nil if the node kind has no valid blank representation.

@akyrtzi What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to step back a bit here, what is the use case for the makeBlank methods, why do we need them? When would I call a makeBlank method instead of explicitly creating the syntax node that I want?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Paging @harlanhaskins for my question.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry! I think makeBlank was meant to be used for e.g. error recovery where we'd just fill in missing tokens with blank values in order to send a well-formed-but-invalid AST to semantic analysis.

public static func makeBlank${node.syntax_kind}() -> ${node.name} {
let data = SyntaxData.forRoot(RawSyntax.create(kind: .${node.swift_syntax_kind},
layout: [
Expand Down
42 changes: 42 additions & 0 deletions Sources/SwiftSyntax/SyntaxNodes.swift.gyb.template
Expand Up @@ -70,14 +70,29 @@ public struct ${node.name}: ${base_type}Protocol, SyntaxHashable {
public init?(_ syntax: Syntax) {
guard syntax.raw.kind == .${node.swift_syntax_kind} else { return nil }
self._syntaxNode = syntax
% if node.must_uphold_invariant:
if !isValid {
fatalError("Instance of ${node.name} is invalid.")
}
% end
ahoppen marked this conversation as resolved.
Show resolved Hide resolved
}

/// Creates a `${node.name}` node from the given `SyntaxData`. This assumes
/// that the `SyntaxData` is of the correct kind. If it is not, the behaviour
/// is undefined.
% if node.must_uphold_invariant:
/// This initializer returns nil if the invariant is not satisfied.
internal init?(_ data: SyntaxData) {
% else:
ahoppen marked this conversation as resolved.
Show resolved Hide resolved
internal init(_ data: SyntaxData) {
% end
assert(data.raw.kind == .${node.swift_syntax_kind})
self._syntaxNode = Syntax(data)
% if node.must_uphold_invariant:
if !isValid {
return nil
}
% end
}

public var syntaxNodeType: SyntaxProtocol.Type {
Expand Down Expand Up @@ -107,10 +122,33 @@ public struct ${node.name}: ${base_type}Protocol, SyntaxHashable {
% end
return ${child.type_name}(childData!)
}
% if not node.must_uphold_invariant:
set(value) {
self = with${child.name}(value)
}
% end
}
% if node.must_uphold_invariant:

public enum ${child.name}Error: Error, CustomStringConvertible {
case invalid(${child.swift_name}: ${ret_type})

public var description: String {
switch self {
case .invalid(let ${child.swift_name}):
return "attempted to use setter with invalid ${child.name} \"\(${child.swift_name})\""
}
}
}

mutating public func set${child.name}(_ ${child.swift_name}: ${ret_type}) throws {
if let childSyntax = with${child.name}(${child.swift_name}) {
self = childSyntax
} else {
throw ${child.name}Error.invalid(${child.swift_name}: ${child.swift_name})
}
}
% end
%
% # ===============
% # Adding children
Expand Down Expand Up @@ -149,7 +187,11 @@ public struct ${node.name}: ${base_type}Protocol, SyntaxHashable {
/// - param newChild: The new `${child.swift_name}` to replace the node's
/// current `${child.swift_name}`, if present.
public func with${child.name}(
% if node.must_uphold_invariant:
_ newChild: ${child.type_name}?) -> ${node.name}? {
% else:
_ newChild: ${child.type_name}?) -> ${node.name} {
% end
% if child.is_optional:
let raw = newChild?.raw
% else:
Expand Down
5 changes: 5 additions & 0 deletions Sources/SwiftSyntax/SyntaxRewriter.swift.gyb
Expand Up @@ -91,7 +91,12 @@ open class SyntaxRewriter {
if let newNode = visitAny(node._syntaxNode) { return newNode }
return Syntax(visit(node))
% else:
% if node.must_uphold_invariant:
// We know that the SyntaxData is valid since we are walking a valid syntax tree and haven't modified the syntax data. Thus the initializer below will never return nil.
let node = ${node.name}(data)!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment like the following here to document why the use of the ! is safe here?

// We know that the SyntaxData is valid since we are walking a valid syntax tree and haven't modified the syntax data.  Thus the below initialiser will never return nil.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you forgot to add the comment ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about that. Fixed.

% else:
let node = ${node.name}(data)
% end
// Accessing _syntaxNode directly is faster than calling Syntax(node)
visitPre(node._syntaxNode)
defer { visitPost(node._syntaxNode) }
Expand Down
5 changes: 5 additions & 0 deletions Sources/SwiftSyntax/SyntaxVisitor.swift.gyb
Expand Up @@ -86,7 +86,12 @@ open class SyntaxVisitor {
}
visitPost(node)
% else:
% if node.must_uphold_invariant:
// We know that the SyntaxData is valid since we are walking a valid syntax tree and haven't modified the syntax data. Thus the initializer below will never return nil.
let node = ${node.name}(data)!
ahoppen marked this conversation as resolved.
Show resolved Hide resolved
% else:
let node = ${node.name}(data)
% end
let needsChildren = (visit(node) == .visitChildren)
// Avoid calling into visitChildren if possible.
if needsChildren && node.raw.numberOfChildren > 0 {
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftSyntax/gyb_generated/Misc.swift
Expand Up @@ -1944,6 +1944,6 @@ extension Syntax {
extension SyntaxParser {
static func verifyNodeDeclarationHash() -> Bool {
return String(cString: swiftparse_syntax_structure_versioning_identifier()!) ==
"26709743ccc5ea2001419f44996c8fa671901c03"
"fd719aaf2e0dab2620af2d4d123d123b3d0de28d"
}
}
17 changes: 6 additions & 11 deletions Sources/SwiftSyntax/gyb_generated/SyntaxBuilders.swift
Expand Up @@ -274,7 +274,7 @@ public struct AwaitExprSyntaxBuilder {

internal mutating func buildData() -> SyntaxData {
if (layout[0] == nil) {
layout[0] = RawSyntax.missingToken(TokenKind.identifier(""))
layout[0] = RawSyntax.missingToken(TokenKind.awaitKeyword)
}
if (layout[1] == nil) {
layout[1] = RawSyntax.missing(SyntaxKind.expr)
Expand Down Expand Up @@ -1077,7 +1077,7 @@ extension FloatLiteralExprSyntax {
/// incrementally build the structure of the node.
/// - Returns: A `FloatLiteralExprSyntax` with all the fields populated in the builder
/// closure.
public init(_ build: (inout FloatLiteralExprSyntaxBuilder) -> Void) {
public init?(_ build: (inout FloatLiteralExprSyntaxBuilder) -> Void) {
var builder = FloatLiteralExprSyntaxBuilder()
build(&builder)
let data = builder.buildData()
Expand Down Expand Up @@ -1444,7 +1444,7 @@ extension IntegerLiteralExprSyntax {
/// incrementally build the structure of the node.
/// - Returns: A `IntegerLiteralExprSyntax` with all the fields populated in the builder
/// closure.
public init(_ build: (inout IntegerLiteralExprSyntaxBuilder) -> Void) {
public init?(_ build: (inout IntegerLiteralExprSyntaxBuilder) -> Void) {
var builder = IntegerLiteralExprSyntaxBuilder()
build(&builder)
let data = builder.buildData()
Expand Down Expand Up @@ -1917,7 +1917,7 @@ extension ClosureParamSyntax {

public struct ClosureSignatureSyntaxBuilder {
private var layout =
Array<RawSyntax?>(repeating: nil, count: 6)
Array<RawSyntax?>(repeating: nil, count: 5)

internal init() {}

Expand All @@ -1931,11 +1931,6 @@ public struct ClosureSignatureSyntaxBuilder {
layout[idx] = node.raw
}

public mutating func useAsyncKeyword(_ node: TokenSyntax) {
let idx = ClosureSignatureSyntax.Cursor.asyncKeyword.rawValue
layout[idx] = node.raw
}

public mutating func useThrowsTok(_ node: TokenSyntax) {
let idx = ClosureSignatureSyntax.Cursor.throwsTok.rawValue
layout[idx] = node.raw
Expand All @@ -1952,8 +1947,8 @@ public struct ClosureSignatureSyntaxBuilder {
}

internal mutating func buildData() -> SyntaxData {
if (layout[5] == nil) {
layout[5] = RawSyntax.missingToken(TokenKind.inKeyword)
if (layout[4] == nil) {
layout[4] = RawSyntax.missingToken(TokenKind.inKeyword)
}

return .forRoot(RawSyntax.createAndCalcLength(kind: .closureSignature,
Expand Down
12 changes: 2 additions & 10 deletions Sources/SwiftSyntax/gyb_generated/SyntaxClassification.swift
Expand Up @@ -65,16 +65,8 @@ extension SyntaxClassification {
// Separate checks for token nodes (most common checks) versus checks for layout nodes.
if childKind == .token {
switch (parentKind, indexInParent) {
case (.awaitExpr, 0):
return (.keyword, false)
case (.arrowExpr, 0):
return (.keyword, false)
case (.closureSignature, 2):
return (.keyword, false)
case (.expressionSegment, 2):
return (.stringInterpolationAnchor, true)
case (.functionSignature, 1):
return (.keyword, false)
case (.ifConfigClause, 0):
return (.buildConfigId, false)
case (.ifConfigDecl, 1):
Expand All @@ -93,8 +85,6 @@ extension SyntaxClassification {
return (.typeIdentifier, false)
case (.someType, 0):
return (.keyword, false)
case (.functionType, 3):
return (.keyword, false)
case (.availabilityVersionRestriction, 0):
return (.keyword, false)
default: return nil
Expand Down Expand Up @@ -222,6 +212,8 @@ extension RawTokenKind {
return .keyword
case .throwsKeyword:
return .keyword
case .awaitKeyword:
return .keyword
case .__file__Keyword:
return .keyword
case .__line__Keyword:
Expand Down