Skip to content

Commit 13e4e17

Browse files
authored
Implement a "create variable" code action (#40)
1 parent 5e4c19c commit 13e4e17

14 files changed

+858
-1725
lines changed

internal/analysis/check.go

+58-118
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package analysis
22

33
import (
44
"math/big"
5+
"math/rand"
56
"slices"
67
"strings"
78

@@ -87,6 +88,7 @@ var Builtins = map[string]FnCallResolution{
8788
type Diagnostic struct {
8889
Range parser.Range
8990
Kind DiagnosticKind
91+
Id int32
9092
}
9193

9294
type CheckResult struct {
@@ -149,30 +151,30 @@ func newCheckResult(program parser.Program) CheckResult {
149151
}
150152

151153
func (res *CheckResult) check() {
152-
for _, varDecl := range res.Program.Vars {
153-
if varDecl.Type != nil {
154-
res.checkVarType(*varDecl.Type)
155-
}
154+
if res.Program.Vars != nil {
155+
for _, varDecl := range res.Program.Vars.Declarations {
156+
if varDecl.Type != nil {
157+
res.checkVarType(*varDecl.Type)
158+
}
156159

157-
if varDecl.Name != nil {
158-
res.checkDuplicateVars(*varDecl.Name, varDecl)
159-
}
160+
if varDecl.Name != nil {
161+
res.checkDuplicateVars(*varDecl.Name, varDecl)
162+
}
160163

161-
if varDecl.Origin != nil {
162-
res.checkVarOrigin(*varDecl.Origin, varDecl)
164+
if varDecl.Origin != nil {
165+
res.checkVarOrigin(*varDecl.Origin, varDecl)
166+
}
163167
}
164168
}
169+
165170
for _, statement := range res.Program.Statements {
166171
res.unboundedAccountInSend = nil
167172
res.checkStatement(statement)
168173
}
169174

170175
// after static AST traversal is complete, check for unused vars
171176
for name, rng := range res.unusedVars {
172-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
173-
Range: rng,
174-
Kind: &UnusedVar{Name: name},
175-
})
177+
res.pushDiagnostic(rng, UnusedVar{Name: name})
176178
}
177179
}
178180

@@ -214,19 +216,12 @@ func CheckSource(source string) CheckResult {
214216
result := parser.Parse(source)
215217
res := newCheckResult(result.Value)
216218
for _, parserError := range result.Errors {
217-
res.Diagnostics = append(res.Diagnostics, parsingErrorToDiagnostic(parserError))
219+
res.pushDiagnostic(parserError.Range, Parsing{Description: parserError.Msg})
218220
}
219221
res.check()
220222
return res
221223
}
222224

223-
func parsingErrorToDiagnostic(parserError parser.ParserError) Diagnostic {
224-
return Diagnostic{
225-
Range: parserError.Range,
226-
Kind: &Parsing{Description: parserError.Msg},
227-
}
228-
}
229-
230225
func (res *CheckResult) checkFnCallArity(fnCall *parser.FnCall) {
231226
resolution, resolved := res.fnCallResolution[fnCall.Caller]
232227

@@ -244,12 +239,9 @@ func (res *CheckResult) checkFnCallArity(fnCall *parser.FnCall) {
244239

245240
if actualArgs < expectedArgs {
246241
// Too few args
247-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
248-
Range: fnCall.Range,
249-
Kind: &BadArity{
250-
Expected: expectedArgs,
251-
Actual: actualArgs,
252-
},
242+
res.pushDiagnostic(fnCall.Range, BadArity{
243+
Expected: expectedArgs,
244+
Actual: actualArgs,
253245
})
254246
} else if actualArgs > expectedArgs {
255247
// Too many args
@@ -262,12 +254,9 @@ func (res *CheckResult) checkFnCallArity(fnCall *parser.FnCall) {
262254
End: lastIllegalArg.GetRange().End,
263255
}
264256

265-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
266-
Range: rng,
267-
Kind: &BadArity{
268-
Expected: expectedArgs,
269-
Actual: actualArgs,
270-
},
257+
res.pushDiagnostic(rng, BadArity{
258+
Expected: expectedArgs,
259+
Actual: actualArgs,
271260
})
272261
}
273262

@@ -287,11 +276,8 @@ func (res *CheckResult) checkFnCallArity(fnCall *parser.FnCall) {
287276
res.checkExpression(arg, TypeAny)
288277
}
289278

290-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
291-
Range: fnCall.Caller.Range,
292-
Kind: &UnknownFunction{
293-
Name: fnCall.Caller.Name,
294-
},
279+
res.pushDiagnostic(fnCall.Caller.Range, UnknownFunction{
280+
Name: fnCall.Caller.Name,
295281
})
296282
}
297283
}
@@ -302,20 +288,14 @@ func isTypeAllowed(typeName string) bool {
302288

303289
func (res *CheckResult) checkVarType(typeDecl parser.TypeDecl) {
304290
if !isTypeAllowed(typeDecl.Name) {
305-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
306-
Range: typeDecl.Range,
307-
Kind: &InvalidType{Name: typeDecl.Name},
308-
})
291+
res.pushDiagnostic(typeDecl.Range, InvalidType{Name: typeDecl.Name})
309292
}
310293
}
311294

312295
func (res *CheckResult) checkDuplicateVars(variableName parser.Variable, decl parser.VarDeclaration) {
313296
// check there aren't duplicate variables
314297
if _, ok := res.declaredVars[variableName.Name]; ok {
315-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
316-
Range: variableName.Range,
317-
Kind: &DuplicateVariable{Name: variableName.Name},
318-
})
298+
res.pushDiagnostic(variableName.Range, DuplicateVariable{Name: variableName.Name})
319299
} else {
320300
res.declaredVars[variableName.Name] = decl
321301
res.unusedVars[variableName.Name] = variableName.Range
@@ -337,20 +317,17 @@ func (res *CheckResult) checkVarOrigin(fnCall parser.FnCall, decl parser.VarDecl
337317
}
338318

339319
func (res *CheckResult) checkExpression(lit parser.ValueExpr, requiredType string) {
340-
actualType := res.checkTypeOf(lit)
320+
actualType := res.checkTypeOf(lit, requiredType)
341321
res.assertHasType(lit, requiredType, actualType)
342322
}
343323

344-
func (res *CheckResult) checkTypeOf(lit parser.ValueExpr) string {
324+
func (res *CheckResult) checkTypeOf(lit parser.ValueExpr, typeHint string) string {
345325
switch lit := lit.(type) {
346326
case *parser.Variable:
347327
if varDeclaration, ok := res.declaredVars[lit.Name]; ok {
348328
res.varResolution[lit] = varDeclaration
349329
} else {
350-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
351-
Range: lit.Range,
352-
Kind: &UnboundVariable{Name: lit.Name},
353-
})
330+
res.pushDiagnostic(lit.Range, UnboundVariable{Name: lit.Name, Type: typeHint})
354331
}
355332
delete(res.unusedVars, lit.Name)
356333

@@ -408,19 +385,16 @@ func (res *CheckResult) checkTypeOf(lit parser.ValueExpr) string {
408385
}
409386

410387
func (res *CheckResult) checkInfixOverload(bin *parser.BinaryInfix, allowed []string) string {
411-
leftType := res.checkTypeOf(bin.Left)
388+
leftType := res.checkTypeOf(bin.Left, allowed[0])
412389

413390
if leftType == TypeAny || slices.Contains(allowed, leftType) {
414391
res.checkExpression(bin.Right, leftType)
415392
return leftType
416393
}
417394

418-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
419-
Range: bin.Left.GetRange(),
420-
Kind: &TypeMismatch{
421-
Expected: strings.Join(allowed, "|"),
422-
Got: leftType,
423-
},
395+
res.pushDiagnostic(bin.Left.GetRange(), TypeMismatch{
396+
Expected: strings.Join(allowed, "|"),
397+
Got: leftType,
424398
})
425399
return TypeAny
426400
}
@@ -430,14 +404,10 @@ func (res *CheckResult) assertHasType(lit parser.ValueExpr, requiredType string,
430404
return
431405
}
432406

433-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
434-
Range: lit.GetRange(),
435-
Kind: &TypeMismatch{
436-
Expected: requiredType,
437-
Got: actualType,
438-
},
407+
res.pushDiagnostic(lit.GetRange(), TypeMismatch{
408+
Expected: requiredType,
409+
Got: actualType,
439410
})
440-
441411
}
442412

443413
func (res *CheckResult) checkSentValue(sentValue parser.SentValue) {
@@ -455,30 +425,21 @@ func (res *CheckResult) checkSource(source parser.Source) {
455425
}
456426

457427
if res.unboundedAccountInSend != nil {
458-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
459-
Range: source.GetRange(),
460-
Kind: &UnboundedAccountIsNotLast{},
461-
})
428+
res.pushDiagnostic(source.GetRange(), UnboundedAccountIsNotLast{})
462429
}
463430

464431
switch source := source.(type) {
465432
case *parser.SourceAccount:
466433
res.checkExpression(source.ValueExpr, TypeAccount)
467434
if account, ok := source.ValueExpr.(*parser.AccountInterpLiteral); ok {
468435
if account.IsWorld() && res.unboundedSend {
469-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
470-
Range: source.GetRange(),
471-
Kind: &InvalidUnboundedAccount{},
472-
})
436+
res.pushDiagnostic(source.GetRange(), InvalidUnboundedAccount{})
473437
} else if account.IsWorld() {
474438
res.unboundedAccountInSend = account
475439
}
476440

477441
if _, emptied := res.emptiedAccount[account.String()]; emptied && !account.IsWorld() {
478-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
479-
Kind: &EmptiedAccount{Name: account.String()},
480-
Range: account.Range,
481-
})
442+
res.pushDiagnostic(account.Range, EmptiedAccount{Name: account.String()})
482443
}
483444

484445
res.emptiedAccount[account.String()] = struct{}{}
@@ -497,10 +458,7 @@ func (res *CheckResult) checkSource(source parser.Source) {
497458
}
498459

499460
if res.unboundedSend {
500-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
501-
Range: source.Address.GetRange(),
502-
Kind: &InvalidUnboundedAccount{},
503-
})
461+
res.pushDiagnostic(source.Address.GetRange(), InvalidUnboundedAccount{})
504462
}
505463

506464
res.checkExpression(source.Address, TypeAccount)
@@ -528,10 +486,7 @@ func (res *CheckResult) checkSource(source parser.Source) {
528486

529487
case *parser.SourceAllotment:
530488
if res.unboundedSend {
531-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
532-
Kind: &NoAllotmentInSendAll{},
533-
Range: source.Range,
534-
})
489+
res.pushDiagnostic(source.Range, NoAllotmentInSendAll{})
535490
}
536491

537492
var remainingAllotment *parser.RemainingAllotment = nil
@@ -555,10 +510,7 @@ func (res *CheckResult) checkSource(source parser.Source) {
555510
if isLast {
556511
remainingAllotment = allotment
557512
} else {
558-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
559-
Range: source.Range,
560-
Kind: &RemainingIsNotLast{},
561-
})
513+
res.pushDiagnostic(source.Range, RemainingIsNotLast{})
562514
}
563515
}
564516

@@ -637,10 +589,7 @@ func (res *CheckResult) tryEvaluatingPortionExpr(expr parser.ValueExpr) *big.Rat
637589
}
638590

639591
if right.Cmp(big.NewInt(0)) == 0 {
640-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
641-
Kind: &DivByZero{},
642-
Range: expr.Range,
643-
})
592+
res.pushDiagnostic(expr.Range, DivByZero{})
644593
return nil
645594
}
646595

@@ -708,10 +657,7 @@ func (res *CheckResult) checkDestination(destination parser.Destination) {
708657
if isLast {
709658
remainingAllotment = allotment
710659
} else {
711-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
712-
Range: destination.Range,
713-
Kind: &RemainingIsNotLast{},
714-
})
660+
res.pushDiagnostic(destination.Range, RemainingIsNotLast{})
715661
}
716662
}
717663

@@ -746,36 +692,22 @@ func (res *CheckResult) checkHasBadAllotmentSum(
746692

747693
if cmp == -1 && len(variableLiterals) == 1 {
748694
var value big.Rat
749-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
750-
Range: variableLiterals[0].GetRange(),
751-
Kind: &FixedPortionVariable{
752-
Value: *value.Sub(big.NewRat(1, 1), &sum),
753-
},
695+
res.pushDiagnostic(variableLiterals[0].GetRange(), FixedPortionVariable{
696+
Value: *value.Sub(big.NewRat(1, 1), &sum),
754697
})
755698
} else {
756-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
757-
Range: rng,
758-
Kind: &BadAllotmentSum{
759-
Sum: sum,
760-
},
761-
})
699+
res.pushDiagnostic(rng, BadAllotmentSum{Sum: sum})
762700
}
763701

764702
// sum == 1
765703
case 0:
766704
for _, varLit := range variableLiterals {
767-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
768-
Range: varLit.GetRange(),
769-
Kind: &FixedPortionVariable{
770-
Value: *big.NewRat(0, 1),
771-
},
705+
res.pushDiagnostic(varLit.GetRange(), FixedPortionVariable{
706+
Value: *big.NewRat(0, 1),
772707
})
773708
}
774709
if remaining != nil {
775-
res.Diagnostics = append(res.Diagnostics, Diagnostic{
776-
Range: remaining.Range,
777-
Kind: &RedundantRemaining{},
778-
})
710+
res.pushDiagnostic(remaining.Range, RedundantRemaining{})
779711
}
780712
}
781713
}
@@ -820,3 +752,11 @@ func (res *CheckResult) enterCappedSource() func() {
820752
exitCloneUnboundedSend()
821753
}
822754
}
755+
756+
func (res *CheckResult) pushDiagnostic(rng parser.Range, kind DiagnosticKind) {
757+
res.Diagnostics = append(res.Diagnostics, Diagnostic{
758+
Range: rng,
759+
Kind: kind,
760+
Id: rand.Int31(),
761+
})
762+
}

0 commit comments

Comments
 (0)