Repository: hylo-lang/hylo (swift)

Issue:
Cannot call Int methods on literal
This program should compile:
```
public fun main() {
  _ = 1.copy() // error: undefined name 'copy' in this scope
}
```

Commit: 289ed31a49747a3bfbec14547e0ace165d72e3e0

Pull request description:
This PR fixes #1037

Patch:
diff --git a/Sources/FrontEnd/Parse/Parser.swift b/Sources/FrontEnd/Parse/Parser.swift
index 4156e8997..cdee41686 100644
--- a/Sources/FrontEnd/Parse/Parser.swift
+++ b/Sources/FrontEnd/Parse/Parser.swift
@@ -1627,18 +1627,7 @@ public enum Parser {
 
     case .int:
       // Integer or Float literal.
-
-      // Try to parse a float literal first.
-      if let expr = try parseFloatLiteralExpr(in: &state) {
-        return AnyExprID(expr)
-      }
-
-      // Note: state.take(.int) is called in `parseFloatLiteralExpr(in:)`.
-      let expr = state.insert(
-        IntegerLiteralExpr(
-          value: state.lexer.sourceCode[head.site].filter({ $0 != "_" }),
-          site: head.site))
-      return AnyExprID(expr)
+      return try parseIntegerOrFloatLiteralExpr(in: &state)
 
     case .string:
       // String literal.
@@ -1716,27 +1705,36 @@ public enum Parser {
     }
   }
 
-  private static func parseFloatLiteralExpr(in state: inout ParserState) throws -> FloatLiteralExpr
-    .ID?
-  {
-    guard let first = state.take(.int) else { return nil }
+  /// Parses an integer or float literal expression from `state`.
+  private static func parseIntegerOrFloatLiteralExpr(
+    in state: inout ParserState
+  ) throws -> AnyExprID? {
+    guard let integer = state.take(.int) else { return nil }
 
-    if state.take(if: { $0.kind == .dot }) != nil {
-      guard state.take(if: { $0.kind == .int }) != nil else { return nil }
-    } else {
-      if state.peek()?.kind != .exponent {
-        return nil
-      }
+    if let e = parseFloatLiteralExpr(after: integer, in: &state) {
+      return AnyExprID(e)
     }
 
-    _ = state.take(if: { $0.kind == .exponent })
+    let e = state.insert(
+      IntegerLiteralExpr(
+        value: state.lexer.sourceCode[integer.site].filter({ $0 != "_" }),
+        site: integer.site))
+    return AnyExprID(e)
+  }
+
+  /// Parses a float literal expression from `state`, assuming it has the given `integerPart`.
+  private static func parseFloatLiteralExpr(
+    after integerPart: Token, in state: inout ParserState
+  ) -> FloatLiteralExpr.ID? {
+    let i = state.currentIndex
+    _ = state.take(.dot, .int) != nil
+    _ = state.take(.exponent)
 
-    let site = first.site.extended(upTo: state.currentIndex)
+    if state.currentIndex == i { return nil }
 
+    let s = integerPart.site.extended(upTo: state.currentIndex)
     return state.insert(
-      FloatLiteralExpr(
-        value: state.lexer.sourceCode[site].filter({ $0 != "_" }),
-        site: site))
+      FloatLiteralExpr(value: state.lexer.sourceCode[s].filter({ $0 != "_" }), site: s))
   }
 
   private static func parseExistentialTypeExpr(
diff --git a/Sources/FrontEnd/Parse/ParserState.swift b/Sources/FrontEnd/Parse/ParserState.swift
index c5a8103e2..704923898 100644
--- a/Sources/FrontEnd/Parse/ParserState.swift
+++ b/Sources/FrontEnd/Parse/ParserState.swift
@@ -139,7 +139,7 @@ struct ParserState {
     return token
   }
 
-  /// Fills the lookahead buffer until it contains `n` tokens, or fewer if the lexer is exhausted.
+  /// Returns up to the next `n` next tokens without consuming them.
   mutating func peek(_ n: Int) -> Deque<Token>.SubSequence {
     while lookahead.count < n {
       guard let t = lexer.next() else { break }


Test patch:
diff --git a/Tests/HyloTests/ParserTests.swift b/Tests/HyloTests/ParserTests.swift
index 6932dd8aa..28085b8ba 100644
--- a/Tests/HyloTests/ParserTests.swift
+++ b/Tests/HyloTests/ParserTests.swift
@@ -949,6 +949,14 @@ final class ParserTests: XCTestCase {
     }
   }
 
+  func testMethodCallOnIntegerLiteral() throws {
+    // See #1037
+    let input: SourceFile = "1.copy()"
+    let (e, ast) = try input.parse(with: Parser.parseExpr(in:))
+    let call = try XCTUnwrap(ast[e] as? FunctionCallExpr)
+    XCTAssertEqual(input[ast[call.callee].site], "1.copy")
+  }
+
   // MARK: Compound expressions
 
   func testLabeledMemberExpr() throws {