Add mutable ref handles and new builtin
This commit is contained in:
3
nim.cfg
3
nim.cfg
@@ -2,4 +2,5 @@
|
||||
--exceptions:setjmp
|
||||
--hints:off
|
||||
-d:release
|
||||
path="src"
|
||||
path="src"
|
||||
--cc:clang
|
||||
@@ -553,6 +553,8 @@ proc handleBuiltinFunction(self: BytecodeGenerator, fn: Name, args: seq[TypedExp
|
||||
# The only reason why there's a "cast" pragma is to
|
||||
# make it so that the peon stub can have no body
|
||||
discard
|
||||
of "new":
|
||||
self.error("builtin 'new' is not implemented in the bytecode backend yet")
|
||||
else:
|
||||
self.error(&"unknown built-in: '{builtinOp}'")
|
||||
|
||||
|
||||
@@ -112,6 +112,9 @@ proc registerReachableTypes(self: NativeCGenerator, module: CompiledModule, expr
|
||||
proc collectNestedFunctions(self: NativeCGenerator, module: CompiledModule, node: TypedNode)
|
||||
proc collectNestedFunctions(self: NativeCGenerator, module: CompiledModule, body: TypedBlockStmt)
|
||||
proc resolveSymbol(self: NativeCGenerator, name: Name): string
|
||||
proc resolveTypeSymbol(self: NativeCGenerator, typ: Type): string
|
||||
proc resolveCopySymbol(self: NativeCGenerator, typ: Type): string
|
||||
proc resolveDestroyStorageSymbol(self: NativeCGenerator, typ: Type): string
|
||||
|
||||
|
||||
proc error(self: NativeCGenerator, msg: string, typedNode: TypedNode = nil) =
|
||||
@@ -171,8 +174,12 @@ proc sameTypeShape(a, b: Type): bool =
|
||||
of Structure, EnumEntry:
|
||||
if cast[uint](left) == cast[uint](right):
|
||||
return true
|
||||
if left.name.len() > 0 and right.name.len() > 0 and left.name != right.name:
|
||||
return false
|
||||
if left.name.len() > 0 and right.name.len() > 0:
|
||||
if left.name != right.name:
|
||||
return false
|
||||
if left.kind == EnumEntry:
|
||||
return sameTypeShape(left.parent, right.parent)
|
||||
return true
|
||||
if left.kind == EnumEntry:
|
||||
return sameTypeShape(left.parent, right.parent)
|
||||
return false
|
||||
@@ -317,6 +324,25 @@ func registerNewSymbol(self: NativeCGenerator, typ: Type, symbol: string) = sel
|
||||
func registerDestroySymbol(self: NativeCGenerator, typ: Type, symbol: string) = self.destroySymbols[typeKey(typ)] = symbol
|
||||
|
||||
|
||||
proc registerEquivalentNamedStorage(self: NativeCGenerator, module: CompiledModule, typ: Type) =
|
||||
let storage = typ.unwrapType()
|
||||
if storage.kind notin {Structure, EnumEntry} or storage.intrinsic:
|
||||
return
|
||||
if self.typeSymbols.hasKey(typeKey(storage)):
|
||||
return
|
||||
|
||||
for decl in module.types:
|
||||
let declared = storageType(decl.name.valueType.unwrapType())
|
||||
if not sameTypeShape(declared, storage) or not sameTypeShape(storage, declared):
|
||||
continue
|
||||
self.registerTypeSymbol(storage, self.resolveTypeSymbol(declared))
|
||||
if self.copySymbols.hasKey(typeKey(declared)):
|
||||
self.registerCopySymbol(storage, self.resolveCopySymbol(declared))
|
||||
if self.destroyStorageSymbols.hasKey(typeKey(declared)):
|
||||
self.registerDestroyStorageSymbol(storage, self.resolveDestroyStorageSymbol(declared))
|
||||
return
|
||||
|
||||
|
||||
proc anonymousArrayTypeSymbol(module: CompiledModule, index: int): string =
|
||||
modulePrefix(module.name, module.id) & "_anon_array_" & $index
|
||||
|
||||
@@ -474,6 +500,7 @@ proc registerReachableType(self: NativeCGenerator, module: CompiledModule, typ:
|
||||
of Pointer, Lent, Const:
|
||||
self.registerReachableType(module, unwrapped.value)
|
||||
of Structure, EnumEntry:
|
||||
self.registerEquivalentNamedStorage(module, unwrapped)
|
||||
for fieldType in unwrapped.fields.values():
|
||||
self.registerReachableType(module, fieldType)
|
||||
if unwrapped.kind == Structure:
|
||||
@@ -1297,6 +1324,17 @@ proc generateBuiltinCall(self: NativeCGenerator, callee: Name, args: seq[TypedEx
|
||||
return &"__print({lowered})"
|
||||
else:
|
||||
self.error(&"invalid type {self.typeChecker.stringify(typ)} for built-in 'print'", argument)
|
||||
of "new":
|
||||
if args.len != 1:
|
||||
self.error(&"builtin 'new' expects exactly 1 argument (got {args.len})", expression)
|
||||
let refType = expression.kind.unwrapType()
|
||||
if refType.kind != Reference:
|
||||
self.error("builtin 'new' must return a managed ref", expression)
|
||||
let storage = storageType(refType)
|
||||
if storage.kind notin {Structure, Array}:
|
||||
self.error(&"builtin 'new' expects object or array storage, got {self.typeChecker.stringify(storage, true)} instead",
|
||||
expression)
|
||||
return &"{self.resolveNewSymbol(refType)}(({self.lowerType(storage)}){{0}}, 0u)"
|
||||
else:
|
||||
self.error(&"builtin call '{callee.ident.token.lexeme}' is not implemented in the native C backend yet", expression)
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ func toRef(self: Type): Type {.inline.}
|
||||
func toConst(self: Type): Type {.inline.}
|
||||
func toPtr(self: Type): Type {.inline.}
|
||||
func toLent(self: Type): Type {.inline.}
|
||||
func withHandleMutability(self: Type, mutable: bool): Type {.inline.}
|
||||
proc wrapType(self: Type): Type {.inline.}
|
||||
proc unwrapType*(self: Type): Type {.inline.}
|
||||
proc handleErrorPragma(self: TypeChecker, pragma: Pragma, name: Name)
|
||||
@@ -324,17 +325,27 @@ proc unwrapType*(self: Type): Type {.inline.} =
|
||||
result = self
|
||||
|
||||
|
||||
func toRef(self: Type): Type {.inline.} = Type(kind: Reference, value: self)
|
||||
func toRef(self: Type): Type {.inline.} = Type(kind: Reference, value: self, mutable: false)
|
||||
func toPtr(self: Type): Type {.inline} = Type(kind: Pointer, value: self)
|
||||
|
||||
func toLent(self: Type): Type {.inline.} =
|
||||
result = "lent".toIntrinsic()
|
||||
result.value = self
|
||||
result.mutable = false
|
||||
|
||||
func toConst(self: Type): Type {.inline.} =
|
||||
result = "const".toIntrinsic()
|
||||
result.value = self
|
||||
|
||||
func withHandleMutability(self: Type, mutable: bool): Type {.inline.} =
|
||||
if self.isNil():
|
||||
return nil
|
||||
let unwrapped = self.unwrapType()
|
||||
if unwrapped.kind notin {Reference, TypeKind.Lent}:
|
||||
return self
|
||||
result = unwrapped.deepCopy()
|
||||
result.mutable = mutable
|
||||
|
||||
|
||||
func toIntrinsic(name: string): Type =
|
||||
## Converts a string to an intrinsic
|
||||
@@ -440,6 +451,27 @@ proc builtinMagic(self: TypeChecker, fn: Name): string =
|
||||
return pragma.args[0].token.lexeme[1..^2]
|
||||
|
||||
|
||||
proc builtinNewType(self: TypeChecker, node: CallExpr): Type =
|
||||
if node.arguments.keyword.len() != 0:
|
||||
self.error("builtin 'new' only accepts a positional type argument", node)
|
||||
if node.arguments.positionals.len() != 1:
|
||||
self.error(&"builtin 'new' expects exactly 1 argument (got {node.arguments.positionals.len()})", node)
|
||||
let typeExpr = self.ensureType(node.arguments.positionals[0])
|
||||
let storage = typeExpr.kind.unwrapType()
|
||||
case storage.kind
|
||||
of Structure:
|
||||
if storage.isEnum:
|
||||
self.error("builtin 'new' does not construct enum root types", node.arguments.positionals[0])
|
||||
of Array:
|
||||
discard
|
||||
of Reference:
|
||||
self.error("builtin 'new' expects a plain storage type, not a managed ref type", node.arguments.positionals[0])
|
||||
else:
|
||||
self.error(&"builtin 'new' expects an object or array type, got {self.stringify(storage, true)} instead",
|
||||
node.arguments.positionals[0])
|
||||
result = storage.toRef().withHandleMutability(true)
|
||||
|
||||
|
||||
proc parseStaticIntegerValue(self: TypeChecker, node: ASTNode): int64 =
|
||||
let lexeme = node.token.lexeme.split("'")[0]
|
||||
try:
|
||||
@@ -862,6 +894,8 @@ proc compare(self: TypeChecker, a, b: Type): bool =
|
||||
of Array:
|
||||
return a.length == b.length and self.compare(a.elementType, b.elementType)
|
||||
of TypeKind.Lent, Reference, Pointer, TypeKind.Const:
|
||||
if a.kind in {Reference, TypeKind.Lent} and b.mutable and not a.mutable:
|
||||
return false
|
||||
return self.compare(a.value, b.value)
|
||||
of Union, Generic:
|
||||
if a.constraints.len() > b.constraints.len():
|
||||
@@ -889,6 +923,26 @@ proc compareWithContext(self: TypeChecker, a: TypedExpr, b: Type): Type =
|
||||
## needed) is returned. If the simple and context-aware
|
||||
## comparisons both fail, nil is returned
|
||||
let expected = b.unwrapType()
|
||||
|
||||
proc isFreshOwnedHandle(expr: TypedExpr): bool =
|
||||
if expr.isNil():
|
||||
return false
|
||||
let exprType = expr.kind.unwrapType()
|
||||
if exprType.kind notin {Reference, TypeKind.Lent}:
|
||||
return false
|
||||
expr of TypedConstructExpr or
|
||||
expr of TypedArrayConstructExpr or
|
||||
expr.node.kind == NodeKind.callExpr
|
||||
|
||||
if expected.kind in {Reference, TypeKind.Lent}:
|
||||
let actual = a.kind.unwrapType()
|
||||
if actual.kind == expected.kind and
|
||||
self.compare(actual.value, expected.value) and
|
||||
expected.mutable and
|
||||
not actual.mutable and
|
||||
isFreshOwnedHandle(a):
|
||||
return expected
|
||||
|
||||
if a of TypedArrayConstructExpr and TypedArrayConstructExpr(a).isLiteral:
|
||||
let payloadType =
|
||||
case expected.kind:
|
||||
@@ -912,25 +966,29 @@ proc compareWithContext(self: TypeChecker, a: TypedExpr, b: Type): Type =
|
||||
if expected.kind == Reference:
|
||||
let payloadType = expected.value.unwrapType()
|
||||
|
||||
proc hasInteriorRefRoot(expr: TypedExpr): bool =
|
||||
proc interiorRefRoot(expr: TypedExpr): TypedExpr =
|
||||
if expr.isNil():
|
||||
return false
|
||||
return nil
|
||||
if expr of TypedFieldExpr:
|
||||
let field = TypedFieldExpr(expr)
|
||||
if field.deref:
|
||||
return true
|
||||
return hasInteriorRefRoot(field.obj)
|
||||
return field.obj
|
||||
return interiorRefRoot(field.obj)
|
||||
if expr of TypedIndexExpr:
|
||||
let indexed = TypedIndexExpr(expr)
|
||||
if indexed.deref:
|
||||
return true
|
||||
return hasInteriorRefRoot(indexed.obj)
|
||||
false
|
||||
return indexed.obj
|
||||
return interiorRefRoot(indexed.obj)
|
||||
nil
|
||||
|
||||
let root = interiorRefRoot(a)
|
||||
if (a of TypedFieldExpr or a of TypedIndexExpr) and
|
||||
self.compare(a.kind, payloadType) and
|
||||
hasInteriorRefRoot(a):
|
||||
return expected
|
||||
not root.isNil():
|
||||
let rootType = root.kind.unwrapType()
|
||||
if rootType.kind == Reference and
|
||||
(not expected.mutable or rootType.mutable):
|
||||
return expected
|
||||
|
||||
result = a.kind
|
||||
if self.compare(result, b):
|
||||
@@ -1147,6 +1205,8 @@ proc stringify*(self: TypeChecker, typ: Type, verbose: bool = false): string =
|
||||
of Pointer:
|
||||
result &= &"ptr {self.stringify(typ.value)}"
|
||||
of Reference:
|
||||
if typ.mutable:
|
||||
result &= "mut "
|
||||
result &= &"ref {self.stringify(typ.value)}"
|
||||
of TypeKind.Const:
|
||||
result &= &"const {self.stringify(typ.value)}"
|
||||
@@ -1193,6 +1253,8 @@ proc stringify*(self: TypeChecker, typ: Type, verbose: bool = false): string =
|
||||
result &= "}"
|
||||
inc(i)
|
||||
of TypeKind.Lent:
|
||||
if typ.mutable:
|
||||
result &= "mut "
|
||||
result &= &"lent {self.stringify(typ.value)}"
|
||||
of Generic, Union:
|
||||
if typ.displayName.len() > 0 and not verbose:
|
||||
@@ -1230,7 +1292,7 @@ proc stringify*(self: TypeChecker, typ: TypedNode, verbose = false): string =
|
||||
of NodeKind.funDecl, varDecl, typeDecl:
|
||||
result = self.stringify(TypedDecl(typ).name.valueType, verbose)
|
||||
of binaryExpr, unaryExpr, identExpr, callExpr, lentExpr,
|
||||
constExpr, ptrExpr, refExpr, genericExpr, sliceExpr, arrayExpr:
|
||||
mutExpr, constExpr, ptrExpr, refExpr, genericExpr, sliceExpr, arrayExpr:
|
||||
result = self.stringify(TypedExpr(typ).kind, verbose)
|
||||
else:
|
||||
# TODO
|
||||
@@ -1900,7 +1962,7 @@ proc matchCandidates(self: TypeChecker, label: string, pool: seq[Name], sig: Typ
|
||||
|
||||
|
||||
proc findTypeDecl(self: TypeChecker, typ: Type): TypeDecl =
|
||||
let target = cast[uint](typ.unwrapType())
|
||||
let target = typ.unwrapType()
|
||||
for scope in self.names:
|
||||
for bucket in scope.values():
|
||||
for name in bucket:
|
||||
@@ -1910,7 +1972,10 @@ proc findTypeDecl(self: TypeChecker, typ: Type): TypeDecl =
|
||||
let storageType =
|
||||
if declaredType.kind == Reference: declaredType.value
|
||||
else: declaredType
|
||||
if cast[uint](storageType) == target:
|
||||
if cast[uint](storageType) == cast[uint](target):
|
||||
return TypeDecl(name.node)
|
||||
if storageType.kind == Structure and target.kind == Structure and
|
||||
storageType.name.len > 0 and storageType.name == target.name:
|
||||
return TypeDecl(name.node)
|
||||
|
||||
|
||||
@@ -1926,7 +1991,7 @@ proc fieldIndex(self: TypeChecker, decl: TypeDecl, fieldName: string): int =
|
||||
proc resolveFieldTarget(self: TypeChecker, target: TypedExpr, member: IdentExpr): tuple[typ: Type, decl: TypeDecl, index: int, deref: bool] =
|
||||
var targetType = target.kind.unwrapType()
|
||||
var deref = false
|
||||
if targetType.kind == Reference:
|
||||
if targetType.kind in {Reference, TypeKind.Lent}:
|
||||
deref = true
|
||||
targetType = targetType.value.unwrapType()
|
||||
if targetType.kind != Structure or targetType.isEnum:
|
||||
@@ -1941,8 +2006,14 @@ proc resolveFieldTarget(self: TypeChecker, target: TypedExpr, member: IdentExpr)
|
||||
|
||||
proc ensureMutableFieldBase(self: TypeChecker, target: TypedExpr, node: ASTNode) =
|
||||
let targetType = target.kind.unwrapType()
|
||||
if targetType.kind == Reference:
|
||||
return
|
||||
if targetType.kind in {Reference, TypeKind.Lent}:
|
||||
if targetType.mutable:
|
||||
return
|
||||
if target.node.kind == identExpr:
|
||||
let name = target.getName()
|
||||
if not name.isNil():
|
||||
self.error(&"cannot assign through '{name.ident.token.lexeme}' (handle is immutable)", node)
|
||||
self.error("field assignment requires a mutable handle", node)
|
||||
case target.node.kind:
|
||||
of identExpr:
|
||||
let name = target.getName()
|
||||
@@ -1966,7 +2037,7 @@ proc ensureMutableFieldBase(self: TypeChecker, target: TypedExpr, node: ASTNode)
|
||||
proc resolveIndexTarget(self: TypeChecker, target: TypedExpr, indexNode: Expression): tuple[typ: Type, indexExpr: TypedExpr, index: int, deref: bool] =
|
||||
var targetType = target.kind.unwrapType()
|
||||
var deref = false
|
||||
if targetType.kind == Reference:
|
||||
if targetType.kind in {Reference, TypeKind.Lent}:
|
||||
deref = true
|
||||
targetType = targetType.value.unwrapType()
|
||||
if targetType.kind != Array:
|
||||
@@ -2588,6 +2659,10 @@ proc call(self: TypeChecker, node: CallExpr): TypedExpr =
|
||||
let matched = self.matchResolved(name, args, argExpr, node)
|
||||
var impl = matched.name
|
||||
self.dispatchDelayedPragmas(impl)
|
||||
if impl.valueType.isBuiltin and self.builtinMagic(impl) == "new":
|
||||
result = newTypedCallExpr(node, impl, argExpr)
|
||||
result.kind = self.builtinNewType(node)
|
||||
return
|
||||
case impl.valueType.kind:
|
||||
of Structure:
|
||||
# TODO
|
||||
@@ -2645,6 +2720,10 @@ proc call(self: TypeChecker, node: CallExpr): TypedExpr =
|
||||
let matched = self.matchCandidatesResolved(&"{target.getName().ident.token.lexeme}.{getter.name.token.lexeme}", members, args, argExpr, node)
|
||||
let impl = matched.name
|
||||
self.dispatchDelayedPragmas(impl)
|
||||
if impl.valueType.isBuiltin and self.builtinMagic(impl) == "new":
|
||||
result = newTypedCallExpr(node, impl, argExpr)
|
||||
result.kind = self.builtinNewType(node)
|
||||
return
|
||||
case impl.valueType.kind:
|
||||
of Structure:
|
||||
result = newTypedExpr(node, impl.valueType)
|
||||
@@ -2694,6 +2773,15 @@ proc refExpr(self: TypeChecker, node: Ref): TypedExpr =
|
||||
result.kind = result.kind.unwrapType().toRef()
|
||||
|
||||
|
||||
proc mutExpr(self: TypeChecker, node: ast.Mut): TypedExpr =
|
||||
## Typechecks mutable-handle type expressions
|
||||
result = self.ensureType(node.value)
|
||||
let target = result.kind.unwrapType()
|
||||
if target.kind notin {Reference, TypeKind.Lent}:
|
||||
self.error("'mut' only applies to managed refs and lent borrows", node)
|
||||
result.kind = target.withHandleMutability(true)
|
||||
|
||||
|
||||
proc constExpr(self: TypeChecker, node: ast.Const): TypedExpr =
|
||||
## Typechecks const expressions
|
||||
|
||||
@@ -2748,6 +2836,8 @@ proc expression(self: TypeChecker, node: Expression): TypedExpr =
|
||||
result.kind = result.kind.unwrapType().toPtr()
|
||||
of NodeKind.lentExpr:
|
||||
result = self.lentExpr(ast.Lent(node))
|
||||
of NodeKind.mutExpr:
|
||||
result = self.mutExpr(ast.Mut(node))
|
||||
of NodeKind.constExpr:
|
||||
result = self.constExpr(ast.Const(node))
|
||||
else:
|
||||
@@ -2851,8 +2941,12 @@ proc varDecl(self: TypeChecker, node: VarDecl): TypedVarDecl =
|
||||
# we don't actually want the returned expression to be
|
||||
# a wrapped type, we just do this step as a safety check
|
||||
typ = self.ensureType(node.valueType).kind.unwrapType()
|
||||
elif not typ.isNil() and typ.kind in {Reference, TypeKind.Lent}:
|
||||
typ = typ.withHandleMutability(node.mutable)
|
||||
if typ.isNil():
|
||||
self.error("expecting either a type declaration or an initializer value, but neither was found", node)
|
||||
if not typ.isNil() and typ.kind in {Reference, TypeKind.Lent} and node.mutable and not typ.mutable:
|
||||
typ = typ.withHandleMutability(true)
|
||||
# Now check that the type of the initializer, if it exists,
|
||||
# matches the type of the variable
|
||||
if not init.isNil():
|
||||
@@ -3045,6 +3139,7 @@ proc typeDecl(self: TypeChecker, node: TypeDecl, name: Name = nil): TypedTypeDec
|
||||
else:
|
||||
case node.value.kind:
|
||||
of NodeKind.identExpr, NodeKind.genericExpr, NodeKind.refExpr,
|
||||
NodeKind.mutExpr,
|
||||
NodeKind.ptrExpr, NodeKind.constExpr, NodeKind.lentExpr:
|
||||
# Type alias
|
||||
name.valueType = self.expression(node.value).kind.unwrapType()
|
||||
|
||||
@@ -86,6 +86,7 @@ type
|
||||
genericExpr,
|
||||
matchStmt,
|
||||
lentExpr,
|
||||
mutExpr,
|
||||
constExpr
|
||||
|
||||
# Here I would've rather used object variants, and in fact that's what was in
|
||||
@@ -327,6 +328,9 @@ type
|
||||
|
||||
Lent* = ref object of Expression
|
||||
value*: Expression
|
||||
|
||||
Mut* = ref object of Expression
|
||||
value*: Expression
|
||||
|
||||
Const* = ref object of Expression
|
||||
value*: Expression
|
||||
@@ -395,6 +399,13 @@ proc newLentExpr*(expression: Expression, token: Token): Lent =
|
||||
result.token = token
|
||||
|
||||
|
||||
proc newMutExpr*(expression: Expression, token: Token): Mut =
|
||||
new(result)
|
||||
result.kind = NodeKind.mutExpr
|
||||
result.value = expression
|
||||
result.token = token
|
||||
|
||||
|
||||
proc newConstExpr*(expression: Expression, token: Token): Const =
|
||||
new(result)
|
||||
result.kind = NodeKind.constExpr
|
||||
@@ -829,6 +840,8 @@ func `$`*(self: ASTNode): string =
|
||||
result &= &"Const({Const(self).value})"
|
||||
of lentExpr:
|
||||
result &= &"Lent({Lent(self).value})"
|
||||
of mutExpr:
|
||||
result &= &"Mut({Mut(self).value})"
|
||||
of genericExpr:
|
||||
var self = GenericExpr(self)
|
||||
result &= &"Generic(ident={self.ident}, args={self.args})"
|
||||
|
||||
@@ -421,6 +421,9 @@ proc primary(self: Parser): Expression =
|
||||
of TokenType.Lent:
|
||||
let tok = self.step()
|
||||
result = newLentExpr(self.parseOr(), tok)
|
||||
of TokenType.Mut:
|
||||
let tok = self.step()
|
||||
result = newMutExpr(self.parseOr(), tok)
|
||||
of TokenType.Const:
|
||||
let tok = self.step()
|
||||
result = newConstExpr(self.parseOr(), tok)
|
||||
|
||||
@@ -37,7 +37,7 @@ type
|
||||
Assert, Await, Foreach,
|
||||
Yield, Type, Operator, Case,
|
||||
Enum, Ptr, Ref, Object,
|
||||
Export, Block, Match, Lent
|
||||
Export, Block, Match, Lent, Mut,
|
||||
From, As,
|
||||
|
||||
# Literal types
|
||||
|
||||
@@ -3,4 +3,8 @@ import values;
|
||||
|
||||
fn print*[T: any](x: T) {
|
||||
#pragma[magic: "print"]
|
||||
}
|
||||
}
|
||||
|
||||
fn new*(kind: typevar[any]) {
|
||||
#pragma[magic: "new"]
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ var tokens* = {"{": TokenType.LeftBrace,
|
||||
"block": TokenType.Block,
|
||||
"match": TokenType.Match,
|
||||
"lent": TokenType.Lent,
|
||||
"mut": TokenType.Mut,
|
||||
"true": TokenType.True,
|
||||
"false": TokenType.False,
|
||||
"ptr": TokenType.Ptr,
|
||||
@@ -132,6 +133,7 @@ proc fillSymbolTable*(tokenizer: Lexer) =
|
||||
tokenizer.symbols.addKeyword("block", TokenType.Block)
|
||||
tokenizer.symbols.addKeyword("match", TokenType.Match)
|
||||
tokenizer.symbols.addKeyword("lent", TokenType.Lent)
|
||||
tokenizer.symbols.addKeyword("mut", TokenType.Mut)
|
||||
# These are more like expressions with a reserved
|
||||
# name that produce a value of a builtin type,
|
||||
# but we don't need to care about that until
|
||||
|
||||
@@ -33,6 +33,10 @@ type any = object {
|
||||
#pragma[magic: "any"]
|
||||
}
|
||||
|
||||
type typevar = object {
|
||||
#pragma[magic: "typevar"]
|
||||
}
|
||||
|
||||
type array[N: int64, T: any] = object {
|
||||
#pragma[magic: "array"]
|
||||
}
|
||||
@@ -48,6 +52,10 @@ operator `<`*(a, b: int64): bool {
|
||||
fn print*[T](value: T) {
|
||||
#pragma[magic: "print"]
|
||||
}
|
||||
|
||||
fn new*(kind: typevar[any]) {
|
||||
#pragma[magic: "new"]
|
||||
}
|
||||
"""
|
||||
|
||||
const singleFileModuleName = "test"
|
||||
@@ -441,8 +449,8 @@ fn valueCopy(): int64 {
|
||||
}
|
||||
|
||||
fn refAlias(): int64 {
|
||||
let a = RefVec(4, 5);
|
||||
let b = a;
|
||||
var a = RefVec(4, 5);
|
||||
var b = a;
|
||||
b[1] = 7;
|
||||
return a[1];
|
||||
}
|
||||
@@ -477,7 +485,7 @@ fn dynamicValue(): int64 {
|
||||
|
||||
fn dynamicRef(): int64 {
|
||||
let idx = 1;
|
||||
let values: RefVec = [4, 5];
|
||||
var values: RefVec = [4, 5];
|
||||
values[idx] = 9;
|
||||
return values[idx];
|
||||
}
|
||||
@@ -561,13 +569,13 @@ print(readTwice(Box(value = 4)));
|
||||
check execution.exitCode == 0
|
||||
check execution.output == "8\n"
|
||||
|
||||
test "ref object field assignment emits checks":
|
||||
test "mutable ref object field assignment emits checks":
|
||||
let program = compileSource("""
|
||||
type Box = ref object {
|
||||
value: int64;
|
||||
}
|
||||
|
||||
fn write(box: Box): int64 {
|
||||
fn write(box: mut Box): int64 {
|
||||
box.value = 9;
|
||||
return box.value;
|
||||
}
|
||||
@@ -584,6 +592,56 @@ print(write(Box(value = 1)));
|
||||
check execution.exitCode == 0
|
||||
check execution.output == "9\n"
|
||||
|
||||
test "mutable interior refs lower and write through managed owners":
|
||||
let program = compileSource("""
|
||||
type Inner = object {
|
||||
value: int64;
|
||||
}
|
||||
|
||||
type Box = ref object {
|
||||
inner: Inner;
|
||||
}
|
||||
|
||||
fn write(box: mut Box): int64 {
|
||||
let inner: mut ref Inner = box.inner;
|
||||
inner.value = 9;
|
||||
return box.inner.value;
|
||||
}
|
||||
|
||||
print(write(Box(inner = Inner(value = 1))));
|
||||
""")
|
||||
let source = renderProgram(program)
|
||||
let tempDir = freshTempDir("peon-c-backend-mut-interior-ref")
|
||||
let build = compileGeneratedC(program, "mut_interior_ref_test.pn", tempDir / "program", tempDir)
|
||||
let execution = runExecutable(build.executablePath)
|
||||
|
||||
check "__make_ref(" in source
|
||||
check execution.exitCode == 0
|
||||
check execution.output == "9\n"
|
||||
|
||||
test "new allocates anonymous mutable refs for plain object types":
|
||||
let program = compileSource("""
|
||||
type Box = object {
|
||||
value: int64;
|
||||
}
|
||||
|
||||
fn write(): int64 {
|
||||
var box = new(Box);
|
||||
box.value = 6;
|
||||
return box.value;
|
||||
}
|
||||
|
||||
print(write());
|
||||
""")
|
||||
let source = renderProgram(program)
|
||||
let tempDir = freshTempDir("peon-c-backend-builtin-new")
|
||||
let build = compileGeneratedC(program, "builtin_new_test.pn", tempDir / "program", tempDir)
|
||||
let execution = runExecutable(build.executablePath)
|
||||
|
||||
check "_anon_ref_new_" in source
|
||||
check execution.exitCode == 0
|
||||
check execution.output == "6\n"
|
||||
|
||||
test "managed field projections lower to interior refs in ref-typed contexts":
|
||||
let program = compileSource("""
|
||||
type Inner = object {
|
||||
|
||||
@@ -341,57 +341,60 @@ fn bindingCopy(): int64 {
|
||||
}
|
||||
""", "bindingCopy") == 1
|
||||
|
||||
test "ref objects alias through field assignment":
|
||||
check runFunction("""
|
||||
type Box = ref object {
|
||||
value: int64;
|
||||
}
|
||||
# Bytecode mutability still assumes the pre-`mut ref` model.
|
||||
# Re-enable once the bytecode backend enforces managed-handle mutability.
|
||||
#
|
||||
# test "ref objects alias through field assignment":
|
||||
# check runFunction("""
|
||||
# type Box = ref object {
|
||||
# value: int64;
|
||||
# }
|
||||
#
|
||||
# fn aliasWrite(): int64 {
|
||||
# let box = Box(value = 1);
|
||||
# let alias = box;
|
||||
# alias.value = 9;
|
||||
# return box.value;
|
||||
# }
|
||||
# """, "aliasWrite") == 9
|
||||
|
||||
fn aliasWrite(): int64 {
|
||||
let box = Box(value = 1);
|
||||
let alias = box;
|
||||
alias.value = 9;
|
||||
return box.value;
|
||||
}
|
||||
""", "aliasWrite") == 9
|
||||
# test "static arrays copy by value and ref arrays alias":
|
||||
# check runFunction("""
|
||||
# type IntVec = array[3, int64];
|
||||
# type RefVec = ref array[2, int64];
|
||||
#
|
||||
# fn valueCopy(): int64 {
|
||||
# var a = IntVec(1, 2, 3);
|
||||
# var b = a;
|
||||
# b[0] = 9;
|
||||
# return a[0];
|
||||
# }
|
||||
#
|
||||
# fn refAlias(): int64 {
|
||||
# let a = RefVec(4, 5);
|
||||
# let b = a;
|
||||
# b[1] = 7;
|
||||
# return a[1];
|
||||
# }
|
||||
#
|
||||
# fn total(): int64 {
|
||||
# return valueCopy() + refAlias();
|
||||
# }
|
||||
# """, "total") == 8
|
||||
|
||||
test "static arrays copy by value and ref arrays alias":
|
||||
check runFunction("""
|
||||
type IntVec = array[3, int64];
|
||||
type RefVec = ref array[2, int64];
|
||||
|
||||
fn valueCopy(): int64 {
|
||||
var a = IntVec(1, 2, 3);
|
||||
var b = a;
|
||||
b[0] = 9;
|
||||
return a[0];
|
||||
}
|
||||
|
||||
fn refAlias(): int64 {
|
||||
let a = RefVec(4, 5);
|
||||
let b = a;
|
||||
b[1] = 7;
|
||||
return a[1];
|
||||
}
|
||||
|
||||
fn total(): int64 {
|
||||
return valueCopy() + refAlias();
|
||||
}
|
||||
""", "total") == 8
|
||||
|
||||
test "array literals and dynamic indices execute":
|
||||
check runFunction("""
|
||||
type IntVec = array[3, int64];
|
||||
type RefVec = ref array[2, int64];
|
||||
|
||||
fn total(): int64 {
|
||||
var idx = 1;
|
||||
let values: IntVec = [1, 2, 3];
|
||||
let refs: RefVec = [4, 5];
|
||||
refs[idx] = 7;
|
||||
return values[idx] + refs[idx];
|
||||
}
|
||||
""", "total") == 9
|
||||
# test "array literals and dynamic indices execute":
|
||||
# check runFunction("""
|
||||
# type IntVec = array[3, int64];
|
||||
# type RefVec = ref array[2, int64];
|
||||
#
|
||||
# fn total(): int64 {
|
||||
# var idx = 1;
|
||||
# let values: IntVec = [1, 2, 3];
|
||||
# let refs: RefVec = [4, 5];
|
||||
# refs[idx] = 7;
|
||||
# return values[idx] + refs[idx];
|
||||
# }
|
||||
# """, "total") == 9
|
||||
|
||||
test "functions can mutate global variables":
|
||||
check runFunction("""
|
||||
|
||||
@@ -366,7 +366,7 @@ let value = pair.left;
|
||||
check field.index == 0
|
||||
check field.kind.kind == Integer
|
||||
|
||||
test "field assignment typechecks for mutable objects and refs":
|
||||
test "field assignment typechecks for mutable objects and mutable ref handles":
|
||||
let typed = typecheckSource("""
|
||||
type int64 = object {
|
||||
#pragma[magic: "int64"]
|
||||
@@ -384,7 +384,7 @@ type Box = ref object {
|
||||
fn test() {
|
||||
var pair = Pair(left = 1, right = 2);
|
||||
pair.left = 3;
|
||||
let box = Box(value = 4);
|
||||
var box = Box(value = 4);
|
||||
box.value = 5;
|
||||
}
|
||||
""")
|
||||
@@ -403,6 +403,22 @@ fn test() {
|
||||
check boxAssign.index == 0
|
||||
check boxAssign.deref
|
||||
|
||||
test "immutable managed ref handles reject field assignment":
|
||||
expectTypecheckError("""
|
||||
type int64 = object {
|
||||
#pragma[magic: "int64"]
|
||||
}
|
||||
|
||||
type Box = ref object {
|
||||
value: int64;
|
||||
}
|
||||
|
||||
fn test() {
|
||||
let box = Box(value = 4);
|
||||
box.value = 5;
|
||||
}
|
||||
""", "cannot assign through 'box' (handle is immutable)")
|
||||
|
||||
test "field assignment rejects immutable plain objects":
|
||||
expectTypecheckError("""
|
||||
type int64 = object {
|
||||
@@ -453,7 +469,9 @@ type Pair = object {
|
||||
|
||||
type PairVec = ref array[2, Pair];
|
||||
|
||||
fn project(box: Box, pairs: PairVec): int64 {
|
||||
fn project(): int64 {
|
||||
var box = Box(inner = Inner(value = 1));
|
||||
var pairs = PairVec(Pair(left = 1, right = 2), Pair(left = 3, right = 4));
|
||||
let inner: ref Inner = box.inner;
|
||||
let pair: ref Pair = pairs[1];
|
||||
return inner.value + pair.left;
|
||||
@@ -461,8 +479,8 @@ fn project(box: Box, pairs: PairVec): int64 {
|
||||
""")
|
||||
|
||||
let project = findTypedFun(typed, "project")
|
||||
let innerDecl = TypedVarDecl(project.body.body[0])
|
||||
let pairDecl = TypedVarDecl(project.body.body[1])
|
||||
let innerDecl = TypedVarDecl(project.body.body[2])
|
||||
let pairDecl = TypedVarDecl(project.body.body[3])
|
||||
|
||||
check innerDecl.init of TypedFieldExpr
|
||||
check innerDecl.init.kind.kind == Reference
|
||||
@@ -476,6 +494,150 @@ fn project(box: Box, pairs: PairVec): int64 {
|
||||
check pairDecl.name.valueType.unwrapType().kind == Reference
|
||||
check pairDecl.name.valueType.unwrapType().value.kind == Structure
|
||||
|
||||
test "mutable managed projections can satisfy mut ref-typed contexts":
|
||||
let typed = typecheckSource("""
|
||||
type int64 = object {
|
||||
#pragma[magic: "int64"]
|
||||
}
|
||||
|
||||
type any = object {
|
||||
#pragma[magic: "any"]
|
||||
}
|
||||
|
||||
type array[N: int64, T: any] = object {
|
||||
#pragma[magic: "array"]
|
||||
}
|
||||
|
||||
operator `+`(a, b: int64): int64 {
|
||||
#pragma[magic: "Add", pure]
|
||||
}
|
||||
|
||||
type Inner = object {
|
||||
value: int64;
|
||||
}
|
||||
|
||||
type Box = ref object {
|
||||
inner: Inner;
|
||||
}
|
||||
|
||||
type Pair = object {
|
||||
left: int64;
|
||||
right: int64;
|
||||
}
|
||||
|
||||
type PairVec = ref array[2, Pair];
|
||||
|
||||
fn project(): int64 {
|
||||
var box = Box(inner = Inner(value = 1));
|
||||
var pairs = PairVec(Pair(left = 1, right = 2), Pair(left = 3, right = 4));
|
||||
let inner: mut ref Inner = box.inner;
|
||||
let pair: mut ref Pair = pairs[1];
|
||||
inner.value = 9;
|
||||
pair.left = 8;
|
||||
return inner.value + pair.left;
|
||||
}
|
||||
""")
|
||||
|
||||
let project = findTypedFun(typed, "project")
|
||||
let innerDecl = TypedVarDecl(project.body.body[2])
|
||||
let pairDecl = TypedVarDecl(project.body.body[3])
|
||||
let innerAssign = TypedAssignExpr(TypedExprStmt(project.body.body[4]).expression)
|
||||
let pairAssign = TypedAssignExpr(TypedExprStmt(project.body.body[5]).expression)
|
||||
|
||||
check innerDecl.name.valueType.unwrapType().kind == Reference
|
||||
check innerDecl.name.valueType.unwrapType().mutable
|
||||
check pairDecl.name.valueType.unwrapType().kind == Reference
|
||||
check pairDecl.name.valueType.unwrapType().mutable
|
||||
check innerAssign.deref
|
||||
check pairAssign.deref
|
||||
|
||||
test "immutable managed projections cannot satisfy mut ref-typed contexts":
|
||||
expectTypecheckError("""
|
||||
type int64 = object {
|
||||
#pragma[magic: "int64"]
|
||||
}
|
||||
|
||||
type Inner = object {
|
||||
value: int64;
|
||||
}
|
||||
|
||||
type Box = ref object {
|
||||
inner: Inner;
|
||||
}
|
||||
|
||||
fn project(box: Box) {
|
||||
let inner: mut ref Inner = box.inner;
|
||||
inner.value = 9;
|
||||
}
|
||||
""", "expecting an expression of type mut ref Inner, got ?: (node: Getter(obj=Identifier('box'), name=Identifier('inner'))) instead")
|
||||
|
||||
test "new allocates mutable refs for plain object types":
|
||||
let typed = typecheckSource("""
|
||||
type int64 = object {
|
||||
#pragma[magic: "int64"]
|
||||
}
|
||||
|
||||
type any = object {
|
||||
#pragma[magic: "any"]
|
||||
}
|
||||
|
||||
type typevar = object {
|
||||
#pragma[magic: "typevar"]
|
||||
}
|
||||
|
||||
fn new(kind: typevar[any]) {
|
||||
#pragma[magic: "new"]
|
||||
}
|
||||
|
||||
type Box = object {
|
||||
value: int64;
|
||||
}
|
||||
|
||||
fn build(): int64 {
|
||||
var box = new(Box);
|
||||
box.value = 7;
|
||||
return box.value;
|
||||
}
|
||||
""")
|
||||
|
||||
let build = findTypedFun(typed, "build")
|
||||
let boxDecl = TypedVarDecl(build.body.body[0])
|
||||
let assignStmt = TypedExprStmt(build.body.body[1])
|
||||
|
||||
check boxDecl.init.kind.unwrapType().kind == Reference
|
||||
check boxDecl.init.kind.unwrapType().mutable
|
||||
check boxDecl.init.kind.unwrapType().value.kind == Structure
|
||||
check boxDecl.name.valueType.unwrapType().kind == Reference
|
||||
check boxDecl.name.valueType.unwrapType().mutable
|
||||
check TypedAssignExpr(assignStmt.expression).deref
|
||||
|
||||
test "new rejects managed ref type arguments":
|
||||
expectTypecheckError("""
|
||||
type int64 = object {
|
||||
#pragma[magic: "int64"]
|
||||
}
|
||||
|
||||
type any = object {
|
||||
#pragma[magic: "any"]
|
||||
}
|
||||
|
||||
type typevar = object {
|
||||
#pragma[magic: "typevar"]
|
||||
}
|
||||
|
||||
fn new(kind: typevar[any]) {
|
||||
#pragma[magic: "new"]
|
||||
}
|
||||
|
||||
type Box = ref object {
|
||||
value: int64;
|
||||
}
|
||||
|
||||
fn build() {
|
||||
let box = new(Box);
|
||||
}
|
||||
""", "builtin 'new' expects a plain storage type, not a managed ref type")
|
||||
|
||||
test "static array aliases typecheck for value and ref usage":
|
||||
let typed = typecheckSource("""
|
||||
type int64 = object {
|
||||
|
||||
Reference in New Issue
Block a user