Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lang: add explicit type widening (As) #122

Merged
merged 13 commits into from
Mar 23, 2025
9 changes: 9 additions & 0 deletions languages/source.nim
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const lang* = language:
Call(+expr)
FieldAccess(expr, IntVal(z))
At(expr, expr)
As(expr, texpr)
And(expr, expr)
Or(expr, expr)
If(expr, expr, expr)
Expand Down Expand Up @@ -302,6 +303,13 @@ const lang* = language:
where SeqTy(typ_2), typ_1
conclusion C_1, At(e_1, e_2), mut(typ_2)

rule "S-as":
premise mtypes(C_1, e_1, typ_1)
premise ttypes(C_1, texpr_1, typ_2)
condition typ_2 != VoidTy()
condition typ_1 <:= typ_2
conclusion C_1, As(e_1, texpr_1), typ_2

rule "S-asgn":
premise types(C_1, e_1, mut(typ_1))
premise mtypes(C_1, e_2, typ_2)
Expand Down Expand Up @@ -689,6 +697,7 @@ const lang* = language:
axiom "E-exprs", Exprs(TupleCons(), +e_1), Exprs(...e_1)
axiom "E-if-true", If(True, e_1, e_2), e_1
axiom "E-if-false", If(False, e_1, e_2), e_2
axiom "E-as", As(e_1, texpr), e_1 # a no-op

axiom "E-while", While(e_1, e_2), If(e_1, Exprs(e_2, While(e_1, e_2)), TupleCons())

Expand Down
1 change: 1 addition & 0 deletions languages/specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ expr ::= <ident>
| (Call <expr>+)
| (FieldAccess <expr> <intVal>)
| (At <expr> <expr>)
| (As <expr> <texpr>)
| (And <expr> <expr>)
| (Or <expr> <expr>)
| (If <expr> <expr> <expr>?)
Expand Down
13 changes: 12 additions & 1 deletion passes/source2il.nim
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,11 @@ proc fitExpr(c; e: sink Expr, target: SemType): Expr =
else:
# TODO: this needs a better error message
c.error("type mismatch")
result = Expr(stmts: e.stmts, typ: errorType())
# still return a proper expression so that analysis can carry on
if target.kind == tkVoid:
result = Expr(typ: target, stmts: e.stmts)
else:
result = Expr(typ: target, stmts: e.stmts, expr: e.expr)

proc fitExprStrict(c; e: sink Expr, typ: SemType): Expr =
## Makes sure expression `e` fits `typ` exactly, reporting an error and
Expand Down Expand Up @@ -1274,6 +1278,13 @@ proc exprToIL(c; t: InTree, n: NodeIndex, expr, stmts): ExprType =
newCall(at, @[c.inlineLvalue(arr, stmts), c.capture(idx, stmts)]))
else:
result = errorType() + arr.attribs
of SourceKind.As:
let
(a, b) = t.pair(n)
e = c.exprToIL(t, a)
typ = c.expectNot(c.evalType(t, b), tkVoid)
expr = inline(c.fitExpr(e, typ), stmts)
result = typ + {} # lvalue-ness and mutability are discarded
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still learning to read the source, but it seems that lvalue-ness and mutability dropping isn't there, I'm guessing it can't be represented?

I guess the drop is required right now because if we're actually widening the bits then a new location would be required?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still learning to read the source, but it seems that lvalue-ness and mutability dropping isn't there, I'm guessing it can't be represented?

It's there, albeit not easy to spot. exprToIL (the enclosing procedure) returns an ExprType, which is a type + qualifiers (ExprFlags, currently a bunch of flags), where the qualifiers track both mutability and lvalue-ness.

If the qualifiers were to be kept, the result assignment would look like this:

result = typ + e.attribs # e.attribs are the source expression's qualifiers 

I guess the drop is required right now because if we're actually widening the bits then a new location would be required?

Yep, that's the idea. If no widening takes place, then no new location is needed, so the lvalue-ness and mutability could be kept, but I decided against it because:

  • to the human reader, a large context window is potentially required to know what a specific As does (the same problem exists in NimSkull with lvalue conversions)
  • the lvalue-ness/mutability changing depending on the target type could become an issue once/if per-procedure type inference is introduced

This doesn't have to (nor should it have to) be the final decision, however. If it turns out that the current rules are problematic (for whatever reason), we should alter them.

of SourceKind.Asgn:
let (a, b) = t.pair(n)
var dst = c.exprToIL(t, a)
Expand Down
4 changes: 3 additions & 1 deletion passes/syntax_source.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type
TupleCons
Seq
FieldAccess, At
As
Exprs
Asgn
Return
Expand All @@ -34,7 +35,8 @@ type

const
ExprNodes* = {IntVal, FloatVal, Ident, And, Or, If, While, Call, TupleCons,
Seq, FieldAccess, At, Asgn, Return, Unreachable, Exprs, Decl}
Seq, FieldAccess, At, As, Asgn, Return, Unreachable, Exprs,
Decl}
DeclNodes* = {ProcDecl, TypeDecl}
AllNodes* = {low(NodeKind) .. high(NodeKind)}

Expand Down
4 changes: 4 additions & 0 deletions tests/expr/t02_as.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
discard """
output: "1 : (IntTy)"
"""
(As 1 (IntTy))
4 changes: 4 additions & 0 deletions tests/expr/t02_as_no_void_error.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
discard """
reject: true
"""
(As 1 (VoidTy))
4 changes: 4 additions & 0 deletions tests/expr/t02_as_type_mismatch_error.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
discard """
reject: true
"""
(As 1 (UnitTy))
3 changes: 1 addition & 2 deletions tests/expr/t05_union_type_1.test
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
discard """
output: "100 : (UnionTy (IntTy) (FloatTy))"
"""
(ProcDecl (Ident "a") (UnionTy (IntTy) (FloatTy)) (Params)
(Return (IntVal 100)))
(As 100 (UnionTy (IntTy) (FloatTy)))
3 changes: 1 addition & 2 deletions tests/expr/t05_union_type_2.test
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
discard """
output: "1.5 : (UnionTy (IntTy) (FloatTy))"
"""
(ProcDecl (Ident "a") (UnionTy (IntTy) (FloatTy)) (Params)
(Return (FloatVal 1.5)))
(As 1.5 (UnionTy (IntTy) (FloatTy)))
3 changes: 1 addition & 2 deletions tests/expr/t05_union_type_of_single_type.test
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
discard """
output: "100 : (UnionTy (IntTy))"
"""
(ProcDecl (Ident "a") (UnionTy (IntTy)) (Params)
(Return (IntVal 100)))
(As 100 (UnionTy (IntTy)))
3 changes: 1 addition & 2 deletions tests/expr/t06_union_duplicate_types.test
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
discard """
reject: true
"""
(ProcDecl (Ident "a") (UnionTy (IntTy) (IntTy)) (Params)
(Return (IntVal 100)))
(As 100 (UnionTy (IntTy) (IntTy)))
13 changes: 3 additions & 10 deletions tests/expr/t08_if_expr_unify_type_1.test
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
discard """
description: "Ensure types unify for an if-expression"
"""
(Module
(ProcDecl (Ident "p") (UnionTy (IntTy) (FloatTy)) (Params)
(Return (IntVal 100)))

(ProcDecl (Ident "test") (UnionTy (IntTy) (FloatTy)) (Params)
(Return
(If (Ident "true")
(FloatVal 0.5)
(Call (Ident "p")))))
)
(If (Ident "true")
(FloatVal 0.5)
(As (IntVal 100) (UnionTy (IntTy) (FloatTy)))))
13 changes: 3 additions & 10 deletions tests/expr/t08_if_expr_unify_type_2.test
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
discard """
description: "Ensure types unify for an if-expression"
"""
(Module
(ProcDecl (Ident "p") (UnionTy (IntTy) (FloatTy)) (Params)
(Return (IntVal 100)))

(ProcDecl (Ident "test") (UnionTy (IntTy) (FloatTy)) (Params)
(Return
(If (Ident "false")
(Call (Ident "p"))
(FloatVal 0.5))))
)
(If (Ident "false")
(As (IntVal 100) (UnionTy (IntTy) (FloatTy)))
(FloatVal 0.5))))
14 changes: 5 additions & 9 deletions tests/expr/t09_asgn_compatible_type.test
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
discard """
output: "1.5 : (UnionTy (IntTy) (FloatTy))"
"""
(Module
(ProcDecl (Ident "union") (UnionTy (IntTy) (FloatTy)) (Params)
(Return (IntVal 100)))

(ProcDecl (Ident "test") (UnionTy (IntTy) (FloatTy)) (Params)
(Exprs
(Decl (Ident "x") (Call (Ident "union")))
(Asgn (Ident "x") (FloatVal 1.5))
(Return (Ident "x")))))
(Exprs
(Decl (Ident "x")
(As (IntVal 100) (UnionTy (IntTy) (FloatTy))))
(Asgn (Ident "x") (FloatVal 1.5))
(Ident "x"))
17 changes: 7 additions & 10 deletions tests/expr/t17_seq_copy_3.test
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
discard """
output: "(array 1 2 3) : (UnionTy (IntTy) (SeqTy (IntTy)))"
"""
(Module
(ProcDecl union (UnionTy (IntTy) (SeqTy (IntTy))) (Params)
(Return 1))
(ProcDecl main (UnionTy (IntTy) (SeqTy (IntTy))) (Params)
(Exprs
(Decl x (Seq (IntTy) 1 2 3))
(Decl y (Call union))
(Asgn y x)
(Asgn (At x 0) 4)
(Return y))))
(Exprs
(Decl x (Seq (IntTy) 1 2 3))
(Decl y
(As 1 (UnionTy (IntTy) (SeqTy (IntTy)))))
(Asgn y x)
(Asgn (At x 0) 4)
y)
14 changes: 6 additions & 8 deletions tests/expr/t17_seq_nested_copy_3.test
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ discard """
description: "Sequences part of unions are fully copied when the union is"
output: "(array 1 2 3) : (UnionTy (IntTy) (SeqTy (IntTy)))"
"""
(Module
(ProcDecl union (UnionTy (IntTy) (SeqTy (IntTy))) (Params)
(Return (Seq (IntTy) 1 2 3)))
(ProcDecl main (UnionTy (IntTy) (SeqTy (IntTy))) (Params)
(Exprs
(Decl x (Call union))
(Decl y x)
(Return y))))
(Exprs
(Decl x
(As (Seq (IntTy) 1 2 3)
(UnionTy (IntTy) (SeqTy (IntTy)))))
(Decl y x)
y)
Loading