Skip to content

Commit

Permalink
return.value inherits return type of function
Browse files Browse the repository at this point in the history
Start to flesh out type AST
  • Loading branch information
edemaine committed Feb 13, 2023
1 parent c09b656 commit e7176ed
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 33 deletions.
17 changes: 8 additions & 9 deletions civet.dev/cheatsheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,14 @@ you can prepare it ahead of time using `return.value`
Using this feature disables implicit `return` for that function.
<Playground>
function search(list)
function sum(list: number[])
return .= 0
for item of list
return += item
</Playground>
<Playground>
function search<T>(list: T[]): T | undefined
return unless list
for item of list
if match item
Expand All @@ -412,14 +419,6 @@ function search(list)
list.destroy()
</Playground>
<Playground>
function search<T>(list: T[], pred: (T) => boolean)
let return: number | undefined
if list
return = list.findIndex pred
list.splice return.value, 1
</Playground>
### Single-Argument Function Shorthand
<Playground>
Expand Down
82 changes: 58 additions & 24 deletions source/parser.hera
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ ArrowFunctionTail
return {
type: "ArrowFunction",
parameters,
returnType: suffix?.children?.[1]?.[0]?.[1]?.token,
returnType: suffix,
ts: false,
block: expOrBlock,
children: $0,
Expand Down Expand Up @@ -1331,7 +1331,7 @@ FunctionSignature
type: "FunctionSignature",
id: wid?.[1],
parameters,
returnType: suffix?.children?.[1]?.[0]?.[1]?.token,
returnType: suffix,
ts: false,
block: null,
children: !parameters.implicit ? $0 :
Expand Down Expand Up @@ -1425,7 +1425,7 @@ OperatorSignature
type: "FunctionSignature",
id,
parameters,
returnType: suffix?.children?.[1]?.[0]?.[1]?.token,
returnType: suffix,
ts: false,
block: null,
children: [ func, w1, id, w2, parameters, suffix ],
Expand Down Expand Up @@ -1469,7 +1469,7 @@ ThinArrowFunction
type: "FunctionExpression",
id: undefined,
parameters,
returnType: suffix?.children?.[1]?.[0]?.[1]?.token,
returnType: suffix,
ts: false,
block: block,
children: [
Expand Down Expand Up @@ -2272,7 +2272,7 @@ MethodSignature
name: name,
modifier: $1?.[0]?.token, // get/set
// TODO: get return type from type annotation
returnType: undefined,
returnType: suffix,
parameters,
}

Expand Down Expand Up @@ -5273,21 +5273,31 @@ TypeSuffix
}

ReturnTypeSuffix
__ Colon ( __ "asserts" NonIdContinue )? TypePredicate ->
const children = [...$1, $2]
if ($3) children./**/push($3)
children./**/push($4)
__ Colon ( __ "asserts" NonIdContinue )?:asserts TypePredicate:t ->

This comment has been minimized.

Copy link
@STRd6

STRd6 Feb 13, 2023

Contributor

We should probably make this _? before the colon while we're in here.

if (asserts) {
t = {
type: "AssertsType",
t,
children: [asserts[0], asserts[1], t],
}
}

return {
type: "ReturnTypeAnnotation",
children,
children: [$1, $2, t],
t,
ts: true,
}

TypePredicate
Type ( __ "is" NonIdContinue Type )? ->
if (!$2) return $1
return $0
Type:lhs ( __ "is" NonIdContinue Type )?:rhs ->
if (!rhs) return lhs
return {
type: "TypePredicate",
lhs,
rhs: rhs[3],
children: [lhs, ...rhs],
}

Type
TypeConditional
Expand All @@ -5299,6 +5309,7 @@ TypeBinary

TypeUnary
( __ TypeUnaryOp NonIdContinue )* TypePrimary TypeUnarySuffix* ->
if (!$1.length && !$3.length) return $2
return [...$1, $2, ...$3]

TypeUnarySuffix
Expand All @@ -5320,8 +5331,19 @@ TypePrimary
_? InlineInterfaceLiteral
_? TypeTuple
_? ImportType
_? TypeLiteral
_? IdentifierName (Dot IdentifierName)* TypeArguments?
_? TypeLiteral:t ->
return {
type: "LiteralType",
t,
children: $0,
}
_? IdentifierName (Dot IdentifierName)* TypeArguments?:args ->
return {
type: "IdentifierType",
children: $0,
raw: [$2.name, ...$3.map(([dot, id]) => dot.token + id.name), ].join(''),
args,
}
# NOTE: Check FunctionType before parenthesized in order to distinguish between (a: T) => U and
# A parenthesized inline interface (a: T) ---> ({a: T})
# NOTE: Check Type before ( EOS Type ) to find implicit nested interfaces first. EOS would swallow the
Expand Down Expand Up @@ -5360,7 +5382,7 @@ TypeLiteral
# interpolated strings get checked first before StringLiteral.
Literal
"void" NonIdContinue ->
return { $loc, token: "void" }
return { type: "VoidType", $loc, token: $1 }
"[]" ->
return { $loc, token: "[]" }

Expand Down Expand Up @@ -7014,7 +7036,8 @@ Init
// Support for `return.value` and `return =`
// for changing automatic return value of function.
// Returns whether any present (so shouldn't do implicit return).
function processReturnValue(block) {
function processReturnValue(func) {
const {block} = func
const values = gatherRecursiveWithinFunction(block,
({type}) => type === "ReturnValue")
if (!values.length) return false
Expand All @@ -7038,11 +7061,18 @@ Init

// Add declaration of return.value after {
if (!declared) {
let returnType = func.returnType ?? func.signature?.returnType
if (returnType) {
const {t} = returnType
if (t.type === "AssertsType" || t.type === "TypePredicate") {
returnType = undefined
}
}
block.expressions.unshift([
getIndent(block.expressions[0]),
{
type: "Declaration",
children: ["let ", ref, ";\n"],
children: ["let ", ref, returnType, ";\n"],
names: [],
}
])
Expand Down Expand Up @@ -7071,13 +7101,17 @@ Init
return true
}

function isVoidType(t) {
return t?.type === "LiteralType" && t.t.type === "VoidType"
}

function processFunctions(statements) {
gatherRecursiveAll(statements, ({type}) => type === "FunctionExpression" || type === "ArrowFunction")
.forEach((f) => {
processParams(f)
const { block, returnType } = f
if (!processReturnValue(block) && module.config.implicitReturns) {
const isVoid = returnType === "void"
if (!processReturnValue(f) && module.config.implicitReturns) {
const { block, returnType } = f
const isVoid = isVoidType(returnType?.t)
const isBlock = block?.type === "BlockStatement"
if (!isVoid && isBlock) {
insertReturn(block)
Expand All @@ -7088,10 +7122,10 @@ Init
gatherRecursiveAll(statements, ({type}) => type === "MethodDefinition")
.forEach((f) => {
processParams(f)
const {signature, block} = f
if (!processReturnValue(block) && module.config.implicitReturns) {
if (!processReturnValue(f) && module.config.implicitReturns) {
const {signature, block} = f
const isConstructor = signature.name === "constructor"
const isVoid = signature.returnType === "void"
const isVoid = isVoidType(signature.returnType?.t)
const isSet = signature.modifier === "set"

if (!isConstructor && !isSet && !isVoid) {
Expand Down
69 changes: 69 additions & 0 deletions test/function.civet
Original file line number Diff line number Diff line change
Expand Up @@ -1451,6 +1451,75 @@ describe "function", ->
}
"""

testCase """
return.value typed by function
---
function f: number
return = 5
---
function f(): number {
let ret: number;
ret = 5
return ret
}
"""

testCase """
return.value typed by arrow function
---
(): number =>
return = 5
---
(): number => {
let ret: number;
ret = 5
return ret
}
"""

testCase """
return.value typed by method
---
{
f(): number
return = 5
}
---
({
f(): number {
let ret: number;
ret = 5
return ret
}
})
"""

testCase """
return.value not typed for asserts
---
function f(x): asserts x is number
return = false
---
function f(x): asserts x is number {
let ret;
ret = false
return ret
}
"""

testCase """
return.value not typed for is
---
function f(x): x is number
return = false
---
function f(x): x is number {
let ret;
ret = false
return ret
}
"""

testCase.skip """
return.value parameter
---
Expand Down

0 comments on commit e7176ed

Please sign in to comment.