Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,844 changes: 2,994 additions & 2,850 deletions compiler/parser/parser.go

Large diffs are not rendered by default.

18 changes: 14 additions & 4 deletions compiler/parser/parser.peg
Original file line number Diff line number Diff line change
Expand Up @@ -939,9 +939,9 @@ Assignment
Expr = CondExpr

CondExpr
= cond:LogicalOrExpr opt:(__ "?" __ Expr __ ":" __ Expr)? {
= cond:NoneishExpr opt:(__ "?" !"?" __ Expr __ ":" __ Expr)? {
if opt != nil {
t, e := opt.([]any)[3], opt.([]any)[7]
t, e := opt.([]any)[4], opt.([]any)[8]
return &ast.CondExpr{
Kind: "CondExpr",
Cond: cond.(ast.Expr),
Expand All @@ -953,6 +953,12 @@ CondExpr
return cond, nil
}

NoneishExpr
= first:LogicalOrExpr
rest:(__ "??" __ expr:LogicalOrExpr { return []any{"??", expr}, nil })* {
return makeBinaryExprChain(first, rest, c), nil
}

LogicalOrExpr
= first:LogicalAndExpr
rest:(__ op:OR __ expr:LogicalAndExpr { return []any{op, expr}, nil })* {
Expand Down Expand Up @@ -1084,10 +1090,10 @@ DerefExpr
Loc: loc(c),
}, nil
}
/ expr:DerefExpr "." id:DerefKey {
/ expr:DerefExpr op:DotOp id:DerefKey {
return &ast.BinaryExpr{
Kind: "BinaryExpr",
Op: ".",
Op: op.(string),
LHS: expr.(ast.Expr),
RHS: id.(ast.Expr),
Loc: loc(c),
Expand All @@ -1097,6 +1103,10 @@ DerefExpr
/ Function
/ Primary

DotOp
= "." { return ".", nil}
/ "?." { return "?.", nil }

DerefKey
= id:Identifier { return &ast.IDExpr{Kind: "IDExpr", ID: *(id.(*ast.ID)) }, nil }
/ s:DoubleQuotedString { return &ast.IDExpr{Kind: "IDExpr", ID: ast.ID{Name:s.(string), Loc: loc(c)}}, nil }
Expand Down
2 changes: 1 addition & 1 deletion compiler/rungen/vop.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func (b *Builder) compileVamLeaf(o dag.Op, parent vio.Puller) (vio.Puller, error
if err != nil {
return nil, err
}
return vamop.NewDistinct(parent, e), nil
return vamop.NewDistinct(b.sctx(), parent, e), nil
case *dag.DropOp:
fields := make(field.List, 0, len(o.Args))
for _, e := range o.Args {
Expand Down
21 changes: 17 additions & 4 deletions compiler/semantic/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func (c *checker) assignments(in super.Type, assignments []sem.Assignment) []pat
for _, a := range assignments {
var path []string
if this, ok := a.LHS.(*sem.ThisExpr); ok {
path = this.Path
path = this.Path.IDs()
}
typ := c.expr(in, a.RHS)
paths = append(paths, pathType{path, typ})
Expand Down Expand Up @@ -448,8 +448,9 @@ func (c *checker) binary(op string, loc, lloc, rloc ast.Node, lhs, rhs super.Typ
}

func (c *checker) this(loc ast.Node, this *sem.ThisExpr, typ super.Type) super.Type {
for _, field := range this.Path {
typ, _ = c.deref(loc, typ, field)
for _, comp := range this.Path {
//XXX type check should use comp.Nullish too
typ, _ = c.deref(loc, typ, comp.ID)
}
return typ
}
Expand Down Expand Up @@ -672,7 +673,7 @@ func (c *checker) lvalsToPaths(exprs []sem.Expr) []path {
if !ok {
return nil
}
paths = append(paths, path{loc: this.Node, elems: this.Path})
paths = append(paths, path{loc: this.Node, elems: this.Path.IDs()})
}
return paths
}
Expand Down Expand Up @@ -948,6 +949,18 @@ func hasString(typ super.Type) bool {
return false
}

func hasNone(typ super.Type) bool {
switch typ := super.TypeUnder(typ).(type) {
case *super.TypeError:
return isUnknown(typ)
case *super.TypeOfNone:
return true
case *super.TypeUnion:
return slices.ContainsFunc(typ.Types, hasNone)
}
return false
}

func isUnknown(typ super.Type) bool {
if err, ok := super.TypeUnder(typ).(*super.TypeError); ok {
if rec, ok := err.Type.(*super.TypeRecord); ok {
Expand Down
2 changes: 1 addition & 1 deletion compiler/semantic/dagen.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ func (d *dagen) expr(e sem.Expr) dag.Expr {
case *sem.ThisExpr:
return &dag.ThisExpr{
Kind: "ThisExpr",
Path: e.Path,
Path: e.Path.IDs(),
}
case *sem.TypeExpr:
return &dag.TypeExpr{
Expand Down
58 changes: 37 additions & 21 deletions compiler/semantic/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ func (t *translator) expr(e ast.Expr, inType super.Type) (sem.Expr, super.Type)
panic(e)
}

func (t *translator) dot(e *ast.BinaryExpr, inType super.Type) (sem.Expr, super.Type) {
func (t *translator) dot(e *ast.BinaryExpr, nullish bool, inType super.Type) (sem.Expr, super.Type) {
id, ok := e.RHS.(*ast.IDExpr)
if !ok {
t.error(e, errors.New("RHS of dot operator is not an identifier"))
Expand All @@ -430,41 +430,42 @@ func (t *translator) dot(e *ast.BinaryExpr, inType super.Type) (sem.Expr, super.
// Check for plain-ID this (not double quoted) and resolve accordingly.
if lhs.Name == "this" && t.scope.sql != nil {
this, typ := t.scope.resolveThis(t, lhs, inType)
return t.deref(e, this, id, typ)
return t.deref(e, this, id, nullish, typ)
}
return t.dottedBaseCase(e, lhs, id, inType)
return t.dottedBaseCase(e, lhs, id, nullish, inType)
}
// Handle SQL double-quoted IDs without checking for this.
if lhs, ok := e.LHS.(*ast.DoubleQuoteExpr); ok && t.scope.sql != nil {
lhsID := &ast.IDExpr{ID: ast.ID{Name: lhs.Text, Loc: lhs.Loc}}
return t.dottedBaseCase(e, lhsID, id, inType)
return t.dottedBaseCase(e, lhsID, id, nullish, inType)
}
lhs, typ := t.expr(e.LHS, inType)
return t.deref(e, lhs, id, typ)
return t.deref(e, lhs, id, nullish, typ)
}

func (t *translator) dottedBaseCase(loc ast.Node, lhs *ast.IDExpr, rhs *ast.IDExpr, inType super.Type) (sem.Expr, super.Type) {
func (t *translator) dottedBaseCase(loc ast.Node, lhs *ast.IDExpr, rhs *ast.IDExpr, nullish bool, inType super.Type) (sem.Expr, super.Type) {
if e, typ := t.idExpand(lhs, false, inType); e != nil {
return t.deref(loc, e, rhs, typ)
return t.deref(loc, e, rhs, nullish, typ)
}
if t.scope.sql != nil {
return t.scope.resolve(t, loc, []string{lhs.Name, rhs.Name}, inType)
}
id, typ := t.idExpr(lhs, false, inType)
return t.deref(loc, id, rhs, typ)
return t.deref(loc, id, rhs, nullish, typ)
}

func (t *translator) deref(loc ast.Node, lhs sem.Expr, id *ast.IDExpr, inType super.Type) (sem.Expr, super.Type) {
func (t *translator) deref(loc ast.Node, lhs sem.Expr, id *ast.IDExpr, nullish bool, inType super.Type) (sem.Expr, super.Type) {
typ, _ := t.checker.deref(id, inType, id.Name)
if lhs, ok := lhs.(*sem.ThisExpr); ok {
lhs.Path = append(lhs.Path, id.Name)
lhs.Path = append(lhs.Path, sem.PathElem{ID: id.Name, Nullish: nullish})
lhs.Node = loc
return lhs, typ
}
return &sem.DotExpr{
Node: loc,
LHS: lhs,
RHS: id.Name,
//XXX nullish
}, typ
}

Expand All @@ -478,9 +479,9 @@ func (t *translator) idExpr(id *ast.IDExpr, lval bool, inType super.Type) (sem.E
}
return t.scope.resolve(t, id, []string{id.Name}, inType)
}
var path []string
var path sem.Path
if id.Name != "this" {
path = []string{id.Name}
path = sem.NewPath(id.Name)
}
this := sem.NewThis(id, path)
return this, t.checker.this(id, this, inType)
Expand Down Expand Up @@ -578,8 +579,8 @@ func (t *translator) binaryExpr(e *ast.BinaryExpr, inType super.Type) (sem.Expr,
return e, typ
}
op := strings.ToLower(e.Op)
if op == "." {
return t.dot(e, inType)
if op == "." || op == "?." {
return t.dot(e, op == "?.", inType)
}
lhs, lhsType := t.expr(e.LHS, inType)
rhs, rhsType := t.expr(e.RHS, inType)
Expand Down Expand Up @@ -622,6 +623,15 @@ func (t *translator) binaryExpr(e *ast.BinaryExpr, inType super.Type) (sem.Expr,
Tag: "concat",
Args: []sem.Expr{lhs, rhs},
}, super.TypeString
case "??":
t.noneish(e.LHS, lhsType)
t.expr(e.RHS, rhsType)
//XXX we use coalesce for now and will have a dedicated operator in a subsequent PR
return &sem.CallExpr{
Node: e,
Tag: "coalesce",
Args: []sem.Expr{lhs, rhs},
}, t.checker.unknown //XXX this should be union of LHS and RHS
case "not in":
t.checker.in(e, e.LHS, e.RHS, lhsType, rhsType)
return sem.NewUnaryExpr(e, "!", sem.NewBinaryExpr(e, "in", lhs, rhs)), super.TypeBool
Expand All @@ -646,10 +656,16 @@ func (t *translator) stringy(loc ast.Node, typ super.Type) {
}
}

func (t *translator) noneish(loc ast.Node, typ super.Type) {
if !hasNone(typ) {
t.error(loc, errors.New("none check used with expression that cannot be none"))
}
}

func (t *translator) isIndexOfThis(lhs, rhs sem.Expr) *sem.ThisExpr {
if this, ok := lhs.(*sem.ThisExpr); ok {
if s, ok := t.maybeEvalString(rhs); ok {
this.Path = append(this.Path, s)
this.Path = append(this.Path, sem.PathElem{ID: s})
return this
}
}
Expand Down Expand Up @@ -957,7 +973,7 @@ func (t *translator) assignment(assign *ast.Assignment, inType super.Type) (sem.
rhs, typ := t.expr(assign.RHS, inType)
var lhs sem.Expr
if assign.LHS == nil {
lhs = sem.NewThis(assign.RHS, []string{deriveNameFromExpr(assign.RHS)})
lhs = sem.NewThis(assign.RHS, sem.NewPath(deriveNameFromExpr(assign.RHS)))
} else {
lhs = t.lval(assign.LHS)
}
Expand Down Expand Up @@ -998,7 +1014,7 @@ func isLval(e sem.Expr) ([]string, bool) {
}
return path, ok
case *sem.ThisExpr:
return e.Path, true
return e.Path.IDs(), true
}
return nil, false
}
Expand Down Expand Up @@ -1173,7 +1189,7 @@ func (t *translator) aggFunc(n ast.Node, name string, arg ast.Expr, filter ast.E
func DotExprToFieldPath(e ast.Expr) *sem.ThisExpr {
switch e := e.(type) {
case *ast.BinaryExpr:
if e.Op == "." {
if e.Op == "." || e.Op == "?." {
lhs := DotExprToFieldPath(e.LHS)
if lhs == nil {
return nil
Expand All @@ -1182,7 +1198,7 @@ func DotExprToFieldPath(e ast.Expr) *sem.ThisExpr {
if !ok {
return nil
}
lhs.Path = append(lhs.Path, id.Name)
lhs.Path = append(lhs.Path, sem.PathElem{ID: id.Name, Nullish: e.Op == "?."})
return lhs
}
case *ast.IndexExpr:
Expand All @@ -1194,10 +1210,10 @@ func DotExprToFieldPath(e ast.Expr) *sem.ThisExpr {
if !ok || id.Type != "string" {
return nil
}
this.Path = append(this.Path, id.Text)
this.Path = append(this.Path, sem.PathElem{ID: id.Text})
return this
case *ast.IDExpr:
return sem.NewThis(e, []string{e.Name})
return sem.NewThis(e, sem.NewPath(e.Name))
}
// This includes a null Expr, which can happen if the AST is missing
// a field or sets it to null.
Expand Down
24 changes: 12 additions & 12 deletions compiler/semantic/op.go
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ func (t *translator) semOp(o ast.Op, seq sem.Seq, inType super.Type) (sem.Seq, s
var fields field.List
for _, a := range assignments {
if this, ok := a.LHS.(*sem.ThisExpr); ok {
fields = append(fields, this.Path)
fields = append(fields, this.Path.IDs())
}
}
if _, err := super.NewRecordBuilder(t.sctx, fields); err != nil {
Expand Down Expand Up @@ -899,7 +899,7 @@ func (t *translator) semOp(o ast.Op, seq sem.Seq, inType super.Type) (sem.Seq, s
var fields field.List
for _, a := range assignments {
if this, ok := a.LHS.(*sem.ThisExpr); ok {
fields = append(fields, this.Path)
fields = append(fields, this.Path.IDs())
}
}
if err := checkPutFields(fields); err != nil {
Expand All @@ -925,7 +925,7 @@ func (t *translator) semOp(o ast.Op, seq sem.Seq, inType super.Type) (sem.Seq, s
lhs, lhsOk := assign.LHS.(*sem.ThisExpr)
rhs, rhsOk := assign.RHS.(*sem.ThisExpr)
if rhsOk && lhsOk {
if err := expr.CheckRenameField(lhs.Path, rhs.Path); err != nil {
if err := expr.CheckRenameField(lhs.Path.IDs(), rhs.Path.IDs()); err != nil {
t.error(&fa, err)
}
}
Expand Down Expand Up @@ -985,19 +985,19 @@ func (t *translator) semOp(o ast.Op, seq sem.Seq, inType super.Type) (sem.Seq, s
Aggs: []sem.Assignment{
{
Node: o,
LHS: sem.NewThis(o, []string{"sample"}),
LHS: sem.NewThis(o, sem.NewPath("sample")),
RHS: &sem.AggFunc{Node: o, Name: "any", Expr: e},
},
},
Keys: []sem.Assignment{
{
Node: o,
LHS: sem.NewThis(o, []string{"shape"}),
LHS: sem.NewThis(o, sem.NewPath("shape")),
RHS: sem.NewCall(o, "typeof", []sem.Expr{e}),
},
},
})
return append(seq, sem.NewValues(o, sem.NewThis(o, []string{"sample"}))), t.checker.unknown
return append(seq, sem.NewValues(o, sem.NewThis(o, sem.NewPath("sample")))), t.checker.unknown
case *ast.AssertOp:
cond, _ := t.expr(o.Expr, inType)
// 'assert EXPR' is equivalent to
Expand Down Expand Up @@ -1181,8 +1181,8 @@ func (t *translator) pipeJoinCond(cond ast.JoinCond, leftAlias, rightAlias strin
case *ast.JoinUsingCond:
var exprs []sem.Expr
for _, id := range cond.Fields {
lhs := sem.NewThis(id, []string{leftAlias, id.Name})
rhs := sem.NewThis(id, []string{rightAlias, id.Name})
lhs := sem.NewThis(id, sem.NewPath(leftAlias, id.Name))
rhs := sem.NewThis(id, sem.NewPath(rightAlias, id.Name))
exprs = append(exprs, sem.NewBinaryExpr(id, "==", lhs, rhs))
}
return andUsingExprs(cond, exprs)
Expand Down Expand Up @@ -1566,12 +1566,12 @@ func (t *translator) maybeCallShortcut(call *ast.CallExpr, seq sem.Seq, inType s
Aggs: []sem.Assignment{
{
Node: call,
LHS: sem.NewThis(f, []string{name}),
LHS: sem.NewThis(f, sem.NewPath(name)),
RHS: agg,
},
},
}
values := sem.NewValues(call, sem.NewThis(call, []string{name}))
values := sem.NewValues(call, sem.NewThis(call, sem.NewPath(name)))
return append(append(seq, aggregate), values), typ
}
if !function.HasBoolResult(strings.ToLower(name)) {
Expand All @@ -1591,12 +1591,12 @@ func (t *translator) aggFuncShortcut(agg *ast.AggFuncExpr, seq sem.Seq, inType s
Aggs: []sem.Assignment{
{
Node: aggFunc,
LHS: sem.NewThis(agg, []string{name}),
LHS: sem.NewThis(agg, sem.NewPath(name)),
RHS: aggFunc,
},
},
}
values := sem.NewValues(agg, sem.NewThis(agg, []string{name}))
values := sem.NewValues(agg, sem.NewThis(agg, sem.NewPath(name)))
return append(seq, aggregate, values), typ
}

Expand Down
Loading
Loading