Further work on function calls

This commit is contained in:
Mattia Giambirtone 2023-08-05 12:46:40 +02:00
parent f34b71ec0b
commit db41234ee0
Signed by: nocturn9x
GPG Key ID: 8270F9F467971E59
1 changed files with 75 additions and 87 deletions

View File

@ -70,7 +70,7 @@ type
isGenerator*: bool
isCoroutine*: bool
isAuto*: bool
args*: seq[tuple[name: string, kind: Type, default: Expression]]
args*: TypeSignature
returnType*: Type
builtinOp*: string
fun*: Declaration
@ -178,6 +178,8 @@ type
# Is this name generated by user code or internally by the compiler?
isReal: bool
TypeSignature = seq[tuple[name: string, kind: Type, default: Expression]]
## Our typed AST representation
TypedNode* = ref object of RootObj
@ -198,7 +200,12 @@ type
TypedIdentExpr* = ref object of TypedExpr
## A typed identifier expression
name*: Name
name*: Name
TypedCallExpr* = ref object of TypedExpr
## A typed function call expression
callee*: Name
args*: seq[tuple[name: string, kind: Type, default: TypedExpr]]
TypedDecl* = ref object of TypedNode
## A typed declaration node
@ -297,7 +304,7 @@ proc stringify*(self: PeonCompiler, typ: TypedNode): string
proc inferOrError*(self: PeonCompiler, node: Expression): TypedExpr
proc compare(self: PeonCompiler, a, b: Type): bool
proc expression(self: PeonCompiler, node: Expression): TypedExpr
proc specialize(self: PeonCompiler, typ: Type, args: seq[Expression]): Type {.discardable.}
proc specialize(self: PeonCompiler, name: Name, args: seq[Expression]): Type
proc funDecl(self: PeonCompiler, node: FunDecl, name: Name = nil): TypedDecl
proc getTypeDistance(self: PeonCompiler, a, b: Type): int
@ -358,6 +365,13 @@ proc newTypedBinaryExpr(node: UnaryExpr, kind: Type, a, b: TypedExpr): TypedBina
result = TypedBinaryExpr(node: node, a: a, b: b, kind: kind)
proc newTypedCallExpr(node: CallExpr, callee: Name,
args: seq[tuple[name: string, kind: Type, default: TypedExpr]]): TypedCallExpr =
## Initializes a new typed function call expression
result = TypedCallExpr(node: node, callee: callee, args: args, kind: callee.valueType.returnType)
proc newTypedBlockStmt(node: BlockStmt, body: seq[TypedNode]): TypedBlockStmt =
## Initializes a new typed block statement
result = TypedBlockStmt(node: node, body: body)
@ -982,6 +996,8 @@ proc inferOrError(self: PeonCompiler, node: Expression): TypedExpr =
proc stringify*(self: PeonCompiler, typ: Type): string =
## Returns the string representation of a
## type object
if typ.isNil():
return ""
case typ.kind:
of Char, Byte, String, Nil, TypeKind.Nan,
Auto, Any:
@ -1171,7 +1187,7 @@ proc getTypeDistance(self: PeonCompiler, a, b: Type): int =
parent = parent.parent
proc calcTypeDistance(self: PeonCompiler, typ: Type, sig: seq[tuple[name: string, kind: Type, default: Expression]]): int =
proc calcTypeDistance(self: PeonCompiler, typ: Type, sig: TypeSignature): int =
## Computes the cumulative type distance between
## the given type and the given type signature. It's
## basically just adding up the type distances of each
@ -1191,7 +1207,7 @@ proc calcTypeDistance(self: PeonCompiler, typ: Type, sig: seq[tuple[name: string
self.error(&"cannot compute type distance for object of type {self.stringify(typ)}")
proc checkTypeSignature(self: PeonCompiler, typ: Type, sig: seq[tuple[name: string, kind: Type, default: Expression]]): bool =
proc checkTypeSignature(self: PeonCompiler, typ: Type, sig: TypeSignature): bool =
## Helper for to check type signatures.
## Returns true if the given type matches
## the given type signature
@ -1213,7 +1229,7 @@ proc checkTypeSignature(self: PeonCompiler, typ: Type, sig: seq[tuple[name: stri
# third argument has a default value
return false
# We construct a new signature, without the function's default arguments
var args: seq[tuple[name: string, kind: Type, default: Expression]] = @[]
var args: TypeSignature = @[]
for argument in typ.args:
if argument.default.isNil():
args.add(argument)
@ -1237,7 +1253,7 @@ proc checkTypeSignature(self: PeonCompiler, typ: Type, sig: seq[tuple[name: stri
self.error(&"cannot check type signature for object of type {self.stringify(typ)}")
proc match(self: PeonCompiler, name: string, sig: seq[tuple[name: string, kind: Type, default: Expression]], node: ASTNode = nil): Name =
proc match(self: PeonCompiler, name: string, sig: TypeSignature, node: ASTNode = nil): Name =
## Tries to find a matching type for a given (typeName, typeSignature) pair
## and returns it. In this context, "type signature" means an ordered list of
## tuples (paramName, paramType, paramDefault) that represents the arguments we
@ -1368,50 +1384,39 @@ proc match(self: PeonCompiler, name: string, sig: seq[tuple[name: string, kind:
self.error(msg, node)
proc specialize(self: PeonCompiler, typ: Type, args: seq[Expression]): Type =
proc specialize(self: PeonCompiler, name: Name, args: seq[Expression]): Type =
## Instantiates a generic type
var
mapping: TableRef[string, Type] = newTable[string, Type]()
kind: Type
gen: Type
result = deepCopy(typ)
case result.kind:
of TypeKind.Function:
# This loop checks if a user tries to reassign a generic's
# name to a different type
for i, arg in args:
if i > typ.fun.generics.high():
gen = typ.genericArgs[typ.fun.generics[^1].name.token.lexeme]
if not name.isGeneric:
self.error(&"cannot instantiate concrete type from {self.stringify(name.valueType)}: a generic is required")
case name.node.kind:
of NodeKind.funDecl:
var fun = FunDecl(name.node)
if fun.generics.len() != args.len():
self.error(&"wrong number of types supplied for generic instantiation (expected {fun.generics.len()}, got {args.len()})")
var concrete = deepCopy(name.valueType)
var types: seq[Type] = @[]
var map = newTable[string, Type]()
for arg in args:
types.add(self.inferOrError(arg).kind)
if types[^1].kind != Typevar:
self.error(&"expecting type name during generic instantiation, got {self.stringify(types[^1])}", arg)
for (gen, value) in zip(fun.generics, args):
map[gen.name.token.lexeme] = self.inferOrError(value).kind
for i, argument in concrete.args:
if argument.kind.kind != Generic:
continue
elif argument.name in map:
concrete.args[i].kind = map[argument.name]
else:
gen = typ.genericArgs[typ.fun.generics[i].name.token.lexeme]
kind = self.inferOrError(arg).kind
if gen.name in mapping and not self.compare(kind, mapping[gen.name]):
self.error(&"expecting generic argument '{gen.name}' to be of type {self.stringify(mapping[gen.name])}, got {self.stringify(kind)}", args[i])
mapping[gen.name] = kind
result.args[i].kind = kind
for arg in result.args.mitems():
# We only replace types we know about. This allows
# us to do partial instantiation
if arg.kind.kind == Generic and arg.kind.name in mapping:
arg.kind = mapping[arg.kind.name]
if not result.returnType.isNil() and result.returnType.kind == Generic:
if result.returnType.name in mapping:
result.returnType = mapping[result.returnType.name]
elif mapping.len() == 0:
# The function has no generic arguments,
# just a generic return type
var typ: Type
for i, gen in result.fun.generics:
if gen.name.token.lexeme == result.returnType.name:
typ = result.args[i].kind
break
if typ.isNil():
self.error(&"unknown generic argument name '{result.returnType.name}'", result.fun)
result.returnType = typ
self.error(&"unknown generic argument name '{argument.name}'", concrete.fun)
if not concrete.returnType.isNil() and concrete.returnType.kind == Generic:
if concrete.returnType.name in map:
concrete.returnType = map[concrete.returnType.name]
else:
self.error(&"unknown generic argument name '{result.returnType.name}'", result.fun)
self.error(&"unknown generic argument name '{concrete.returnType.name}'", concrete.fun)
result = concrete
else:
discard
self.error(&"cannot instantiate concrete type for object of type {self.stringify(name.valueType)}")
proc unpackTypes(self: PeonCompiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type, value: Expression]], accept: bool = true, isGeneric: bool = false) =
@ -1620,7 +1625,7 @@ proc unary(self: PeonCompiler, node: UnaryExpr): TypedUnaryExpr =
var name = self.match(node.token.lexeme, fn.args, node)
var impl = name.valueType
if name.isGeneric:
impl = self.specialize(impl, @[node.a])
impl = self.specialize(name, @[node.a])
result = newTypedUnaryExpr(node, impl.returnType, typeOfA)
@ -1636,50 +1641,19 @@ proc binary(self: PeonCompiler, node: BinaryExpr): TypedBinaryExpr =
var name = self.match(node.token.lexeme, fn.args, node)
var impl = name.valueType
if name.isGeneric:
impl = self.specialize(impl, @[node.a])
impl = self.specialize(name, @[node.a])
result = newTypedBinaryExpr(node, impl.returnType, typeOfA, typeOfB)
proc genericExpr(self: PeonCompiler, node: GenericExpr): TypedExpr =
## Compiles generic instantiation
var name = self.findOrError(node.ident.token.lexeme)
if not name.isGeneric:
self.error(&"cannot instantiate concrete type from {self.stringify(name.valueType)}: a generic is required")
case name.node.kind:
of NodeKind.funDecl:
var fun = FunDecl(name.node)
if fun.generics.len() != node.args.len():
self.error(&"wrong number of types supplied for generic instantiation (expected {fun.generics.len()}, got {node.args.len()})")
var concrete = deepCopy(name.valueType)
var types: seq[Type] = @[]
var map = newTable[string, Type]()
for arg in node.args:
types.add(self.inferOrError(arg).kind)
if types[^1].kind != Typevar:
self.error(&"expecting type name during generic instantiation, got {self.stringify(types[^1])}", arg)
for (gen, value) in zip(fun.generics, node.args):
map[gen.name.token.lexeme] = self.inferOrError(value).kind
for i, argument in concrete.args:
if argument.kind.kind != Generic:
continue
elif argument.name in map:
concrete.args[i].kind = map[argument.name]
else:
self.error(&"unknown generic argument name '{argument.name}'", concrete.fun)
if not concrete.returnType.isNil() and concrete.returnType.kind == Generic:
if concrete.returnType.name in map:
concrete.returnType = map[concrete.returnType.name]
else:
self.error(&"unknown generic argument name '{concrete.returnType.name}'", concrete.fun)
result = newTypedExpr(node, concrete)
else:
self.error(&"cannot instantiate concrete type for object of type {self.stringify(name.valueType)}")
result = newTypedExpr(node, self.specialize(self.findOrError(node.ident.token.lexeme), node.args))
proc call(self: PeonCompiler, node: CallExpr): TypedExpr =
## Compiles call expressions. This includes
## things like object and enum construction
var args: seq[tuple[name: string, kind: Type, default: Expression]] = @[]
var args: TypeSignature = @[]
var argExpr: seq[Expression] = @[]
var default: Expression
var kind: Type
@ -1695,11 +1669,25 @@ proc call(self: PeonCompiler, node: CallExpr): TypedExpr =
of NodeKind.identExpr:
# Calls like hi()
var impl = self.match(IdentExpr(node.callee).name.lexeme, args, node)
var function = impl.valueType
if impl.isGeneric:
function = self.specialize(function, argExpr)
result = newTypedExpr(node, function.returnType)
self.dispatchDelayedPragmas(impl)
var typ = impl.valueType
if impl.isGeneric:
typ = self.specialize(impl, argExpr)
case typ.kind:
of Enum, CustomType:
# TODO
result = newTypedExpr(node, typ)
of Function:
var typedArgs: seq[tuple[name: string, kind: Type, default: TypedExpr]] = @[]
for arg in args:
if not arg.default.isNil():
typedArgs.add((arg.name, arg.kind, self.expression(arg.default)))
else:
typedArgs.add((arg.name, arg.kind, nil))
result = newTypedCallExpr(node, impl, typedArgs)
else:
# TODO?
discard
of NodeKind.callExpr:
# Calling a call expression, like hello()()
# TODO