Skip to content

Commit 22dbb78

Browse files
authored
fix: error for lookups with same handle (#1317)
* add test cases This adds two (failing) tests for this issue. * fix: use global handle checking Previously, the algorithm for checking that two constraints do not have the same handle (in the same module) only worked when the constraints were defined within the same module block. However, if they were defined in different blocks (e.g. in different files) the duplicate handle was not reported as an error. To resolve this, a global map of all handles is now maintained which is shared across all source files and module blocks.
1 parent c284827 commit 22dbb78

File tree

4 files changed

+85
-58
lines changed

4 files changed

+85
-58
lines changed

pkg/corset/compiler/parser.go

Lines changed: 64 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,22 @@ import (
4242
// Thus, you should never expect to see duplicate module names in the returned
4343
// array.
4444
func ParseSourceFiles(files []source.File, enforceTypes bool) (ast.Circuit, *source.Maps[ast.Node], []SyntaxError) {
45-
var circuit ast.Circuit
46-
// (for now) at most one error per source file is supported.
47-
var errors []SyntaxError
48-
// Construct an initially empty source map
49-
srcmaps := source.NewSourceMaps[ast.Node]()
50-
// Contents map holds the combined fragments of each module.
51-
contents := make(map[string]ast.Module, 0)
52-
// Names identifies the names of each unique module.
53-
names := make([]string, 0)
45+
var (
46+
circuit ast.Circuit
47+
// (for now) at most one error per source file is supported.
48+
errors []SyntaxError
49+
// Construct an initially empty source map
50+
srcmaps = source.NewSourceMaps[ast.Node]()
51+
// Contents map holds the combined fragments of each module.
52+
contents = make(map[string]ast.Module, 0)
53+
// Names identifies the names of each unique module.
54+
names = make([]string, 0)
55+
// Handles used for detecting duplicate constraint handles
56+
handles = make(map[string]bool)
57+
)
5458
//
5559
for _, file := range files {
56-
c, srcmap, errs := ParseSourceFile(file, enforceTypes)
60+
c, srcmap, errs := parseSourceFile(file, enforceTypes, handles)
5761
// Handle errors
5862
if len(errs) > 0 {
5963
// Report any errors encountered
@@ -94,10 +98,9 @@ func ParseSourceFiles(files []source.File, enforceTypes bool) (ast.Circuit, *sou
9498
return circuit, srcmaps, nil
9599
}
96100

97-
// ParseSourceFile parses the contents of a single lisp file into one or more
98-
// modules. Observe that every lisp file starts in the "prelude" or "root"
99-
// module, and may declare items for additional modules as necessary.
100-
func ParseSourceFile(srcfile source.File, enforceTypes bool) (ast.Circuit, *source.Map[ast.Node], []SyntaxError) {
101+
func parseSourceFile(srcfile source.File, enforceTypes bool,
102+
handles map[string]bool) (ast.Circuit, *source.Map[ast.Node], []SyntaxError) {
103+
//
101104
//
102105
var (
103106
circuit ast.Circuit
@@ -111,7 +114,7 @@ func ParseSourceFile(srcfile source.File, enforceTypes bool) (ast.Circuit, *sour
111114
return circuit, nil, []SyntaxError{*err}
112115
}
113116
// Construct parser for corset syntax
114-
p := NewParser(srcfile, srcmap, enforceTypes)
117+
p := NewParser(srcfile, srcmap, enforceTypes, handles)
115118
// Parse whatever is declared at the beginning of the file before the first
116119
// module declaration. These declarations form part of the "prelude".
117120
if circuit.Declarations, terms, errors = p.parseModuleContents(path, terms); len(errors) > 0 {
@@ -150,6 +153,8 @@ func ParseSourceFile(srcfile source.File, enforceTypes bool) (ast.Circuit, *sour
150153
// ensuring that expressions are well-typed, etc) --- that is left up to the
151154
// compiler.
152155
type Parser struct {
156+
// Handles map used to detect duplicate handles
157+
handles map[string]bool
153158
// Translator used for recursive expressions.
154159
translator *sexp.Translator[ast.Expr]
155160
// Mapping from constructed S-Expressions to their spans in the original text.
@@ -160,12 +165,14 @@ type Parser struct {
160165

161166
// NewParser constructs a new parser using a given mapping from S-Expressions to
162167
// spans in the underlying source file.
163-
func NewParser(srcfile source.File, srcmap *source.Map[sexp.SExp], enforceTypes bool) *Parser {
168+
func NewParser(srcfile source.File, srcmap *source.Map[sexp.SExp], enforceTypes bool, handles map[string]bool,
169+
) *Parser {
170+
//
164171
p := sexp.NewTranslator[ast.Expr](&srcfile, srcmap)
165172
// Construct (initially empty) node map
166173
nodemap := source.NewSourceMap[ast.Node](srcmap.Source())
167174
// Construct parser
168-
parser := &Parser{p, nodemap, enforceTypes}
175+
parser := &Parser{handles, p, nodemap, enforceTypes}
169176
// Configure expression translator
170177
p.AddSymbolRule(constantParserRule)
171178
p.AddSymbolRule(varAccessParserRule)
@@ -215,9 +222,8 @@ func (p *Parser) parseModuleContents(path file.Path, terms []sexp.SExp) ([]ast.D
215222
[]SyntaxError) {
216223
//
217224
var (
218-
errors []SyntaxError
219-
handles = make(map[string]bool)
220-
decls = make([]ast.Declaration, 0)
225+
errors []SyntaxError
226+
decls = make([]ast.Declaration, 0)
221227
)
222228
//
223229
for i, s := range terms {
@@ -228,7 +234,7 @@ func (p *Parser) parseModuleContents(path file.Path, terms []sexp.SExp) ([]ast.D
228234
errors = append(errors, *err)
229235
} else if e.MatchSymbols(2, "module") {
230236
return decls, terms[i:], errors
231-
} else if decl, errs := p.parseDeclaration(path, e, handles); len(errs) > 0 {
237+
} else if decl, errs := p.parseDeclaration(path, e); len(errs) > 0 {
232238
errors = append(errors, errs...)
233239
} else {
234240
// Continue accumulating declarations for this module.
@@ -268,8 +274,7 @@ func (p *Parser) parseModuleStart(s sexp.SExp) (string, []SyntaxError) {
268274
return name, errors
269275
}
270276

271-
func (p *Parser) parseDeclaration(module file.Path, s *sexp.List,
272-
handles map[string]bool) (ast.Declaration, []SyntaxError) {
277+
func (p *Parser) parseDeclaration(module file.Path, s *sexp.List) (ast.Declaration, []SyntaxError) {
273278
//
274279
var (
275280
decl ast.Declaration
@@ -285,7 +290,7 @@ func (p *Parser) parseDeclaration(module file.Path, s *sexp.List,
285290
} else if s.Len() > 1 && s.MatchSymbols(1, "defconst") {
286291
decl, errors = p.parseDefConst(module, s.Elements)
287292
} else if s.Len() == 4 && s.MatchSymbols(2, "defconstraint") {
288-
decl, errors = p.parseDefConstraint(module, s.Elements, handles)
293+
decl, errors = p.parseDefConstraint(module, s.Elements)
289294
} else if s.Len() == 3 && s.MatchSymbols(1, "defpurefun") {
290295
decl, errors = p.parseDefFun(module, true, s.Elements)
291296
} else if s.Len() == 3 && s.MatchSymbols(1, "defun") {
@@ -295,21 +300,21 @@ func (p *Parser) parseDeclaration(module file.Path, s *sexp.List,
295300
} else if s.Len() == 3 && s.MatchSymbols(1, "definterleaved") {
296301
decl, errors = p.parseDefInterleaved(module, s.Elements)
297302
} else if s.Len() == 4 && s.MatchSymbols(1, "deflookup") {
298-
decl, errors = p.parseDefLookup(s.Elements, handles)
303+
decl, errors = p.parseDefLookup(module, s.Elements)
299304
} else if (s.Len() == 5 || s.Len() == 6) && s.MatchSymbols(1, "defclookup") {
300-
decl, errors = p.parseDefConditionalLookup(s.Elements, handles)
305+
decl, errors = p.parseDefConditionalLookup(module, s.Elements)
301306
} else if s.Len() == 4 && s.MatchSymbols(1, "defmlookup") {
302-
decl, errors = p.parseDefMultiLookup(s.Elements, handles)
307+
decl, errors = p.parseDefMultiLookup(module, s.Elements)
303308
} else if s.Len() == 3 && s.MatchSymbols(2, "defpermutation") {
304309
decl, errors = p.parseDefPermutation(module, s.Elements)
305310
} else if s.Len() == 4 && s.MatchSymbols(2, "defperspective") {
306311
decl, errors = p.parseDefPerspective(module, s.Elements)
307312
} else if 3 <= s.Len() && s.Len() <= 4 && s.MatchSymbols(2, "defproperty") {
308-
decl, errors = p.parseDefProperty(s.Elements, handles)
313+
decl, errors = p.parseDefProperty(module, s.Elements)
309314
} else if s.Len() == 3 && s.MatchSymbols(2, "defsorted") {
310-
decl, errors = p.parseDefSorted(false, s.Elements, handles)
315+
decl, errors = p.parseDefSorted(module, false, s.Elements)
311316
} else if 3 <= s.Len() && s.Len() <= 4 && s.MatchSymbols(2, "defstrictsorted") {
312-
decl, errors = p.parseDefSorted(true, s.Elements, handles)
317+
decl, errors = p.parseDefSorted(module, true, s.Elements)
313318
} else if s.Len() == 3 && s.MatchSymbols(1, "defcomputedcolumn") {
314319
decl, errors = p.parseDefComputedColumn(module, s.Elements)
315320
} else {
@@ -761,23 +766,21 @@ func (p *Parser) parseDefConstHead(head sexp.SExp) (*sexp.Symbol, ast.Type, bool
761766
}
762767

763768
// Parse a vanishing declaration
764-
func (p *Parser) parseDefConstraint(module file.Path, elements []sexp.SExp,
765-
handles map[string]bool) (ast.Declaration, []SyntaxError) {
766-
var (
767-
errors []SyntaxError
768-
handle string
769-
)
769+
func (p *Parser) parseDefConstraint(module file.Path, elements []sexp.SExp) (ast.Declaration, []SyntaxError) {
770+
var errors []SyntaxError
770771
// Initial sanity checks
771772
if !isIdentifier(elements[1]) {
772773
return nil, p.translator.SyntaxErrors(elements[1], "invalid constraint handle")
773-
} else {
774-
handle = elements[1].AsSymbol().Value
775774
}
775+
//
776+
handle := elements[1].AsSymbol().Value
777+
// Generate qualified name
778+
qualifiedHandle := fmt.Sprintf("%s:%s", module.String(), handle)
776779
// Check for duplicate
777-
if _, ok := handles[handle]; ok {
780+
if _, ok := p.handles[qualifiedHandle]; ok {
778781
return nil, p.translator.SyntaxErrors(elements[1], "duplicate handle")
779782
} else {
780-
handles[handle] = true
783+
p.handles[qualifiedHandle] = true
781784
}
782785
// Vanishing constraints do not have global scope, hence qualified column
783786
// accesses are not permitted.
@@ -869,9 +872,9 @@ func (p *Parser) parseDefInterleavedSourceArray(source *sexp.Array) (ast.TypedSy
869872
}
870873

871874
// Parse a lookup declaration
872-
func (p *Parser) parseDefLookup(elements []sexp.SExp, handles map[string]bool) (ast.Declaration, []SyntaxError) {
875+
func (p *Parser) parseDefLookup(module file.Path, elements []sexp.SExp) (ast.Declaration, []SyntaxError) {
873876
// Extract items
874-
handle, checked, errors := p.parseLookupHandle(elements[1], handles)
877+
handle, checked, errors := p.parseLookupHandle(module, elements[1])
875878
targets, tgtErrors := p.parseDefLookupSources("target", elements[2])
876879
sources, srcErrors := p.parseDefLookupSources("source", elements[3])
877880
// Combine any and all errors
@@ -897,16 +900,15 @@ func (p *Parser) parseDefLookup(elements []sexp.SExp, handles map[string]bool) (
897900
}
898901

899902
// Parse a conditional lookup declaration
900-
func (p *Parser) parseDefConditionalLookup(elements []sexp.SExp,
901-
handles map[string]bool) (ast.Declaration, []SyntaxError) {
903+
func (p *Parser) parseDefConditionalLookup(module file.Path, elements []sexp.SExp) (ast.Declaration, []SyntaxError) {
902904
// Extract items
903905
var (
904906
targets, sources []ast.Expr
905907
targetSelector, sourceSelector ast.Expr
906908
errs1, errs2, errs3, errs4 []SyntaxError
907909
)
908910
//
909-
handle, checked, errors := p.parseLookupHandle(elements[1], handles)
911+
handle, checked, errors := p.parseLookupHandle(module, elements[1])
910912
//
911913
if len(elements) == 6 {
912914
targetSelector, errs1 = p.translator.Translate(elements[2])
@@ -942,9 +944,9 @@ func (p *Parser) parseDefConditionalLookup(elements []sexp.SExp,
942944
[][]ast.Expr{targets}), nil
943945
}
944946

945-
func (p *Parser) parseDefMultiLookup(elements []sexp.SExp, handles map[string]bool) (ast.Declaration, []SyntaxError) {
947+
func (p *Parser) parseDefMultiLookup(module file.Path, elements []sexp.SExp) (ast.Declaration, []SyntaxError) {
946948
// Extract items
947-
handle, checked, errors := p.parseLookupHandle(elements[1], handles)
949+
handle, checked, errors := p.parseLookupHandle(module, elements[1])
948950
m, targets, tgtErrors := p.parseDefLookupMultiSources("target", elements[2])
949951
n, sources, srcErrors := p.parseDefLookupMultiSources("source", elements[3])
950952
// Combine any and all errors
@@ -966,7 +968,7 @@ func (p *Parser) parseDefMultiLookup(elements []sexp.SExp, handles map[string]bo
966968
return ast.NewDefLookup(handle, checked, sourceSelectors, sources, targetSelectors, targets), nil
967969
}
968970

969-
func (p *Parser) parseLookupHandle(element sexp.SExp, handles map[string]bool) (string, bool, []SyntaxError) {
971+
func (p *Parser) parseLookupHandle(module file.Path, element sexp.SExp) (string, bool, []SyntaxError) {
970972
var (
971973
checked = true
972974
errors []SyntaxError
@@ -981,12 +983,14 @@ func (p *Parser) parseLookupHandle(element sexp.SExp, handles map[string]bool) (
981983
} else {
982984
handle = element.AsSymbol().Value
983985
}
986+
// Generate qualified name
987+
qualifiedHandle := fmt.Sprintf("%s:%s", module.String(), handle)
984988
// Check for duplicate handle
985-
if _, ok := handles[handle]; ok {
989+
if _, ok := p.handles[qualifiedHandle]; ok {
986990
return "", checked, p.translator.SyntaxErrors(element, "duplicate handle")
987991
} else if len(errors) == 0 {
988992
// Done
989-
handles[handle] = true
993+
p.handles[qualifiedHandle] = true
990994
}
991995
//
992996
return handle, checked, errors
@@ -1149,8 +1153,7 @@ func (p *Parser) parseDefPermutation(module file.Path, elements []sexp.SExp) (as
11491153
}
11501154

11511155
// Parse a permutation declaration
1152-
func (p *Parser) parseDefSorted(strict bool, elements []sexp.SExp,
1153-
handles map[string]bool) (ast.Declaration, []SyntaxError) {
1156+
func (p *Parser) parseDefSorted(module file.Path, strict bool, elements []sexp.SExp) (ast.Declaration, []SyntaxError) {
11541157
//
11551158
var (
11561159
selector util.Option[ast.Expr]
@@ -1182,12 +1185,14 @@ func (p *Parser) parseDefSorted(strict bool, elements []sexp.SExp,
11821185
} else {
11831186
handle = elements[1].AsSymbol().Value
11841187
}
1188+
// Generate qualified name
1189+
qualifiedHandle := fmt.Sprintf("%s:%s", module.String(), handle)
11851190
// Check for duplicate handle
1186-
if _, ok := handles[handle]; ok {
1191+
if _, ok := p.handles[qualifiedHandle]; ok {
11871192
return nil, p.translator.SyntaxErrors(elements[1], "duplicate handle")
11881193
} else if len(errors) == 0 {
11891194
// Record handle
1190-
handles[handle] = true
1195+
p.handles[qualifiedHandle] = true
11911196
}
11921197
// Check source Expressions
11931198
if sexpSources == nil {
@@ -1327,7 +1332,7 @@ func (p *Parser) parseDefPerspective(module file.Path, elements []sexp.SExp) (as
13271332
}
13281333

13291334
// Parse a property assertion
1330-
func (p *Parser) parseDefProperty(elements []sexp.SExp, handles map[string]bool) (ast.Declaration, []SyntaxError) {
1335+
func (p *Parser) parseDefProperty(module file.Path, elements []sexp.SExp) (ast.Declaration, []SyntaxError) {
13311336
var (
13321337
errors []SyntaxError
13331338
assertion int = len(elements) - 1
@@ -1341,12 +1346,14 @@ func (p *Parser) parseDefProperty(elements []sexp.SExp, handles map[string]bool)
13411346
// Extract handle
13421347
handle = elements[1].AsSymbol().Value
13431348
}
1349+
// Generate qualified name
1350+
qualifiedHandle := fmt.Sprintf("%s:%s", module.String(), handle)
13441351
// Check for duplicate handle
1345-
if _, ok := handles[handle]; ok {
1352+
if _, ok := p.handles[qualifiedHandle]; ok {
13461353
return nil, p.translator.SyntaxErrors(elements[1], "duplicate handle")
13471354
} else if len(errors) == 0 {
13481355
// Record handle
1349-
handles[handle] = true
1356+
p.handles[qualifiedHandle] = true
13501357
}
13511358
// Check for any attributes
13521359
if len(elements) > 3 {

pkg/test/corset_invalid_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ func Test_Invalid_Basic_18(t *testing.T) {
9797
func Test_Invalid_Basic_19(t *testing.T) {
9898
checkCorsetInvalid(t, "corset/invalid/basic_invalid_19")
9999
}
100-
100+
func Test_Invalid_Basic_20(t *testing.T) {
101+
checkCorsetInvalid(t, "corset/invalid/basic_invalid_20")
102+
}
101103
func Test_Invalid_Logic_01(t *testing.T) {
102104
checkCorsetInvalid(t, "corset/invalid/logic_invalid_01")
103105
}
@@ -515,6 +517,9 @@ func Test_Invalid_Lookup_17(t *testing.T) {
515517
func Test_Invalid_Lookup_18(t *testing.T) {
516518
checkCorsetInvalid(t, "corset/invalid/lookup_invalid_18")
517519
}
520+
func Test_Invalid_Lookup_19(t *testing.T) {
521+
checkCorsetInvalid(t, "corset/invalid/lookup_invalid_19")
522+
}
518523

519524
// ===================================================================
520525
// Interleavings
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
;;error:8:16-20:duplicate handle
2+
(module m1)
3+
4+
(defcolumns (X :i16) (Y :i16))
5+
(defconstraint test () (== X Y))
6+
7+
(module m1)
8+
(defconstraint test () (!= X Y))
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
;;error:7:13-17:duplicate handle
2+
(module m1)
3+
(defcolumns (X :i16) (Y :i16))
4+
(defclookup test (Y) 1 (X))
5+
6+
(module m1)
7+
(defclookup test (X) 1 (Y))

0 commit comments

Comments
 (0)