Skip to content

Commit 0b100c4

Browse files
dlongnecke-crayMaxrimus
authored andcommitted
Merge pull request #17413 from dlongnecke-cray/public-17401
Fix bugs in set composition operators when `parSafe=true` (#17413) The composition operators for `set` declare a result that has a `parSafe` value of `(lhs.parSafe || rhs.parSafe)`. Prior to this PR, the declared return type of these operators omitted the value of `parSafe`, which caused it to default to false. This led to compiler errors when a composition operator was used on a set with `parSafe=true`. Omit a declared return type for the set composition operators. The return type is now correctly inferred. Add a test covering all composition operators (|, +, -, &, ^) as well as their compound assignment variants (|=, +=, -=, &=, ^=). The test is repeated to cover all combinations of `parSafe` values for the LHS and RHS sets. Define a cast operator for the `set` type. Currently it requires both sets to have the same element type. Loosening this restriction (e.g. for a cast from set of subclass to set of superclass) is possible. The cast operator was added to keep the compiler from complaining about assignment between sets with different `parSafe` values. It is not documented. Resolves #17401. Reviewed by @vasslitvinov and @mppf. Thanks! TESTING: - [x] `ALL` on `linux64` when `COMM=none` - [x] `ALL` on `linux64` when `COMM=gasnet` - [x] Checked updated docs locally Signed-off-by: David Longnecker <[email protected]>
1 parent a992351 commit 0b100c4

7 files changed

+239
-25
lines changed

modules/standard/Set.chpl

+49-25
Original file line numberDiff line numberDiff line change
@@ -195,17 +195,26 @@ module Set {
195195
196196
:arg other: A set to initialize this set with.
197197
*/
198-
proc init=(const ref other: set(?t, ?)) lifetime this < other {
199-
this.eltType = t;
200-
this.parSafe = other.parSafe;
198+
proc init=(const ref other: set(?t, ?p)) lifetime this < other {
199+
this.eltType = if this.type.eltType != ? then
200+
this.type.eltType else t;
201+
this.parSafe = if this.type.parSafe != ? then
202+
this.type.parSafe else p;
201203
this.complete();
202204

203-
if !isCopyableType(eltType) then
204-
compilerError('cannot initialize ' + this.type:string + ' from ' +
205-
other.type:string + ' because element type ' +
206-
eltType:string + ' is not copyable');
207-
208-
for elem in other do _addElem(elem);
205+
// TODO: Relax this to allow if 'isCoercible(t, this.eltType)'?
206+
if eltType != t {
207+
compilerError('cannot initialize ', this.type:string, ' from ',
208+
other.type:string, ' due to element type ',
209+
'mismatch');
210+
} else if !isCopyableType(eltType) {
211+
compilerError('cannot initialize ', this.type:string, ' from ',
212+
other.type:string, ' because element type ',
213+
eltType:string, ' is not copyable');
214+
} else {
215+
// TODO: Use a forall when this.parSafe?
216+
for elem in other do _addElem(elem);
217+
}
209218
}
210219

211220
pragma "no doc"
@@ -539,11 +548,14 @@ module Set {
539548
:arg b: A set to take the union of.
540549
541550
:return: A new set containing the union between `a` and `b`.
542-
:rtype: `set(?t, ?)`
543551
*/
544-
proc |(const ref a: set(?t, ?), const ref b: set(t, ?)): set(t) {
552+
proc |(const ref a: set(?t, ?), const ref b: set(t, ?)) {
545553
var result: set(t, (a.parSafe || b.parSafe));
546554

555+
// TODO: Split-init causes weird errors, remove this line and then run
556+
// setCompositionParSafe.chpl to see.
557+
result;
558+
547559
result = a;
548560
result |= b;
549561

@@ -569,9 +581,8 @@ module Set {
569581
:arg b: A set to take the union of.
570582
571583
:return: A new set containing the union between `a` and `b`.
572-
:rtype: `set(?t, ?)`
573584
*/
574-
proc +(const ref a: set(?t, ?), const ref b: set(t, ?)): set(t, ?) {
585+
proc +(const ref a: set(?t, ?), const ref b: set(t, ?)) {
575586
return a | b;
576587
}
577588

@@ -592,13 +603,12 @@ module Set {
592603
:arg b: A set to take the difference of.
593604
594605
:return: A new set containing the difference between `a` and `b`.
595-
:rtype: `set(t)`
596606
*/
597-
proc -(const ref a: set(?t, ?), const ref b: set(t, ?)): set(t) {
607+
proc -(const ref a: set(?t, ?), const ref b: set(t, ?)) {
598608
var result = new set(t, (a.parSafe || b.parSafe));
599609

600610
if a.parSafe && b.parSafe {
601-
forall x in a do
611+
forall x in a with (ref result) do
602612
if !b.contains(x) then
603613
result.add(x);
604614
} else {
@@ -623,7 +633,7 @@ module Set {
623633
*/
624634
proc -=(ref lhs: set(?t, ?), const ref rhs: set(t, ?)) {
625635
if lhs.parSafe && rhs.parSafe {
626-
forall x in rhs do
636+
forall x in rhs with (ref lhs) do
627637
lhs.remove(x);
628638
} else {
629639
for x in rhs do
@@ -638,15 +648,14 @@ module Set {
638648
:arg b: A set to take the intersection of.
639649
640650
:return: A new set containing the intersection of `a` and `b`.
641-
:rtype: `set(t)`
642651
*/
643-
proc &(const ref a: set(?t, ?), const ref b: set(t, ?)): set(t) {
652+
proc &(const ref a: set(?t, ?), const ref b: set(t, ?)) {
644653
var result: set(t, (a.parSafe || b.parSafe));
645654

646655
/* Iterate over the smaller set */
647656
if a.size <= b.size {
648657
if a.parSafe && b.parSafe {
649-
forall x in a do
658+
forall x in a with (ref result) do
650659
if b.contains(x) then
651660
result.add(x);
652661
} else {
@@ -656,7 +665,7 @@ module Set {
656665
}
657666
} else {
658667
if a.parSafe && b.parSafe {
659-
forall x in b do
668+
forall x in b with (ref result) do
660669
if a.contains(x) then
661670
result.add(x);
662671
} else {
@@ -687,14 +696,15 @@ module Set {
687696
var result: set(t, (lhs.parSafe || rhs.parSafe));
688697

689698
if lhs.parSafe && rhs.parSafe {
690-
forall x in lhs do
699+
forall x in lhs with (ref result) do
691700
if rhs.contains(x) then
692701
result.add(x);
693702
} else {
694703
for x in lhs do
695704
if rhs.contains(x) then
696705
result.add(x);
697706
}
707+
698708
lhs = result;
699709
}
700710

@@ -705,11 +715,14 @@ module Set {
705715
:arg b: A set to take the symmetric difference of.
706716
707717
:return: A new set containing the symmetric difference of `a` and `b`.
708-
:rtype: `set(?t, ?)`
709718
*/
710-
proc ^(const ref a: set(?t, ?), const ref b: set(t, ?)): set(t) {
719+
proc ^(const ref a: set(?t, ?), const ref b: set(t, ?)) {
711720
var result: set(t, (a.parSafe || b.parSafe));
712721

722+
// TODO: Split-init causes weird errors, remove this line and then run
723+
// setCompositionParSafe.chpl to see.
724+
result;
725+
713726
/* Expect the loop in ^= to be more expensive than the loop in =,
714727
so arrange for the rhs of the ^= to be the smaller set. */
715728
if a.size <= b.size {
@@ -737,7 +750,7 @@ module Set {
737750
*/
738751
proc ^=(ref lhs: set(?t, ?), const ref rhs: set(t, ?)) {
739752
if lhs.parSafe && rhs.parSafe {
740-
forall x in rhs {
753+
forall x in rhs with (ref lhs) {
741754
if lhs.contains(x) {
742755
lhs.remove(x);
743756
} else {
@@ -784,6 +797,17 @@ module Set {
784797
return result;
785798
}
786799

800+
pragma "no doc"
801+
operator :(x: set(?et1, ?p1), type t: set(?et2, ?p2)) {
802+
// TODO: Allow coercion between element types? If we do then init=
803+
// should also be changed accordingly.
804+
if et1 != et2 then
805+
compilerError('Cannot cast to set with different ',
806+
'element type: ', t:string);
807+
var result: set(et1, p2) = x;
808+
return result;
809+
}
810+
787811
/*
788812
Return `true` if the sets `a` and `b` are not equal.
789813
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use Set;
2+
3+
config const lo = 1;
4+
config const hi = 8;
5+
6+
enum LockMode { neither, left, right, both }
7+
8+
proc addToSets(ref a, ref b) {
9+
for i in lo..hi {
10+
if !(i % 2) {
11+
a.add(i);
12+
} else {
13+
b.add(i);
14+
}
15+
}
16+
}
17+
18+
proc populateSets(param mode: LockMode) where mode == LockMode.left {
19+
var lhs: set(int, parSafe=true);
20+
var rhs: set(int);
21+
addToSets(lhs, rhs);
22+
return (lhs, rhs);
23+
}
24+
25+
proc populateSets(param mode: LockMode) where mode == LockMode.right {
26+
var lhs: set(int);
27+
var rhs: set(int, parSafe=true);
28+
addToSets(lhs, rhs);
29+
return (lhs, rhs);
30+
}
31+
32+
proc populateSets(param mode: LockMode) where mode == LockMode.both {
33+
var lhs, rhs: set(int, parSafe=true);
34+
addToSets(lhs, rhs);
35+
return (lhs, rhs);
36+
}
37+
38+
proc populateSets(param mode: LockMode) where mode == LockMode.neither {
39+
var lhs, rhs: set(int);
40+
addToSets(lhs, rhs);
41+
return (lhs, rhs);
42+
}
43+
44+
proc display(op, s) {
45+
param space = ' ';
46+
write(op, space, ':', space);
47+
for i in lo..hi do
48+
if s.contains(i) then
49+
write(i, space);
50+
writeln();
51+
}
52+
53+
proc doTest(param mode, op) {
54+
var (a, b) = populateSets(mode);
55+
const opEqual = op + '=';
56+
select op {
57+
when '|' { var c = a | b; display(op, c); a |= b; display(opEqual, a); }
58+
when '+' { var c = a + b; display(op, c); a += b; display(opEqual, a); }
59+
when '-' { var c = a - b; display(op, c); a -= b; display(opEqual, a); }
60+
when '&' { var c = a & b; display(op, c); a &= b; display(opEqual, a); }
61+
when '^' { var c = a ^ b; display(op, c); a ^= b; display(opEqual, a); }
62+
otherwise { halt('Unrecognized op: ' + op:string); }
63+
}
64+
}
65+
66+
proc testSetCompositionOps(param mode) {
67+
writeln('Which set is \'parSafe=true\': ', mode);
68+
doTest(mode, '|');
69+
doTest(mode, '+');
70+
doTest(mode, '-');
71+
doTest(mode, '&');
72+
doTest(mode, '^');
73+
}
74+
75+
proc test1() {
76+
writeln('T1');
77+
testSetCompositionOps(LockMode.neither);
78+
writeln();
79+
}
80+
test1();
81+
82+
proc test2() {
83+
writeln('T2');
84+
testSetCompositionOps(LockMode.left);
85+
writeln();
86+
}
87+
test2();
88+
89+
proc test3() {
90+
writeln('T3');
91+
testSetCompositionOps(LockMode.right);
92+
writeln();
93+
}
94+
test3();
95+
96+
proc test4() {
97+
writeln('T4');
98+
testSetCompositionOps(LockMode.both);
99+
writeln();
100+
}
101+
test4();
102+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
T1
2+
Which set is 'parSafe=true': neither
3+
| : 1 2 3 4 5 6 7 8
4+
|= : 1 2 3 4 5 6 7 8
5+
+ : 1 2 3 4 5 6 7 8
6+
+= : 1 2 3 4 5 6 7 8
7+
- : 2 4 6 8
8+
-= : 2 4 6 8
9+
& :
10+
&= :
11+
^ : 1 2 3 4 5 6 7 8
12+
^= : 1 2 3 4 5 6 7 8
13+
14+
T2
15+
Which set is 'parSafe=true': left
16+
| : 1 2 3 4 5 6 7 8
17+
|= : 1 2 3 4 5 6 7 8
18+
+ : 1 2 3 4 5 6 7 8
19+
+= : 1 2 3 4 5 6 7 8
20+
- : 2 4 6 8
21+
-= : 2 4 6 8
22+
& :
23+
&= :
24+
^ : 1 2 3 4 5 6 7 8
25+
^= : 1 2 3 4 5 6 7 8
26+
27+
T3
28+
Which set is 'parSafe=true': right
29+
| : 1 2 3 4 5 6 7 8
30+
|= : 1 2 3 4 5 6 7 8
31+
+ : 1 2 3 4 5 6 7 8
32+
+= : 1 2 3 4 5 6 7 8
33+
- : 2 4 6 8
34+
-= : 2 4 6 8
35+
& :
36+
&= :
37+
^ : 1 2 3 4 5 6 7 8
38+
^= : 1 2 3 4 5 6 7 8
39+
40+
T4
41+
Which set is 'parSafe=true': both
42+
| : 1 2 3 4 5 6 7 8
43+
|= : 1 2 3 4 5 6 7 8
44+
+ : 1 2 3 4 5 6 7 8
45+
+= : 1 2 3 4 5 6 7 8
46+
- : 2 4 6 8
47+
-= : 2 4 6 8
48+
& :
49+
&= :
50+
^ : 1 2 3 4 5 6 7 8
51+
^= : 1 2 3 4 5 6 7 8
52+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use Set;
2+
3+
class C1 { var x = 0; }
4+
class C2 : C1 { var y = 0; }
5+
6+
proc test1() {
7+
var s1: set(shared C1);
8+
for i in 0..3 do s1.add(new shared C1(i));
9+
var s2: set(shared C2) = s1;
10+
}
11+
test1();
12+
13+
proc test2() {
14+
var s1: set(int);
15+
for i in 0..3 do s1.add(i);
16+
var s2: set(real) = s1;
17+
}
18+
test2();
19+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
setInitTypeMismatchError.chpl:6: In function 'test1':
2+
setInitTypeMismatchError.chpl:9: error: invalid copy-initialization
3+
setInitTypeMismatchError.chpl:9: error: cannot initialize set(shared C2,false) from set(shared C1,false) due to element type mismatch
4+
setInitTypeMismatchError.chpl:13: In function 'test2':
5+
setInitTypeMismatchError.chpl:16: error: invalid copy-initialization
6+
setInitTypeMismatchError.chpl:16: error: cannot initialize set(real(64),false) from set(int(64),false) due to element type mismatch
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use Set;
2+
3+
proc test1() {
4+
var a: set(int);
5+
var b: set(int, parSafe=true);
6+
var c: set(int, (a.parSafe || b.parSafe));
7+
8+
c = a;
9+
}
10+
test1();
11+

test/library/standard/Set/setSplitInit.good

Whitespace-only changes.

0 commit comments

Comments
 (0)