-
Notifications
You must be signed in to change notification settings - Fork 9
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
chore: migrate verb schema parsing #1711
Conversation
3333831
to
4a06dff
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome!!! Thanks so much for doing this
"golang.org/x/exp/maps" | ||
) | ||
|
||
var ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we make these const
now?
|
||
// Initializer prepares data prior to the schema extractor runs, e.g. loads FTL types for reference by other | ||
// analyzers. | ||
var Initializer = &analysis.Analyzer{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of this is super nice :D
ftlTypes map[string]*types.Interface | ||
} | ||
|
||
func (r initializeResult) isFtlErrorType(typ types.Type) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nittiest of nits: isFTLErrorType
for go style
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also not sure what these functions mean, can you add some comments? What is an "ftl error type", for example?
return r.assertableToFtlType(typ, "builtin", "error") | ||
} | ||
|
||
func (r initializeResult) isFtlContextType(typ types.Type) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this just be isContextType
since we don't have our own special context
type and just use the standard Go one?
return nil, err | ||
} | ||
if len(pkgs) != 1 { | ||
return nil, errors.New("expected one package") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we make this expected one package, got %d
? Since that usually saves some time debugging whenever an error like that does appear
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 and maybe even do something like strings.Join(slices.Map(pkgs, func(p *packages.Package) (string, error) { return p.Name }), ", ")
go-runtime/schema/analyzers/verb.go
Outdated
var VerbExtractor = newExtractor("verb", extractVerbs, (*verbExtractorFact)(nil)) | ||
|
||
type verbExtractorFact struct { | ||
decl schema.Decl |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would this always be a schema.Verb
? Or do we need to leave this as a schema.Decl
to make the interface happy?
go-runtime/schema/analyzers/verb.go
Outdated
if results.Len() == 0 { | ||
pass.Report(errorf(node, "must at least return an error")) | ||
} else if !loaded.isFtlErrorType(results.At(results.Len() - 1).Type()) { | ||
pass.Report(tokenErrorf(pass.Fset, results.At(results.Len()-1).Pos(), results.At(results.Len()-1).Name(), "must return an error but is %s", results.At(0).Type())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, when returning multiple values, should we specify in the error message that last value in the return statement must be error
, in case they have something like func(context.Context) (error, struct)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's what this is already doing isn't it? results.At(results.Len()-1)
is the last out value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I just meant clarifying the error message. The logic itself looks good yeah
go-runtime/schema/extractor.go
Outdated
@@ -21,7 +21,8 @@ func Extract(moduleDir string) (analyzers.ExtractResult, error) { | |||
RunDespiteLoadErrors: true, | |||
Patterns: []string{"./..."}, | |||
} | |||
results, diagnostics, err := checker.Run(cConfig, append(analyzers.Extractors, analyzers.Finalizer)...) | |||
as := []*analysis.Analyzer{analyzers.Initializer, analyzers.Finalizer} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related to this line, just a general comment: did you happen to check if we need to add any more test coverage? I can't recall if we're missing any significant branch cases for the new approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So good!
ResultType: reflect.TypeFor[ExtractResult](), | ||
RunDespiteErrors: true, | ||
} | ||
|
||
// ExtractResult contains the final schema extraction result. | ||
// ExtractResult contains the final schema extraction extractorResult. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: overly enthusiastic type rename
ftlTypes map[string]*types.Interface | ||
} | ||
|
||
func (r initializeResult) isFtlErrorType(typ types.Type) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also not sure what these functions mean, can you add some comments? What is an "ftl error type", for example?
return nil, err | ||
} | ||
if len(pkgs) != 1 { | ||
return nil, errors.New("expected one package") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 and maybe even do something like strings.Join(slices.Map(pkgs, func(p *packages.Package) (string, error) { return p.Name }), ", ")
} | ||
obj := pkgs[0].Types.Scope().Lookup(name) | ||
if obj == nil { | ||
return nil, errors.New("interface not found") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return nil, errors.New("interface not found") | |
return nil, errors.Errorf("interface %q not found", name) |
} | ||
ifaceType, ok := obj.Type().Underlying().(*types.Interface) | ||
if !ok { | ||
return nil, errors.New("expected an interface, got " + obj.Type().String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return nil, errors.New("expected an interface, got " + obj.Type().String()) | |
return nil, errors.Errorf("expected an interface, got %s" + obj.Type()) |
go-runtime/schema/analyzers/verb.go
Outdated
nodeFilter := []ast.Node{ | ||
(*ast.FuncDecl)(nil), | ||
} | ||
in.Preorder(nodeFilter, func(n ast.Node) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh in.Preorder()
is super handy, nice nice. We can delete my shitty astwalk
function at some point.
go-runtime/schema/analyzers/verb.go
Outdated
reqV, reqOk := req.Get() | ||
resV, respOk := resp.Get() | ||
if !reqOk { | ||
pass.Report(tokenErrorf(pass.Fset, params.At(1).Pos(), params.At(1).Name(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noice.
go-runtime/schema/analyzers/verb.go
Outdated
return newExtractorResult(pass, verbs, nn), nil | ||
} | ||
|
||
func extractVerbMetadata(pass *analysis.Pass, node ast.Node, directives []directive) []schema.Metadata { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Metadata extraction could be another pass. We could look for any //ftl:
directives on any declaration, and add them as facts to the node. Then in verb analysis, pubsub, types, etc. we just pull the metadata fact directly from the node.
go-runtime/schema/analyzers/verb.go
Outdated
if results.Len() == 0 { | ||
pass.Report(errorf(node, "must at least return an error")) | ||
} else if !loaded.isFtlErrorType(results.At(results.Len() - 1).Type()) { | ||
pass.Report(tokenErrorf(pass.Fset, results.At(results.Len()-1).Pos(), results.At(results.Len()-1).Name(), "must return an error but is %s", results.At(0).Type())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's what this is already doing isn't it? results.At(results.Len()-1)
is the last out value.
go-runtime/schema/analyzers/verb.go
Outdated
} | ||
|
||
if results.Len() > 2 { | ||
pass.Report(errorf(node, "must have at most two results (struct, error)")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pass.Report(errorf(node, "must have at most two results (struct, error)")) | |
pass.Report(errorf(node, "must have at most two results (<type>, error)")) |
b084e25
to
07db4f9
Compare
restructuring and adding some additional passes to provide data for the extractors- 1. metadata pass parses all comments in the packages and provides results via facts 2. transitive pass extracts nodes marked as needing extraction via transitive reference from other decls
07db4f9
to
e0e5a12
Compare
ok, completely overhauled this change 😅 the scope creep required to get something working was kind of insane. it's quite different now but all comments should be addressed! this pr has gotten kind of out of hand but the next ones should be muuuuuch better contained for migrating future pieces. |
restructuring and adding some additional passes to provide data for the extractors-
#1518