Skip to content

Commit

Permalink
Merge pull request #1627 from DanielXMoore/method-revamp
Browse files Browse the repository at this point in the history
Fix implicit `async` and `*` in methods
  • Loading branch information
edemaine authored Dec 2, 2024
2 parents 8511ec3 + 971901e commit 2e4e51e
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 128 deletions.
108 changes: 34 additions & 74 deletions source/parser.hera
Original file line number Diff line number Diff line change
Expand Up @@ -3727,60 +3727,22 @@ MethodDefinition
abstract: true,
signature,
parameters: signature.parameters,
ts: true
async: signature.async,
generator: signature.generator,
ts: true,
}
# NOTE: Not adding extra validation using PropertySetParameterList
# NOTE: If this node layout changes, be sure to update `convertMethodTOFunction`
MethodSignature:signature !(PropertyAccess / ExplicitPropertyGlob / UnaryPostfix / NonNullAssertion) BracedBlock?:block ->
let children = $0
let generatorPos = 0
let { modifier } = signature

if (hasAwait(block)) {
generatorPos++
children = children.slice()
// get or set
if (modifier?.get || modifier?.set) {
children.push({
type: "Error",
message: "Getters and setters cannot be async",
})
} else if(modifier?.async) {
// Do nothing, already async
} else {
// Insert implicit async
children.unshift("async ")
modifier = { ...modifier, async: true }
signature = { ...signature, modifier }
}
}

if (hasYield(block)) {
if (children === $0) children = children.slice()

// get or set
if (modifier?.get || modifier?.set) {
children.push({
type: "Error",
message: "Getters and setters cannot be generators",
})
} else if(modifier?.generator) {
// Do nothing, already generator
} else {
// Insert implicit generator
children.splice(generatorPos, 0, "*")
modifier = { ...modifier, generator: true }
signature = { ...signature, modifier }
}
}

return {
type: "MethodDefinition",
children,
children: $0,
name: signature.name,
signature,
block,
parameters: signature.parameters,
async: signature.async,
generator: signature.generator,
}

# shorthand get method definition
Expand Down Expand Up @@ -3863,33 +3825,31 @@ MethodDefinition

MethodModifier
# NOTE: Merged get/set definitions
GetOrSet:kind _? &ClassElementName ->
GetOrSet:kind _?:ws &ClassElementName ->
return {
type: "MethodModifier",
async: false,
generator: false,
get: kind.token === "get",
set: kind.token === "set",
children: $0,
// no async or generator, because getters and setters can't be
modifier: {
async: false,
generator: false,
get: kind.token === "get",
set: kind.token === "set",
},
children: [ kind, ws ],
}
# NOTE: Merged async and generator into MethodModifier
( Async __ ) ( Star __ )? ->
return {
type: "MethodModifier",
async: true,
get: false,
set: false,
generator: !!$2,
children: $0,
}
Star __ ->
( Async __ )?:async ( Star __ )?:generator ->
if (!async) async = []
if (!generator) generator = []
return {
type: "MethodModifier",
async: false,
get: false,
set: false,
generator: true,
children: $0,
async,
generator,
modifier: {
async: !!async.length,
get: false,
set: false,
generator: !!generator.length,
},
children: [ async, generator ],
}

# TypeScript method signature
Expand All @@ -3905,8 +3865,8 @@ MethodSignature
}

# NOTE: If this node layout changes, be sure to update
# `convertMethodToFunction` and `[3]` in code below
MethodModifier?:modifier ClassElementName:name _? QuestionMark?:optional _? NonEmptyParameters:parameters ReturnTypeSuffix?:returnType ->
# `convertMethodToFunction`
MethodModifier:modifier ClassElementName:name _?:ws1 QuestionMark?:optional _?:ws2 NonEmptyParameters:parameters ReturnTypeSuffix?:returnType ->
// Normalize name so we can check if it is `constructor`
if (name.name) {
name = name.name
Expand All @@ -3915,16 +3875,16 @@ MethodSignature
}

// TypeScript supports optional methods with bodies; remove ? from JS output
if (optional) $0[3] = optional = { ...optional, ts: true }

modifier = modifier || {}
if (optional) optional = { ...optional, ts: true }

return {
type: "MethodSignature",
children: $0,
children: [ ...modifier.children, name, ws1, optional, ws2, parameters, returnType ],
async: modifier.async,
generator: modifier.generator,
name,
optional,
modifier, // get/set/async/generator
modifier: modifier.modifier, // get/set/async/generator
returnType,
parameters,
}
Expand Down
34 changes: 18 additions & 16 deletions source/parser/function.civet
Original file line number Diff line number Diff line change
Expand Up @@ -897,39 +897,41 @@ function processSignature(f: FunctionNode): void
{block, signature} := f

if not f.async?# and hasAwait(block)
f.async.push "async "
signature.modifier.async = true
if f.async?
f.async.push "async "
signature.modifier.async = true
else
for each a of gatherRecursiveWithinFunction block, .type is "Await"
i := findChildIndex a.parent, a
// i+1 because after "await" we have a consistent location in sourcemap
a.parent!.children.splice i+1, 0,
type: "Error"
message: `await invalid in ${signature.modifier.get ? "getter" : signature.modifier.set ? "setter" : signature.name}` // name likely constructor

if not f.generator?# and hasYield(block)
if f.type is "ArrowFunction"
gatherRecursiveWithinFunction block, .type is "YieldExpression"
.forEach (y) =>
if f.generator?
f.generator.push "*"
signature.modifier.generator = true
else
for each y of gatherRecursiveWithinFunction block, .type is "YieldExpression"
i := y.children.findIndex .type is "Yield"
// i+1 because after "yield" we have a consistent location in sourcemap
y.children.splice i+1, 0,
type: "Error"
message: "Can't use yield inside of => arrow function"
else
f.generator.push "*"
signature.modifier.generator = true
message: `yield invalid in ${f.type is "ArrowFunction" ? "=> arrow function" : signature.modifier.get ? "getter" : signature.modifier.set ? "setter" : signature.name}` // name likely constructor

if signature.modifier.async and not signature.modifier.generator and
signature.returnType and not isPromiseType signature.returnType.t
replaceNode signature.returnType.t, wrapTypeInPromise signature.returnType.t

function processFunctions(statements, config): void
for each f of gatherRecursiveAll statements, .type is "FunctionExpression" or .type is "ArrowFunction"
if f.type is "FunctionExpression"
for each f of gatherRecursiveAll statements, .type is "FunctionExpression" or .type is "ArrowFunction" or .type is "MethodDefinition"
if f.type is "FunctionExpression" or f.type is "MethodDefinition"
implicitFunctionBlock(f)
processSignature(f)
processParams(f)
processReturn(f, config.implicitReturns)

for each f of gatherRecursiveAll statements, .type is "MethodDefinition"
implicitFunctionBlock(f)
processParams(f)
processReturn(f, config.implicitReturns)

function expressionizeIteration(exp: IterationExpression): void
{ async, generator, block, children, statement } .= exp
i := children.indexOf statement
Expand Down
64 changes: 30 additions & 34 deletions source/parser/lib.civet
Original file line number Diff line number Diff line change
Expand Up @@ -783,34 +783,29 @@ function lastAccessInCallExpression(exp)

// Given a MethodDefinition, convert into a FunctionExpression.
// Returns undefined if the method is a getter or setter.
function convertMethodToFunction(method) {
const { signature, block } = method
let { modifier, optional } = signature
if (optional) return
if (modifier) {
if (modifier.get or modifier.set) {
return
} else if (modifier.async) {
// put function after async
modifier = [modifier.children[0][0], " function ", ...modifier.children.slice(1)]
} else {
modifier = ["function ", ...(modifier.children or [])]
}
} else {
modifier = "function ";
}
function convertMethodToFunction(method)
{ signature, block } := method
{ async, modifier, optional } := signature
return if optional
return if modifier?.get or modifier?.set
func := ["function "]
if async?
// put `function` after `async`
func.unshift async
// ensure space after `async` (absent in e.g. `async* f()`)
if async# and not async.-1?.#
async.push " "
return {
...signature,
id: signature.name,
signature,
type: "FunctionExpression",
...signature
id: signature.name
signature
type: "FunctionExpression"
children: [
[modifier, ...signature.children.slice(1)],
block,
],
block,
[...func, ...signature.children.slice(1)]
block
]
block
}
}

// Convert NamedImports into equivalent ObjectExpression or ObjectBindingPattern
function convertNamedImportsToObject(node, pattern?: boolean)
Expand Down Expand Up @@ -906,6 +901,7 @@ function makeGetterMethod(name, ws, value, returnType, block?: BlockStatement, k
type: "BlockStatement"
expressions
children: ["{ ", expressions, " }"]
bare: false
}

if autoReturn
Expand All @@ -919,21 +915,21 @@ function makeGetterMethod(name, ws, value, returnType, block?: BlockStatement, k
children := [kind, " ", name, parameters, returnType, block]

return {
type: "MethodDefinition",
children,
name,
type: "MethodDefinition"
children
name
signature: {
type: "MethodSignature"
modifier: {
get: token is "get"
set: token is "set"
async: false
},
name,
returnType,
},
block,
parameters,
}
name
returnType
}
block
parameters
}

function processBindingPatternLHS(lhs, tail): void
Expand Down
34 changes: 34 additions & 0 deletions test/class.civet
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,23 @@ describe "class", ->
}
"""

testCase """
async do shields await
---
class A
f()
async do
await 3
---
class A {
f() {
return (async ()=>{{
return await 3
}})()
}
}
"""

describe "implicit generator", ->
testCase """
implicit generator
Expand Down Expand Up @@ -1087,6 +1104,23 @@ describe "class", ->
}
"""

testCase """
do* shields yield
---
class A
f()
do*
yield 3
---
class A {
f() {
return (function*(){{
yield 3
}})()
}
}
"""

describe "implicit async generator", ->
testCase """
implicit async generator
Expand Down
8 changes: 4 additions & 4 deletions test/function.civet
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,15 @@ describe "function", ->
---
=> yield 5
---
ParseErrors: unknown:1:9 Can't use yield inside of => arrow function
ParseErrors: unknown:1:9 yield invalid in => arrow function
"""

throws """
yield forbidden within thick arrow, inlineMap
---
=> yield 5
---
ParseErrors: unknown:1:9 Can't use yield inside of => arrow function
ParseErrors: unknown:1:9 yield invalid in => arrow function
""", inlineMap: true

throws """
Expand All @@ -256,8 +256,8 @@ describe "function", ->
yield 1
yield 2
---
ParseErrors: unknown:2:8 Can't use yield inside of => arrow function
unknown:3:8 Can't use yield inside of => arrow function
ParseErrors: unknown:2:8 yield invalid in => arrow function
unknown:3:8 yield invalid in => arrow function
"""

testCase """
Expand Down

0 comments on commit 2e4e51e

Please sign in to comment.