diff --git a/compiler/compiler.go b/compiler/compiler.go index 625087561..bcd65adb0 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -578,6 +578,7 @@ func generateTables(source *syntax.Model, out *grammar.Grammar, opts genOptions, Report: report, Code: command, Origin: cmdOrigin, + NtName: nt.Name, } if args != nil { act.Vars = &grammar.ActionVars{CmdArgs: *args, Remap: actualPos} @@ -723,6 +724,7 @@ func (e *commandExtractor) extract(n *syntax.Nonterm, command string, vars *gram Code: command, Vars: vars, Origin: cmdOrigin, + NtName: nt.Name, } rule.Action = len(e.actions) e.actions = append(e.actions, act) diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index b059367dd..aa2c63866 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -39,6 +39,7 @@ var testFiles = []string{ "flexmode.tmerr", "max_la.tmerr", "disabled_syntax.tmerr", + "expansion_limit.tmerr", } func TestErrors(t *testing.T) { diff --git a/compiler/options.go b/compiler/options.go index 5e0f5f66e..dbf298186 100644 --- a/compiler/options.go +++ b/compiler/options.go @@ -20,11 +20,15 @@ type optionsParser struct { func newOptionsParser(s *status.Status) *optionsParser { return &optionsParser{ out: &grammar.Options{ - TokenLine: true, - GenParser: true, - AbslIncludePrefix: "absl", - SkipByteOrderMark: true, - OptInstantiationSuffix: "opt", + TokenLine: true, + GenParser: true, + AbslIncludePrefix: "absl", + SkipByteOrderMark: true, + OptInstantiationSuffix: "opt", + ExpansionLimit: 65_536, + ExpansionWarn: 256, + MaxRuleSizeForOrdinalRef: 16, + VariantStackEntry: true, }, Status: s, } @@ -105,6 +109,10 @@ func (p *optionsParser) parseFrom(file ast.File) { opts.MaxLookahead = p.parseExpr(opt.Value(), opts.MaxLookahead).(int) case "disableSyntax": opts.DisableSyntax = p.parseExpr(opt.Value(), opts.DisableSyntax).([]string) + case "expansionLimit": + opts.ExpansionLimit = p.parseExpr(opt.Value(), opts.ExpansionLimit).(int) + case "expansionWarn": + opts.ExpansionWarn = p.parseExpr(opt.Value(), opts.ExpansionWarn).(int) case "eventFields": p.validLangs(opt.Key(), "go") opts.EventFields = p.parseExpr(opt.Value(), opts.EventFields).(bool) @@ -143,6 +151,11 @@ func (p *optionsParser) parseFrom(file ast.File) { case "variantStackEntry": p.validLangs(opt.Key(), "cc") opts.VariantStackEntry = p.parseExpr(opt.Value(), opts.VariantStackEntry).(bool) + case "trackReduces": + p.validLangs(opt.Key(), "cc") + opts.TrackReduces = p.parseExpr(opt.Value(), opts.TrackReduces).(bool) + case "maxRuleSizeForOrdinalRef": + opts.MaxRuleSizeForOrdinalRef = p.parseExpr(opt.Value(), opts.MaxRuleSizeForOrdinalRef).(int) case "skipByteOrderMark": opts.SkipByteOrderMark = p.parseExpr(opt.Value(), opts.SkipByteOrderMark).(bool) default: diff --git a/compiler/syntax.go b/compiler/syntax.go index 49bad86a6..216a0e216 100644 --- a/compiler/syntax.go +++ b/compiler/syntax.go @@ -47,8 +47,10 @@ func newSyntaxLoader(resolver *resolver, targetLang string, opts *grammar.Option case "cc": expandOpts = syntax.CcExpandOptions() default: - expandOpts = &syntax.ExpandOptions{} + expandOpts = syntax.DefaultExpandOptions() } + expandOpts.ExpansionLimit = opts.ExpansionLimit + expandOpts.ExpansionWarn = opts.ExpansionWarn return &syntaxLoader{ resolver: resolver, diff --git a/compiler/testdata/expansion_limit.tmerr b/compiler/testdata/expansion_limit.tmerr new file mode 100644 index 000000000..2e249e1ab --- /dev/null +++ b/compiler/testdata/expansion_limit.tmerr @@ -0,0 +1,36 @@ +language parser(go); + +expansionWarn = 2 +expansionLimit = 8 + +:: lexer + +a: /a/ +b: /b/ +c: /c/ +d: /d/ +e: /e/ +f: /f/ +g: /g/ + +:: parser + +input: a b c d; + +A1: a? b c d; + +A2: a? b? c d; + +A3: a? b? c? d; + +A4: «a? b? c? d?»; +# err: expanding rule produced 16 rules which exeeds the limit of 8. Refactor the rule to reduce expansion or increase the `expansionLimit` value + +A5: «a? b? c? d? e?»; +# err: expanding rule produced 32 rules which exeeds the limit of 8. Refactor the rule to reduce expansion or increase the `expansionLimit` value + +A6: b | «a b? c? d? e?»; +# err: expanding rule produced 16 rules which exeeds the limit of 8. Refactor the rule to reduce expansion or increase the `expansionLimit` value + +A7: «a ( b c? (d | e f? | f | g) | c )»; +# err: expanding rule produced 11 rules which exeeds the limit of 8. Refactor the rule to reduce expansion or increase the `expansionLimit` value \ No newline at end of file diff --git a/gen/funcs.go b/gen/funcs.go index 9b4a52140..ca5e0d291 100644 --- a/gen/funcs.go +++ b/gen/funcs.go @@ -39,6 +39,7 @@ var funcMap = template.FuncMap{ "sub": sub, "go_parser_action": goParserAction, "cc_parser_action": ccParserAction, + "cc_action_func": ccActionFunc, "bison_parser_action": bisonParserAction, "short_pkg": shortPkg, "list": func(vals ...string) []string { return vals }, @@ -285,9 +286,9 @@ func goParserAction(s string, args *grammar.ActionVars, origin status.SourceNode } } - ref, ok := args.Resolve(id) - if !ok { - return "", status.Errorf(origin, "invalid reference %q", id) + ref, err := args.Resolve(id, origin) + if err != nil { + return "", err } index = ref.Index pos = ref.Pos @@ -355,7 +356,7 @@ func ccTypeFromUnion(unionField string) string { return strings.TrimSpace(unionField[:len(unionField)-len(lastID(unionField))]) } -func ccParserAction(s string, args *grammar.ActionVars, origin status.SourceNode, variantStackEntry bool) (ret string, err error) { +func ccParserAction(s string, args *grammar.ActionVars, origin status.SourceNode, opts *grammar.Options) (ret string, err error) { defer func(s string) { if r := recover(); r != nil { err = fmt.Errorf("crashed with %v in %q with %v", err, s, args) @@ -363,6 +364,7 @@ func ccParserAction(s string, args *grammar.ActionVars, origin status.SourceNode }(s) var sb strings.Builder + sb.WriteString("#line " + strconv.Itoa(origin.SourceRange().Line) + " \"" + origin.SourceRange().Filename + "\"\n") for len(s) > 0 { next := strings.IndexAny(s, "@$") if next == -1 { @@ -389,7 +391,7 @@ func ccParserAction(s string, args *grammar.ActionVars, origin status.SourceNode if t == "" { return "", status.Errorf(origin, "$$ cannot be used inside a nonterminal semantic action without a type") } - if variantStackEntry { + if opts.VariantStackEntry { replacement = "std::get<" + t + ">(lhs.value)" } else { replacement = "lhs.value." + lastID(t) @@ -414,9 +416,9 @@ func ccParserAction(s string, args *grammar.ActionVars, origin status.SourceNode s = s[d:] // cc uses 1-based indexing. - ref, ok := args.ResolveOneBased(val) - if !ok { - return "", status.Errorf(origin, "invalid reference %c%q", ch, val) + ref, err := args.ResolveOneBased(val, opts.MaxRuleSizeForOrdinalRef, string(ch)+val, origin) + if err != nil { + return "", err } index := ref.Index @@ -436,7 +438,7 @@ func ccParserAction(s string, args *grammar.ActionVars, origin status.SourceNode if argType == "" { return "", status.Errorf(origin, "symbol %c%q does not have an associated type", ch, val) } - if !variantStackEntry { + if !opts.VariantStackEntry { argType = ccTypeFromUnion(argType) } sb.WriteString(ccWrapInOptional(argType, "")) @@ -453,7 +455,7 @@ func ccParserAction(s string, args *grammar.ActionVars, origin status.SourceNode if argType == "" { return "", status.Errorf(origin, "%c%q does not have an associated type", ch, val) } - if variantStackEntry { + if opts.VariantStackEntry { replacement = "std::get<" + argType + ">(" + target + ".value)" } else { replacement = target + ".value." + lastID(argType) @@ -471,6 +473,16 @@ func ccParserAction(s string, args *grammar.ActionVars, origin status.SourceNode return sb.String(), nil } +// Generate a C++ function name for a reduce action that encodes information useful for +// understanding stack traces including the nonterminal name and line number. We also include the +// rule index because there can be multiple actions on the same line in the grammar file. +func ccActionFunc(idx int, act *grammar.SemanticAction) string { + // Include the index in the name to differentiate between actions of extracted non-terminals and + // also actions of named non-terminals that happen to be on the same line of the grammar file. + return fmt.Sprintf("Action%v__ReduceOf_%v__AtLine_%v_Column_%v", idx, act.NtName, act.Origin.SourceRange().Line, act.Origin.SourceRange().Column) +} + + func bisonParserAction(s string, args *grammar.ActionVars, origin status.SourceNode) (string, error) { var sb strings.Builder for len(s) > 0 { @@ -514,9 +526,9 @@ func bisonParserAction(s string, args *grammar.ActionVars, origin status.SourceN } } - ref, ok := args.Resolve(id) - if !ok { - return "", status.Errorf(origin, "invalid reference %q", id) + ref, err := args.Resolve(id, origin) + if err != nil { + return "", err } index = ref.Index pos = ref.Pos diff --git a/gen/funcs_test.go b/gen/funcs_test.go index e4457a0c0..3871f173d 100644 --- a/gen/funcs_test.go +++ b/gen/funcs_test.go @@ -195,15 +195,19 @@ func TestCcParserAction(t *testing.T) { want string useVariant bool }{ - {"abc", vars(), "abc", false}, + {"abc", vars(), "#line 0 \"abc\"\nabc", false}, // The 1-based index for "a" is 2. - {"$$ = $2", vars("%node", "a:0:expr"), "lhs.value.node = rhs[0].value.expr", false}, - {"$$ = @$ @2", vars("%node", "a:0:expr"), "lhs.value.node = lhs.sym.location rhs[0].sym.location", false}, - {"$$ = $2", vars("%node", "a:0:expr"), "std::get(lhs.value) = std::get(rhs[0].value)", true}, + {"$$ = $2", vars("%node", "a:0:expr"), "#line 0 \"$$ = $2\"\nlhs.value.node = rhs[0].value.expr", false}, + {"$$ = @$ @2", vars("%node", "a:0:expr"), "#line 0 \"$$ = @$ @2\"\nlhs.value.node = lhs.sym.location rhs[0].sym.location", false}, + {"$$ = $2", vars("%node", "a:0:expr"), "#line 0 \"$$ = $2\"\nstd::get(lhs.value) = std::get(rhs[0].value)", true}, } for _, tc := range tests { - got, err := ccParserAction(tc.input, tc.args, node(tc.input), tc.useVariant) + opts := &grammar.Options{ + VariantStackEntry: tc.useVariant, + MaxRuleSizeForOrdinalRef: 2, + } + got, err := ccParserAction(tc.input, tc.args, node(tc.input), opts) if err != nil { t.Errorf("parserAction(%v, %v) failed with %v", tc.input, tc.args, err) continue @@ -214,6 +218,33 @@ func TestCcParserAction(t *testing.T) { } } +func TestCcParserActionSymbolLookupErrors(t *testing.T) { + tests := []struct { + input string + args *grammar.ActionVars + want string + }{ + {"$abc", vars(), "$abc:0:0: invalid reference \"$abc\". Cannot find symbol \"abc\" in rule"}, + {"$$ = $12", vars("%node", "a:0:expr"), "$$ = $12:0:0: index 12 is out of range [1, 3]"}, + {"$$ = $1", vars("%node", "a:0:expr", "b:1:expr", "c:2:expr"), "$$ = $1:0:0: invalid reference \"$1\". Ordinal references disabled for rules with more than 2 symbols, use the symbol alias instead"}, + } + for _, tc := range tests { + opts := &grammar.Options{ + VariantStackEntry: true, + MaxRuleSizeForOrdinalRef: 2, + } + got, err := ccParserAction(tc.input, tc.args, node(tc.input), opts) + if err == nil { + t.Errorf("parserAction(%v, %v) expected to fail with %v", tc.input, tc.args, got) + continue + } + got = err.Error() + if diff := diff.LineDiff(tc.want, got); diff != "" { + t.Errorf("parserAction(%v, %v) failed, but with diff:\n--- want\n+++ got\n%v", tc.input, tc.args, diff) + } + } +} + func vars(list ...string) *grammar.ActionVars { ret := &grammar.ActionVars{ CmdArgs: syntax.CmdArgs{ diff --git a/gen/templates/cc_parser_cc.go.tmpl b/gen/templates/cc_parser_cc.go.tmpl index 880bbb8a5..19b279feb 100644 --- a/gen/templates/cc_parser_cc.go.tmpl +++ b/gen/templates/cc_parser_cc.go.tmpl @@ -707,7 +707,7 @@ void Parser::fetchNext(Lexer& lexer, std::vector& stack) { {{ range $index, $rule := .Parser.Rules -}} {{ $act := index $.Parser.Actions $rule.Action -}} {{ if (ne $act.Code "") -}} -absl::Status Parser::action{{$index}}([[maybe_unused]] stackEntry& lhs, +absl::Status Parser::{{cc_action_func $index $act}}([[maybe_unused]] stackEntry& lhs, [[maybe_unused]] const stackEntry* rhs) { {{ if and $.Options.VariantStackEntry (ne $act.Vars.LHSType "") -}} {{/* Initialize the variant tag so that semantic action code can use $$ as if it @@ -715,7 +715,7 @@ absl::Status Parser::action{{$index}}([[maybe_unused]] stackEntry& lhs, */}} lhs.value.emplace<{{$act.Vars.LHSType}}>(); {{ end -}} -{{ cc_parser_action $act.Code $act.Vars $act.Origin $.Options.VariantStackEntry -}} +{{ cc_parser_action $act.Code $act.Vars $act.Origin $.Options -}} return absl::OkStatus(); } {{ end -}} @@ -745,7 +745,7 @@ absl::Status Parser::applyRule(int32_t rule, int32_t ruleLen, stackEntry& lhs, {{ end -}} {{ if $act.Code -}} { - absl::Status action_result = action{{$index}}(lhs, rhs); + absl::Status action_result = {{cc_action_func $index $act}}(lhs, rhs); if (!action_result.ok()) { return action_result; } @@ -869,6 +869,9 @@ absl::Status Parser::Parse(int{{$stateType}}_t start, int{{$stateType}}_t end, if (action >= 0) { // Reduce. +{{ if .Options.TrackReduces -}} + reduced_states.push_back(stack.back().state); +{{ end -}} int32_t rule = action; int32_t ln = tmRuleLen[rule]; stackEntry entry; @@ -899,6 +902,9 @@ absl::Status Parser::Parse(int{{$stateType}}_t start, int{{$stateType}}_t end, } else if (action {{if .Parser.Tables.Optimized}}<{{else}}=={{end}} -1) { // Shift. +{{ if .Options.TrackReduces -}} + reduced_states.clear(); +{{ end -}} {{ if .Parser.Tables.Optimized -}} state = -2-action; {{ else -}} diff --git a/gen/templates/cc_parser_h.go.tmpl b/gen/templates/cc_parser_h.go.tmpl index a7e279bbb..e14f87c42 100644 --- a/gen/templates/cc_parser_h.go.tmpl +++ b/gen/templates/cc_parser_h.go.tmpl @@ -168,7 +168,7 @@ class Parser final { {{ range $index, $rule := .Parser.Rules -}} {{ $act := index $.Parser.Actions $rule.Action -}} {{ if (ne $act.Code "") -}} - absl::Status action{{$index}}([[maybe_unused]] stackEntry& lhs, + absl::Status {{cc_action_func $index $act}}([[maybe_unused]] stackEntry& lhs, [[maybe_unused]] const stackEntry* rhs); {{ end -}} {{ end -}} diff --git a/grammar/grammar.go b/grammar/grammar.go index fea060765..4b9a2ca35 100644 --- a/grammar/grammar.go +++ b/grammar/grammar.go @@ -73,6 +73,7 @@ type SemanticAction struct { Report []Range // left to right, inner first Vars *ActionVars Origin status.SourceNode + NtName string // empty for mid-rule actions } // ActionVars captures enough information about a production rule to interpret its semantic action. @@ -111,28 +112,32 @@ type Reference struct { // Resolve resolves a symbol reference. `val` can either be a 0-based index (e.g. "0" in "$0") // or a named symbol (e.g. "a" in "$a"). // -// Returns "false" if `val` is not a valid reference, e.g. using "$a" in "start: b { $$ = $a };". +// Returns an error if `val` is not a valid reference in the original rule, e.g. using "$a" in +// "start: b". // -// Returns "true" with Index == -1 if `val` is a valid symbol in the original rule but it is not -// present in the expanded rule. For example, a: b? expands into two rules: +// Returns a Reference with Index -1 if `val` is a valid symbol in the original rule but does not +// show up in the expanded rule. For example, a: b? expands into two rules: // // a: b // | %empty // // For the %empty rule, Resolve("b") returns -1. -func (a *ActionVars) Resolve(val string) (Reference, bool) { - return a.resolve(val, true /*zeroBased*/) +func (a *ActionVars) Resolve(val string, origin status.SourceNode) (Reference, error) { + return a.resolve(val, true /*zeroBased*/, -1, val, origin) } // ResolveOneBased is similar to Resolve, except that `val` is 1-based if it is a number. -func (a *ActionVars) ResolveOneBased(val string) (Reference, bool) { - return a.resolve(val, false /*zeroBased*/) +func (a *ActionVars) ResolveOneBased(val string, maxRuleSizeForOrdinalRef int, name string, origin status.SourceNode) (Reference, error) { + return a.resolve(val, false /*zeroBased*/, maxRuleSizeForOrdinalRef, name, origin) } -func (a *ActionVars) resolve(val string, zeroBased bool) (Reference, bool) { +func (a *ActionVars) resolve(val string, zeroBased bool, maxRuleSizeForOrdinalRef int, name string, origin status.SourceNode) (Reference, error) { // `pos` is always 1-based. pos, err := strconv.Atoi(val) if err == nil { + if maxRuleSizeForOrdinalRef >= 0 && a.SymRefCount > maxRuleSizeForOrdinalRef { + return Reference{}, status.Errorf(origin, "invalid reference %q. Ordinal references disabled for rules with more than %v symbols, use the symbol alias instead", name, maxRuleSizeForOrdinalRef) + } // The input "val" is a number reference, e.g. $1. if zeroBased { // The input reference starts from 0, e.g. $0 references the first symbol. Change it to @@ -141,7 +146,10 @@ func (a *ActionVars) resolve(val string, zeroBased bool) (Reference, bool) { } if pos < 1 || pos >= a.CmdArgs.MaxPos { // Index out of range. - return Reference{}, false + if zeroBased { + return Reference{}, status.Errorf(origin, "index %v is out of range [0, %v]", pos, a.CmdArgs.MaxPos-1) + } + return Reference{}, status.Errorf(origin, "index %v is out of range [1, %v]", pos, a.CmdArgs.MaxPos) } } else { // The input "val" is a named symbol reference, e.g. $a. @@ -149,14 +157,14 @@ func (a *ActionVars) resolve(val string, zeroBased bool) (Reference, bool) { pos, exists = a.CmdArgs.Names[val] if !exists { // No such a symbol exists in the original rule. - return Reference{}, false + return Reference{}, status.Errorf(origin, "invalid reference %q. Cannot find symbol %q in rule", name, val) } } idx, exists := a.Remap[pos] if !exists { idx = -1 } - return Reference{Index: idx, Pos: pos}, true + return Reference{Index: idx, Pos: pos}, nil } // String is used as a digest of a semantic action environment (and also as a debug string). @@ -252,7 +260,9 @@ type Options struct { MaxLookahead int // If set, all lookaheads expressions will be validated to fit this limit. OptInstantiationSuffix string // Suffix that triggers auto-instantiation optional nonterminals (e.g. "opt" or "_opt"). - DisableSyntax []string // Lists grammar syntaxes that should be disabled. + DisableSyntax []string // Lists grammar syntaxes that should be disabled. + ExpansionLimit int // Error if a rule expansion produces more than this many rules. + ExpansionWarn int // Print warning if a rule expansion produces more than this many rules. // AST generation. Go-specific for now. TokenStream bool @@ -270,11 +280,13 @@ type Options struct { CancellableFetch bool // only in Cancellable parsers // C++ - Namespace string - IncludeGuardPrefix string - FilenamePrefix string - AbslIncludePrefix string // "absl" by default - DirIncludePrefix string // for generated headers - ParseParams []string // parser fields initialized in the constructor - VariantStackEntry bool // whether to generate a std::variant stackEntry rather than a union. Default false. + Namespace string + IncludeGuardPrefix string + FilenamePrefix string + AbslIncludePrefix string // "absl" by default + DirIncludePrefix string // for generated headers + ParseParams []string // parser fields initialized in the constructor + VariantStackEntry bool // whether to generate a std::variant stackEntry rather than a union. Default false. + TrackReduces bool // whether to track reduced states since most recent shift for error message generation. + MaxRuleSizeForOrdinalRef int // The number of rhs symbols after which ordinal references are disabled in semantic actions. } diff --git a/syntax/expand.go b/syntax/expand.go index 7f098a4d6..9d280cac8 100644 --- a/syntax/expand.go +++ b/syntax/expand.go @@ -53,6 +53,17 @@ type ExpandOptions struct { // DefaultValue returns the default value of the given type `t`. DefaultValue func(t string) string + + ExpansionLimit int + ExpansionWarn int +} + +// DefaultExpandOptions returns the default ExpandOptions. +func DefaultExpandOptions() *ExpandOptions { + return &ExpandOptions{ + ExpansionLimit: 65_536, + ExpansionWarn: 256, + } } // CcExpandOptions returns the ExpandOptions for generating C++ semantic actions. @@ -129,6 +140,7 @@ func CcExpandOptions() *ExpandOptions { // contain references only. Arrow can contain a sub-sequence if it reports more than one // symbol reference. func Expand(m *Model, opts *ExpandOptions) error { + var s status.Status e := &expander{ Model: m, m: make(map[string]int), @@ -144,13 +156,20 @@ func Expand(m *Model, opts *ExpandOptions) error { case Choice: var out []*Expr for _, rule := range nt.Value.Sub { - out = append(out, e.expandRule(rule)...) + rules, err := e.expandRule(rule, opts) + if err != nil { + s.AddError(err) + } + out = append(out, rules...) } nt.Value.Sub = collapseEmpty(out) case Set, Lookahead: // Do not introduce new nonterminals for top-level sets and lookaheads. default: - rules := e.expandRule(nt.Value) + rules, err := e.expandRule(nt.Value, opts) + if err != nil { + s.AddError(err) + } nt.Value = &Expr{ Kind: Choice, Sub: collapseEmpty(rules), @@ -295,7 +314,7 @@ func Expand(m *Model, opts *ExpandOptions) error { } } checkOrDie(m, "after expanding syntax sugar") - return nil + return s.Err() } type expander struct { @@ -376,9 +395,18 @@ func (e *expander) extractNonterm(expr *Expr, nonTermType string) *Expr { return &Expr{Kind: Reference, Symbol: sym, Model: e.Model, Origin: expr.Origin} } -func (e *expander) expandRule(rule *Expr) (expanded []*Expr) { - e.createdNts = make(map[int]int) - defer func() { +func (e *expander) expandRule(rule *Expr, opts *ExpandOptions) (expanded []*Expr, err status.Status) { + e.createdNts = make(map[int]int) + defer func() { + if len(expanded) > opts.ExpansionLimit { + if err == nil { + err = status.Status{} + } + err.Errorf(rule.Origin, "expanding rule produced %v rules which exeeds the limit of %v. Refactor the rule to reduce expansion or increase the `expansionLimit` value", len(expanded), opts.ExpansionLimit) + } else if len(expanded) > opts.ExpansionWarn { + loc := rule.Origin.SourceRange() + log.Printf("WARNING: Expanding rule produced %v rules which exeeds the warning threshold of %v. Refactor the rule to reduce expansion or increase the `expansionWarn` value. At %v.", len(expanded), opts.ExpansionWarn, loc.String()) + } for _, rule := range expanded { updateArgRefs(rule, e.createdNts) } @@ -395,10 +423,10 @@ func (e *expander) expandRule(rule *Expr) (expanded []*Expr) { Model: rule.Model, } } - return ret + return ret, nil } - return e.expandExpr(rule) + return e.expandExpr(rule), nil } func (e *expander) expandExpr(expr *Expr) []*Expr { diff --git a/syntax/set_test.go b/syntax/set_test.go index 35b8d4b4e..dcfc200d5 100644 --- a/syntax/set_test.go +++ b/syntax/set_test.go @@ -99,7 +99,7 @@ func TestSets(t *testing.T) { t.Errorf("cannot parse %q: %v", tc.input, err) continue } - err = syntax.Expand(model, &syntax.ExpandOptions{}) + err = syntax.Expand(model, syntax.DefaultExpandOptions()) if err != nil { t.Errorf("cannot expand %q: %v", tc.input, err) continue diff --git a/syntax/templates_test.go b/syntax/templates_test.go index f78110e94..f1d458a53 100644 --- a/syntax/templates_test.go +++ b/syntax/templates_test.go @@ -10,7 +10,7 @@ import ( ) func expand(m *syntax.Model) error { - return syntax.Expand(m, &syntax.ExpandOptions{}) + return syntax.Expand(m, syntax.DefaultExpandOptions()) } var modelTests = []struct {