Skip to content

Commit 0e825ae

Browse files
committed
implemented more checks
1 parent ad44569 commit 0e825ae

File tree

4 files changed

+210
-32
lines changed

4 files changed

+210
-32
lines changed

analysis/check.go

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,15 @@ type Diagnostic struct {
8282
}
8383

8484
type CheckResult struct {
85-
emptiedAccount map[string]struct{}
86-
unboundedSend bool
87-
declaredVars map[string]parser.VarDeclaration
88-
unusedVars map[string]parser.Range
89-
varResolution map[*parser.VariableLiteral]parser.VarDeclaration
90-
fnCallResolution map[*parser.FnCallIdentifier]FnCallResolution
91-
Diagnostics []Diagnostic
92-
Program parser.Program
85+
unboundedAccountInSend parser.Literal
86+
emptiedAccount map[string]struct{}
87+
unboundedSend bool
88+
declaredVars map[string]parser.VarDeclaration
89+
unusedVars map[string]parser.Range
90+
varResolution map[*parser.VariableLiteral]parser.VarDeclaration
91+
fnCallResolution map[*parser.FnCallIdentifier]FnCallResolution
92+
Diagnostics []Diagnostic
93+
Program parser.Program
9394
}
9495

9596
func (r CheckResult) GetErrorsCount() int {
@@ -154,6 +155,7 @@ func (res *CheckResult) check() {
154155
}
155156
}
156157
for _, statement := range res.Program.Statements {
158+
res.unboundedAccountInSend = nil
157159
res.checkStatement(statement)
158160
}
159161

@@ -382,32 +384,30 @@ func (res *CheckResult) checkSentValue(sentValue parser.SentValue) {
382384
}
383385
}
384386

385-
func (res *CheckResult) withCloneEmptyAccount() func() {
386-
bkEmptiedAccount := res.emptiedAccount
387-
res.emptiedAccount = make(map[string]struct{})
388-
for k, v := range bkEmptiedAccount {
389-
res.emptiedAccount[k] = v
390-
}
391-
return func() {
392-
res.emptiedAccount = bkEmptiedAccount
393-
}
394-
}
395-
396387
func (res *CheckResult) checkSource(source parser.Source) {
397388
if source == nil {
398389
return
399390
}
400391

392+
if res.unboundedAccountInSend != nil {
393+
res.Diagnostics = append(res.Diagnostics, Diagnostic{
394+
Range: source.GetRange(),
395+
Kind: &UnboundedAccountIsNotLast{},
396+
})
397+
}
398+
401399
switch source := source.(type) {
402400
case *parser.AccountLiteral:
403-
if source.Name == "world" && res.unboundedSend {
401+
if source.IsWorld() && res.unboundedSend {
404402
res.Diagnostics = append(res.Diagnostics, Diagnostic{
405403
Range: source.GetRange(),
406404
Kind: &InvalidUnboundedAccount{},
407405
})
406+
} else if source.IsWorld() {
407+
res.unboundedAccountInSend = source
408408
}
409409

410-
if _, emptied := res.emptiedAccount[source.Name]; emptied && source.Name != "world" {
410+
if _, emptied := res.emptiedAccount[source.Name]; emptied && !source.IsWorld() {
411411
res.Diagnostics = append(res.Diagnostics, Diagnostic{
412412
Kind: &EmptiedAccount{Name: source.Name},
413413
Range: source.Range,
@@ -420,13 +420,17 @@ func (res *CheckResult) checkSource(source parser.Source) {
420420
res.checkLiteral(source, TypeAccount)
421421

422422
case *parser.SourceOverdraft:
423-
if accountLiteral, ok := source.Address.(*parser.AccountLiteral); ok && accountLiteral.Name == "world" {
423+
if accountLiteral, ok := source.Address.(*parser.AccountLiteral); ok && accountLiteral.IsWorld() {
424424
res.Diagnostics = append(res.Diagnostics, Diagnostic{
425425
Range: accountLiteral.Range,
426426
Kind: &InvalidWorldOverdraft{},
427427
})
428428
}
429429

430+
if source.Bounded == nil {
431+
res.unboundedAccountInSend = source.Address
432+
}
433+
430434
if res.unboundedSend {
431435
res.Diagnostics = append(res.Diagnostics, Diagnostic{
432436
Range: source.Address.GetRange(),
@@ -445,18 +449,13 @@ func (res *CheckResult) checkSource(source parser.Source) {
445449
}
446450

447451
case *parser.SourceCapped:
448-
bkSendAll := res.unboundedSend
449-
res.unboundedSend = false
450-
defer func() {
451-
res.unboundedSend = bkSendAll
452-
}()
453-
454-
handler := res.withCloneEmptyAccount()
455-
defer handler()
452+
onExit := res.enterCappedSource()
456453

457454
res.checkLiteral(source.Cap, TypeMonetary)
458455
res.checkSource(source.From)
459456

457+
onExit()
458+
460459
case *parser.SourceAllotment:
461460
if res.unboundedSend {
462461
res.Diagnostics = append(res.Diagnostics, Diagnostic{
@@ -489,9 +488,9 @@ func (res *CheckResult) checkSource(source parser.Source) {
489488
}
490489
}
491490

492-
handler := res.withCloneEmptyAccount()
491+
onExit := res.enterCappedSource()
493492
res.checkSource(allottedItem.From)
494-
handler()
493+
onExit()
495494
}
496495

497496
res.checkHasBadAllotmentSum(*sum, source.Range, remainingAllotment, variableLiterals)
@@ -606,3 +605,44 @@ func (res *CheckResult) checkHasBadAllotmentSum(
606605
}
607606
}
608607
}
608+
609+
func (res *CheckResult) withCloneEmptyAccount() func() {
610+
initial := res.emptiedAccount
611+
res.emptiedAccount = make(map[string]struct{})
612+
for k, v := range initial {
613+
res.emptiedAccount[k] = v
614+
}
615+
return func() {
616+
res.emptiedAccount = initial
617+
}
618+
}
619+
620+
func (res *CheckResult) withCloneUnboundedAccountInSend() func() {
621+
initial := res.unboundedAccountInSend
622+
res.unboundedAccountInSend = nil
623+
624+
return func() {
625+
res.unboundedAccountInSend = initial
626+
}
627+
}
628+
629+
func (res *CheckResult) withCloneUnboundedSend() func() {
630+
initial := res.unboundedSend
631+
res.unboundedSend = false
632+
633+
return func() {
634+
res.unboundedSend = initial
635+
}
636+
}
637+
638+
func (res *CheckResult) enterCappedSource() func() {
639+
exitCloneEmptyAccount := res.withCloneEmptyAccount()
640+
exitCloneUnboundeAccountInSend := res.withCloneUnboundedAccountInSend()
641+
exitCloneUnboundedSend := res.withCloneUnboundedSend()
642+
643+
return func() {
644+
exitCloneEmptyAccount()
645+
exitCloneUnboundeAccountInSend()
646+
exitCloneUnboundedSend()
647+
}
648+
}

analysis/check_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,3 +1232,127 @@ func TestDoNotEmitEmptiedAccountOnAllotment(t *testing.T) {
12321232
diagnostics := analysis.CheckSource(input).Diagnostics
12331233
require.Empty(t, diagnostics)
12341234
}
1235+
1236+
func TestDoNotAllowExprAfterWorld(t *testing.T) {
1237+
input := `
1238+
send [COIN 100] (
1239+
source = {
1240+
@world
1241+
@another
1242+
}
1243+
destination = @dest
1244+
)
1245+
`
1246+
1247+
diagnostics := analysis.CheckSource(input).Diagnostics
1248+
require.Len(t, diagnostics, 1)
1249+
1250+
require.Equal(t,
1251+
diagnostics[0].Kind,
1252+
&analysis.UnboundedAccountIsNotLast{},
1253+
)
1254+
1255+
require.Equal(t,
1256+
diagnostics[0].Range,
1257+
RangeOfIndexed(input, "@another", 0),
1258+
)
1259+
}
1260+
1261+
func TestAllowWorldInNextExpr(t *testing.T) {
1262+
input := `
1263+
send [COIN 1] (
1264+
source = @world
1265+
destination = @dest
1266+
)
1267+
1268+
send [COIN 1] (
1269+
source = @world
1270+
destination = @dest
1271+
)
1272+
`
1273+
1274+
diagnostics := analysis.CheckSource(input).Diagnostics
1275+
require.Empty(t, diagnostics)
1276+
1277+
}
1278+
1279+
func TestAllowWorldInMaxedExpr(t *testing.T) {
1280+
input := `
1281+
send [COIN 10] (
1282+
source = {
1283+
max [COIN 1] from @world
1284+
@x
1285+
}
1286+
destination = @dest
1287+
)
1288+
`
1289+
1290+
diagnostics := analysis.CheckSource(input).Diagnostics
1291+
require.Empty(t, diagnostics)
1292+
1293+
}
1294+
1295+
func TestDoNotAllowExprAfterWorldInsideMaxed(t *testing.T) {
1296+
input := `
1297+
send [COIN 10] (
1298+
source = max [COIN 1] from {
1299+
@world
1300+
@x
1301+
}
1302+
destination = @dest
1303+
)
1304+
`
1305+
1306+
diagnostics := analysis.CheckSource(input).Diagnostics
1307+
require.Len(t, diagnostics, 1)
1308+
1309+
require.Equal(t,
1310+
diagnostics[0].Kind,
1311+
&analysis.UnboundedAccountIsNotLast{},
1312+
)
1313+
1314+
require.Equal(t,
1315+
diagnostics[0].Range,
1316+
RangeOfIndexed(input, "@x", 0),
1317+
)
1318+
}
1319+
1320+
func TestDoNotAllowExprAfterUnbounded(t *testing.T) {
1321+
input := `
1322+
send [COIN 100] (
1323+
source = {
1324+
@unbounded allowing unbounded overdraft
1325+
@another
1326+
}
1327+
destination = @dest
1328+
)
1329+
`
1330+
1331+
diagnostics := analysis.CheckSource(input).Diagnostics
1332+
require.Len(t, diagnostics, 1)
1333+
1334+
require.Equal(t,
1335+
diagnostics[0].Kind,
1336+
&analysis.UnboundedAccountIsNotLast{},
1337+
)
1338+
1339+
require.Equal(t,
1340+
diagnostics[0].Range,
1341+
RangeOfIndexed(input, "@another", 0),
1342+
)
1343+
}
1344+
1345+
func TestAllowExprAfterBoundedOverdraft(t *testing.T) {
1346+
input := `
1347+
send [COIN 100] (
1348+
source = {
1349+
@unbounded allowing overdraft up to [COIN 10]
1350+
@another
1351+
}
1352+
destination = @dest
1353+
)
1354+
`
1355+
1356+
diagnostics := analysis.CheckSource(input).Diagnostics
1357+
require.Empty(t, diagnostics)
1358+
}

analysis/diagnostic_kind.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,13 @@ func (e *EmptiedAccount) Message() string {
247247
func (*EmptiedAccount) Severity() Severity {
248248
return WarningSeverity
249249
}
250+
251+
type UnboundedAccountIsNotLast struct{}
252+
253+
func (e *UnboundedAccountIsNotLast) Message() string {
254+
return "Inorder sources after an unbounded overdraft are never reached"
255+
}
256+
257+
func (*UnboundedAccountIsNotLast) Severity() Severity {
258+
return WarningSeverity
259+
}

parser/ast.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ type RemainingAllotment struct {
9999
Range Range
100100
}
101101

102+
func (a *AccountLiteral) IsWorld() bool {
103+
return a.Name == "world"
104+
}
105+
102106
// Source exprs
103107

104108
type Source interface {

0 commit comments

Comments
 (0)