Skip to content

Commit

Permalink
fix: go compile errors refered to unknown refs without pos (#4728)
Browse files Browse the repository at this point in the history
```
0:0: unknown reference "publisher.slowTopic", is the type annotated and exported?
```
Use `schema.Ref`s in directive parsing to capture Pos within directive,
and then map it to the file position
  • Loading branch information
matt2e authored Feb 28, 2025
1 parent 543c1f6 commit f83596f
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 39 deletions.
4 changes: 2 additions & 2 deletions common/schema/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ var (
}

// Parser options for every parser _except_ the type parser.
parserOptions = append(commonParserOptions, participle.ParseTypeWith(parseType))
parserOptions = append(commonParserOptions, participle.ParseTypeWith(ParseTypeWithLexer))

parser = participle.MustBuild[Schema](parserOptions...)
moduleParser = participle.MustBuild[Module](parserOptions...)
Expand Down Expand Up @@ -175,7 +175,7 @@ func ParseType(filename, input string) (Type, error) {
return typ.Type, nil
}

func parseType(pl *lexer.PeekingLexer) (Type, error) {
func ParseTypeWithLexer(pl *lexer.PeekingLexer) (Type, error) {
typ, err := typeParser.ParseFromLexer(pl, participle.AllowTrailing(true))
if err != nil {
return nil, err
Expand Down
43 changes: 11 additions & 32 deletions go-runtime/schema/common/directive.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"strings"

"github.com/alecthomas/participle/v2"
"github.com/alecthomas/types/optional"

"github.com/block/ftl-golang-tools/go/analysis"
"github.com/block/ftl/common/cron"
Expand Down Expand Up @@ -204,11 +203,10 @@ func (*DirectiveCronJob) MustAnnotate() []ast.Node {
type DirectiveRetry struct {
Pos token.Pos

Count *int `parser:"'retry' (@Number Whitespace)?"`
MinBackoff string `parser:"@(Number (?! Whitespace) Ident)?"`
MaxBackoff string `parser:"@(Number (?! Whitespace) Ident)?"`
CatchModule *string `parser:"('catch' (@Ident '.')?"`
CatchVerb *string `parser:"@Ident)?"`
Count *int `parser:"'retry' (@Number Whitespace)?"`
MinBackoff string `parser:"@(Number (?! Whitespace) Ident)?"`
MaxBackoff string `parser:"@(Number (?! Whitespace) Ident)?"`
Catch *schema.Ref `parser:"('catch' @@)?"`
}

func (*DirectiveRetry) directive() {}
Expand All @@ -222,8 +220,8 @@ func (d *DirectiveRetry) String() string {
if len(d.MaxBackoff) > 0 {
components = append(components, d.MaxBackoff)
}
if catch, ok := d.Catch().Get(); ok {
components = append(components, fmt.Sprintf("catch %v", catch))
if d.Catch != nil {
components = append(components, fmt.Sprintf("catch %v", d.Catch))
}
return strings.Join(components, " ")
}
Expand All @@ -238,20 +236,6 @@ func (*DirectiveRetry) MustAnnotate() []ast.Node {
return []ast.Node{&ast.FuncDecl{}, &ast.GenDecl{}}
}

func (d *DirectiveRetry) Catch() optional.Option[schema.Ref] {
if d.CatchVerb == nil {
return optional.None[schema.Ref]()
}
var module string
if d.CatchModule != nil {
module = *d.CatchModule
}
return optional.Some[schema.Ref](schema.Ref{
Module: module,
Name: *d.CatchVerb,
})
}

// DirectiveTopic is used to configure options for a topic.
type DirectiveTopic struct {
Pos token.Pos
Expand Down Expand Up @@ -290,21 +274,15 @@ func (d *DirectiveTopic) IsExported() bool {
type DirectiveSubscriber struct {
Pos token.Pos

TopicModule string `parser:"'subscribe' (@Ident '.')?"`
TopicName string `parser:"@Ident"`
FromOffset *schema.FromOffset `parser:"'from' '='@('beginning'|'latest')"`
DeadLetter bool `parser:"@'deadletter'?"`
Topic *schema.Ref `parser:"'subscribe' @@"`
FromOffset *schema.FromOffset `parser:"'from' '='@('beginning'|'latest')"`
DeadLetter bool `parser:"@'deadletter'?"`
}

func (*DirectiveSubscriber) directive() {}

func (d *DirectiveSubscriber) String() string {
components := []string{"subscribe"}
if d.TopicModule != "" {
components = append(components, d.TopicModule+"."+d.TopicName)
} else {
components = append(components, d.TopicName)
}
components := []string{"subscribe", d.Topic.String()}
components = append(components, "from="+d.FromOffset.String())
if d.DeadLetter {
components = append(components, "deadletter")
Expand Down Expand Up @@ -428,6 +406,7 @@ var DirectiveParser = participle.MustBuild[directiveWrapper](
&DirectiveIngress{}, &DirectiveCronJob{}, &DirectiveRetry{}, &DirectiveSubscriber{}, &DirectiveExport{},
&DirectiveTypeMap{}, &DirectiveEncoding{}, &DirectiveTopic{}, &DirectiveDatabase{}),
participle.Union[schema.IngressPathComponent](&schema.IngressPathLiteral{}, &schema.IngressPathParameter{}),
participle.ParseTypeWith(schema.ParseTypeWithLexer),
)

func ParseDirectives(pass *analysis.Pass, node ast.Node, docs *ast.CommentGroup) []Directive {
Expand Down
28 changes: 23 additions & 5 deletions go-runtime/schema/metadata/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/block/ftl-golang-tools/go/analysis"
"github.com/block/ftl-golang-tools/go/analysis/passes/inspect"
"github.com/block/ftl-golang-tools/go/ast/inspector"
ireflect "github.com/block/ftl/common/reflect"
"github.com/block/ftl/common/schema"
"github.com/block/ftl/go-runtime/schema/common"
)
Expand Down Expand Up @@ -115,12 +116,21 @@ func extractMetadata(pass *analysis.Pass, node ast.Node, doc *ast.CommentGroup,
Cron: dt.Cron.String(),
})
case *common.DirectiveRetry:
pos := common.GoPosToSchemaPos(pass.Fset, dt.Pos)
var catch *schema.Ref
if dt.Catch != nil {
catch = &schema.Ref{
Module: dt.Catch.Module,
Name: dt.Catch.Name,
Pos: posFromPosWithinDirective(dt.Catch.Pos, pos),
}
}
metadata = append(metadata, &schema.MetadataRetry{
Pos: common.GoPosToSchemaPos(pass.Fset, dt.Pos),
Pos: pos,
Count: dt.Count,
MinBackoff: dt.MinBackoff,
MaxBackoff: dt.MaxBackoff,
Catch: dt.Catch().Ptr(),
Catch: catch,
})
case *common.DirectiveTopic:
newSchType = &schema.Topic{}
Expand All @@ -130,11 +140,13 @@ func extractMetadata(pass *analysis.Pass, node ast.Node, doc *ast.CommentGroup,
})
case *common.DirectiveSubscriber:
newSchType = &schema.Verb{}
pos := common.GoPosToSchemaPos(pass.Fset, dt.Pos)
metadata = append(metadata, &schema.MetadataSubscriber{
Pos: common.GoPosToSchemaPos(pass.Fset, dt.Pos),
Pos: pos,
Topic: &schema.Ref{
Module: dt.TopicModule,
Name: dt.TopicName,
Module: dt.Topic.Module,
Name: dt.Topic.Name,
Pos: posFromPosWithinDirective(dt.Topic.Pos, pos),
},
FromOffset: *dt.FromOffset,
DeadLetter: dt.DeadLetter,
Expand Down Expand Up @@ -178,6 +190,12 @@ func extractMetadata(pass *analysis.Pass, node ast.Node, doc *ast.CommentGroup,
return optional.Some(md)
}

func posFromPosWithinDirective(pos schema.Position, parentPos schema.Position) schema.Position {
out := ireflect.DeepCopy(parentPos)
out.Column += pos.Column - 1
return out
}

func validateMetadata(pass *analysis.Pass, node ast.Node, extracted *common.ExtractedMetadata) {
if _, ok := extracted.Type.(*schema.Verb); !ok {
return
Expand Down

0 comments on commit f83596f

Please sign in to comment.