From 3fa795a6b5baa6147fa108e43b534b11f138ae43 Mon Sep 17 00:00:00 2001 From: Brad Chamberlain Date: Wed, 13 Sep 2023 13:46:26 -0700 Subject: [PATCH 01/27] Generalize and tighten up read/writeBinary() for arrays This changes how read/writeBinary() interacts with arrays by: * extending them to work with multidimensional arrays * restricting them to work only with contiguous, local arrays - and slices thereof * adding errors for cases we can't handle to avoid accidentally getting a promoted call to a scalar routine The first bullet was proposed in #23081 and generally accepted by the IO team since it seemed increasingly arbitrary to support 1D arrays only. The second bullet, I believe, reflects what was intended for these routines on arrays when Michael and I were originally discussing them, which was to limit them to fairly primitive array types that could be blasted out/in with a single write/read. Though the routines accepted Block-distributed arrays (say) before this PR, the read() didn't actually work, and the approach relied on reading/writing element by element. Currently, if the array being read/written is not DR/a DR view, we fall into the error overload and complain about the array not currently being supported. If the array is not contiguous, we throw an illegal argument error. The existing logic also didn't handle the read/write using a single data blast for endiannesses other than native, so I also added a compiler error for those cases until such time as we can do it in a single read/write. TODO: - [ ] are we OK with restricting to native endianness for the time being? - [ ] wording/phrasing of illegal argument error? - [ ] how to document helper routine used in where clause? - [ ] how to document what we do or do not support? - [ ] more testing, particularly for slice cases (e.g., column or row of local 2D array? local slice of block-distributed array?) --- Signed-off-by: Brad Chamberlain --- modules/standard/IO.chpl | 131 +++++++++--------- .../readWriteMultidimArray-block.good | 1 + .../writeBinary/readWriteMultidimArray.chpl | 27 ++++ .../readWriteMultidimArray.compopts | 2 + .../writeBinary/readWriteMultidimArray.good | 21 +++ 5 files changed, 117 insertions(+), 65 deletions(-) create mode 100644 test/library/standard/IO/writeBinary/readWriteMultidimArray-block.good create mode 100644 test/library/standard/IO/writeBinary/readWriteMultidimArray.chpl create mode 100644 test/library/standard/IO/writeBinary/readWriteMultidimArray.compopts create mode 100644 test/library/standard/IO/writeBinary/readWriteMultidimArray.good diff --git a/modules/standard/IO.chpl b/modules/standard/IO.chpl index bd476a9c68b3..a88f09468ba7 100644 --- a/modules/standard/IO.chpl +++ b/modules/standard/IO.chpl @@ -2132,19 +2132,6 @@ proc convertIoMode(mode:iomode):ioMode { } } -pragma "last resort" -@deprecated(notes="open with an iomode argument is deprecated - please use :enum:`ioMode`") -proc open(path:string, mode:iomode, hints=ioHintSet.empty, - style:iostyle): file throws { - return open(path, convertIoMode(mode), hints, style); -} - -@deprecated("open with a 'style' argument is deprecated") -proc open(path:string, mode:ioMode, hints=ioHintSet.empty, - style:iostyle): file throws { - return openHelper(path, mode, hints, style:iostyleInternal); -} - /* Open a file on a filesystem. Note that once the file is open, you will need to @@ -2176,6 +2163,19 @@ proc open(path:string, mode:iomode, hints=ioHintSet.empty): file throws { return open(path, convertIoMode(mode), hints); } +pragma "last resort" +@deprecated(notes="open with an iomode argument is deprecated - please use :enum:`ioMode`") +proc open(path:string, mode:iomode, hints=ioHintSet.empty, + style:iostyle): file throws { + return open(path, convertIoMode(mode), hints, style); +} + +@deprecated("open with a 'style' argument is deprecated") +proc open(path:string, mode:ioMode, hints=ioHintSet.empty, + style:iostyle): file throws { + return openHelper(path, mode, hints, style:iostyleInternal); +} + private proc openHelper(path:string, mode:ioMode, hints=ioHintSet.empty, style:iostyleInternal = defaultIOStyleInternal()): file throws { @@ -8686,6 +8686,11 @@ proc fileWriter.writeBinary(b: bytes, size: int = b.size) throws { } } +@chpldoc.nodoc +private proc isSuitableForBinaryReadWrite(arr: _array) param { + return chpl__isDROrDRView(arr); +} + /* Write an array of binary numbers to a ``fileWriter`` @@ -8703,44 +8708,39 @@ proc fileWriter.writeBinary(b: bytes, size: int = b.size) throws { due to a :ref:`system error`. */ proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioendian.native) throws - where data.rank == 1 && data.isRectangular() && data.strides == strideKind.one && ( + where isSuitableForBinaryReadWrite(data) && data.strides == strideKind.one && ( isIntegralType(t) || isRealType(t) || isImagType(t) || isComplexType(t) ) { var e : errorCode = 0; + if endian != ioendian.native then + compilerError("writeBinary() currently only supports 'ioendian.native'"); + on this._home { try this.lock(); defer { this.unlock(); } const tSize = c_sizeof(t) : c_ssize_t; // Allow either DefaultRectangular arrays or dense slices of DR arrays - const denseDR = chpl__isDROrDRView(data) && - data._value.isDataContiguous(d._value); - if endian == ioendian.native && data.locale == this._home && denseDR { + if !data._value.isDataContiguous(d._value) { + throw new IllegalArgumentError("array data must be contiguous"); + } else if data.locale != this._home { + throw new IllegalArgumentError("array data must be on same locale as 'fileReader'"); + } else { e = try qio_channel_write_amt(false, this._channel_internal, data[d.low], data.size:c_ssize_t * tSize); if e != 0 then throw createSystemOrChplError(e); - } else { - for b in data { - select (endian) { - when ioendian.native { - e = try _write_binary_internal(this._channel_internal, _iokind.native, b); - } - when ioendian.big { - e = try _write_binary_internal(this._channel_internal, _iokind.big, b); - } - when ioendian.little { - e = try _write_binary_internal(this._channel_internal, _iokind.little, b); - } - } - - if e != 0 then - throw createSystemOrChplError(e); - } } } } + +@chpldoc.nodoc +proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioendian.native) throws { + compilerError("writeBinary() does not currently support this type of array"); +} + + /* Write an array of binary numbers to a ``fileWriter`` @@ -8757,7 +8757,7 @@ proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioe due to a :ref:`system error`. */ proc fileWriter.writeBinary(const ref data: [] ?t, endian:ioendian) throws - where data.rank == 1 && data.isRectangular() && data.strides == strideKind.one && ( + where isSuitableForBinaryReadWrite(data) && data.strides == strideKind.one && ( isIntegralType(t) || isRealType(t) || isImagType(t) || isComplexType(t) ) { select (endian) { @@ -8773,6 +8773,12 @@ proc fileWriter.writeBinary(const ref data: [] ?t, endian:ioendian) throws } } +@chpldoc.nodoc +proc fileWriter.writeBinary(const ref data: [] ?t, endian:ioendian) throws +{ + compilerError("writeBinary() does not currently support this type of array"); +} + /* Read a binary number from the ``fileReader`` @@ -8967,7 +8973,7 @@ config param ReadBinaryArrayReturnInt = false; @deprecated(notes="The variant of `readBinary(data: [])` that returns a `bool` is deprecated; please recompile with `-sReadBinaryArrayReturnInt=true` to use the new variant") proc fileReader.readBinary(ref data: [] ?t, param endian = ioendian.native): bool throws where ReadBinaryArrayReturnInt == false && - data.rank == 1 && data.isRectangular() && data.strides == strideKind.one && ( + isSuitableForBinaryReadWrite(data) && data.strides == strideKind.one && ( isIntegralType(t) || isRealType(t) || isImagType(t) || isComplexType(t) ) { var e : errorCode = 0, @@ -9027,44 +9033,26 @@ proc fileReader.readBinary(ref data: [] ?t, param endian = ioendian.native): boo */ proc fileReader.readBinary(ref data: [?d] ?t, param endian = ioendian.native): int throws where ReadBinaryArrayReturnInt == true && - data.rank == 1 && data.isRectangular() && data.strides == strideKind.one && ( + isSuitableForBinaryReadWrite(data) && data.strides == strideKind.one && ( isIntegralType(t) || isRealType(t) || isImagType(t) || isComplexType(t) ) { var e : errorCode = 0, numRead : c_ssize_t = 0; + if endian != ioendian.native then + compilerError("readBinary() currently only supports 'ioendian.native'"); + on this._home { try this.lock(); defer { this.unlock(); } - // Allow either DefaultRectangular arrays or dense slices of DR arrays - const denseDR = chpl__isDROrDRView(data) && - data._value.isDataContiguous(d._value); - if data.locale == this._home && denseDR && endian == ioendian.native { + if !data._value.isDataContiguous(d._value) { + throw new IllegalArgumentError("array data must be contiguous"); + } else if data.locale != this._home { + throw new IllegalArgumentError("array data must be on same locale as 'fileReader'"); + } else { e = qio_channel_read(false, this._channel_internal, data[d.low], (data.size * c_sizeof(data.eltType)) : c_ssize_t, numRead); if e != 0 && e != EEOF then throw createSystemOrChplError(e); - } else { - for (i, b) in zip(data.domain, data) { - select (endian) { - when ioendian.native { - e = try _read_binary_internal(this._channel_internal, _iokind.native, b); - } - when ioendian.big { - e = try _read_binary_internal(this._channel_internal, _iokind.big, b); - } - when ioendian.little { - e = try _read_binary_internal(this._channel_internal, _iokind.little, b); - } - } - - if e == EEOF { - break; - } else if e != 0 { - throw createSystemOrChplError(e); - } else { - numRead += 1; - } - } } } @@ -9094,7 +9082,7 @@ proc fileReader.readBinary(ref data: [?d] ?t, param endian = ioendian.native): i @deprecated(notes="The variant of `readBinary(data: [])` that returns a `bool` is deprecated; please recompile with `-sReadBinaryArrayReturnInt=true` to use the new variant") proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):bool throws where ReadBinaryArrayReturnInt == false && - data.rank == 1 && data.isRectangular() && data.strides == strideKind.one && ( + isSuitableForBinaryReadWrite(data) && data.strides == strideKind.one && ( isIntegralType(t) || isRealType(t) || isImagType(t) || isComplexType(t) ) { var rv: bool = false; @@ -9134,7 +9122,7 @@ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):bool throws */ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):int throws where ReadBinaryArrayReturnInt == true && - data.rank == 1 && data.isRectangular() && data.strides == strideKind.one && ( + isSuitableForBinaryReadWrite(data) && data.strides == strideKind.one && ( isIntegralType(t) || isRealType(t) || isImagType(t) || isComplexType(t) ) { var nr: int = 0; @@ -9154,6 +9142,19 @@ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):int throws return nr; } +@chpldoc.nodoc +proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):int throws +{ + compilerError("readBinary() does not currently support this type of array"); +} + +@chpldoc.nodoc +proc fileReader.readBinary(ref data: [] ?t, param endian = ioendian.native): bool throws +{ + compilerError("readBinary() does not currently support this type of array"); +} + + /* Read up to ``maxBytes`` bytes from a ``fileReader`` into a :class:`~CTypes.c_ptr` diff --git a/test/library/standard/IO/writeBinary/readWriteMultidimArray-block.good b/test/library/standard/IO/writeBinary/readWriteMultidimArray-block.good new file mode 100644 index 000000000000..1d06bdcb1216 --- /dev/null +++ b/test/library/standard/IO/writeBinary/readWriteMultidimArray-block.good @@ -0,0 +1 @@ +readWriteMultidimArray.chpl:13: error: writeBinary() does not currently support this type of array diff --git a/test/library/standard/IO/writeBinary/readWriteMultidimArray.chpl b/test/library/standard/IO/writeBinary/readWriteMultidimArray.chpl new file mode 100644 index 000000000000..35878cc636c1 --- /dev/null +++ b/test/library/standard/IO/writeBinary/readWriteMultidimArray.chpl @@ -0,0 +1,27 @@ +use IO, BlockDist; + +config param useBlock = false; + +var Dom = if useBlock then blockDist.createDomain({1..10, 1..10}) + else {1..10, 1..10}; + +var A: [Dom] real = [(i,j) in Dom] i * 100 + j/100.0; + +writeln(A); + +var outfile = open("out.bin", ioMode.cw); +outfile.writer().writeBinary(A); +outfile.close(); + +var B: [Dom] real; + +var infile = open("out.bin", ioMode.r); +infile.reader().readBinary(B); +infile.close(); + +writeln(B); + +if + reduce (A == B) != Dom.size then + halt("Mismatch between A and B"); +else + writeln("Matched!"); diff --git a/test/library/standard/IO/writeBinary/readWriteMultidimArray.compopts b/test/library/standard/IO/writeBinary/readWriteMultidimArray.compopts new file mode 100644 index 000000000000..d587385b0c9a --- /dev/null +++ b/test/library/standard/IO/writeBinary/readWriteMultidimArray.compopts @@ -0,0 +1,2 @@ +-sReadBinaryArrayReturnInt=true +-sReadBinaryArrayReturnInt=true -suseBlock=true # readWriteMultidimArray-block.good diff --git a/test/library/standard/IO/writeBinary/readWriteMultidimArray.good b/test/library/standard/IO/writeBinary/readWriteMultidimArray.good new file mode 100644 index 000000000000..a8d4eb1b3a70 --- /dev/null +++ b/test/library/standard/IO/writeBinary/readWriteMultidimArray.good @@ -0,0 +1,21 @@ +100.01 100.02 100.03 100.04 100.05 100.06 100.07 100.08 100.09 100.1 +200.01 200.02 200.03 200.04 200.05 200.06 200.07 200.08 200.09 200.1 +300.01 300.02 300.03 300.04 300.05 300.06 300.07 300.08 300.09 300.1 +400.01 400.02 400.03 400.04 400.05 400.06 400.07 400.08 400.09 400.1 +500.01 500.02 500.03 500.04 500.05 500.06 500.07 500.08 500.09 500.1 +600.01 600.02 600.03 600.04 600.05 600.06 600.07 600.08 600.09 600.1 +700.01 700.02 700.03 700.04 700.05 700.06 700.07 700.08 700.09 700.1 +800.01 800.02 800.03 800.04 800.05 800.06 800.07 800.08 800.09 800.1 +900.01 900.02 900.03 900.04 900.05 900.06 900.07 900.08 900.09 900.1 +1000.01 1000.02 1000.03 1000.04 1000.05 1000.06 1000.07 1000.08 1000.09 1000.1 +100.01 100.02 100.03 100.04 100.05 100.06 100.07 100.08 100.09 100.1 +200.01 200.02 200.03 200.04 200.05 200.06 200.07 200.08 200.09 200.1 +300.01 300.02 300.03 300.04 300.05 300.06 300.07 300.08 300.09 300.1 +400.01 400.02 400.03 400.04 400.05 400.06 400.07 400.08 400.09 400.1 +500.01 500.02 500.03 500.04 500.05 500.06 500.07 500.08 500.09 500.1 +600.01 600.02 600.03 600.04 600.05 600.06 600.07 600.08 600.09 600.1 +700.01 700.02 700.03 700.04 700.05 700.06 700.07 700.08 700.09 700.1 +800.01 800.02 800.03 800.04 800.05 800.06 800.07 800.08 800.09 800.1 +900.01 900.02 900.03 900.04 900.05 900.06 900.07 900.08 900.09 900.1 +1000.01 1000.02 1000.03 1000.04 1000.05 1000.06 1000.07 1000.08 1000.09 1000.1 +Matched! From 0a9b1fd80a93ee64c857bfaa144dc1cd92543cb3 Mon Sep 17 00:00:00 2001 From: Brad Chamberlain Date: Wed, 13 Sep 2023 19:18:52 -0700 Subject: [PATCH 02/27] Add a proposed array-of-T to T cast --- Signed-off-by: Brad Chamberlain --- modules/internal/ChapelArray.chpl | 5 +++++ modules/internal/ChapelBase.chpl | 2 -- test/expressions/casts/promotedArraySelfCast.chpl | 7 +++++++ test/expressions/casts/promotedArraySelfCast.good | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 test/expressions/casts/promotedArraySelfCast.chpl create mode 100644 test/expressions/casts/promotedArraySelfCast.good diff --git a/modules/internal/ChapelArray.chpl b/modules/internal/ChapelArray.chpl index 7402ff7107f5..e4e9001347d5 100644 --- a/modules/internal/ChapelArray.chpl +++ b/modules/internal/ChapelArray.chpl @@ -2232,6 +2232,11 @@ module ChapelArray { return try! "%?".format(x); } + @chpldoc.nodoc + operator :(in x: [] ?et, type t: et) where t == et { + return x; + } + pragma "fn returns aliasing array" @chpldoc.nodoc operator #(arr: [], counts: integral) { diff --git a/modules/internal/ChapelBase.chpl b/modules/internal/ChapelBase.chpl index d54dbb516d5e..5ba20178664c 100644 --- a/modules/internal/ChapelBase.chpl +++ b/modules/internal/ChapelBase.chpl @@ -2168,8 +2168,6 @@ module ChapelBase { isIntegralType(t) || isRealType(t); - inline operator :(x:bool, type t:integral) do - return __primitive("cast", t, x); inline operator :(x:bool, type t:chpl_anyreal) do return __primitive("cast", t, x); diff --git a/test/expressions/casts/promotedArraySelfCast.chpl b/test/expressions/casts/promotedArraySelfCast.chpl new file mode 100644 index 000000000000..e9f249b3167e --- /dev/null +++ b/test/expressions/casts/promotedArraySelfCast.chpl @@ -0,0 +1,7 @@ +record R { var x: int; } + +var A = [new R(45), new R(33)]; + +var B = A: R; + +writeln(B); diff --git a/test/expressions/casts/promotedArraySelfCast.good b/test/expressions/casts/promotedArraySelfCast.good new file mode 100644 index 000000000000..c9bf18134d32 --- /dev/null +++ b/test/expressions/casts/promotedArraySelfCast.good @@ -0,0 +1 @@ +(x = 45) (x = 33) From 4760a2fd7c22f6be1a2edd2dc843cc0f17a2d0be Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 14 Sep 2023 09:39:13 -0700 Subject: [PATCH 03/27] Remove guaranteed compiler errors and tweak error messages Signed-off-by: Danila Fedorin --- modules/standard/IO.chpl | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/modules/standard/IO.chpl b/modules/standard/IO.chpl index a88f09468ba7..cfecab37a49a 100644 --- a/modules/standard/IO.chpl +++ b/modules/standard/IO.chpl @@ -8722,9 +8722,9 @@ proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioe // Allow either DefaultRectangular arrays or dense slices of DR arrays if !data._value.isDataContiguous(d._value) { - throw new IllegalArgumentError("array data must be contiguous"); + throw new IllegalArgumentError("writeBinary() array data must be contiguous"); } else if data.locale != this._home { - throw new IllegalArgumentError("array data must be on same locale as 'fileReader'"); + throw new IllegalArgumentError("writeBinary() array data must be on same locale as 'fileWriter'"); } else { e = try qio_channel_write_amt(false, this._channel_internal, data[d.low], data.size:c_ssize_t * tSize); @@ -8737,7 +8737,7 @@ proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioe @chpldoc.nodoc proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioendian.native) throws { - compilerError("writeBinary() does not currently support this type of array"); + compilerError("writeBinary() only supports local, rectangular, non-strided arrays"); } @@ -8764,11 +8764,8 @@ proc fileWriter.writeBinary(const ref data: [] ?t, endian:ioendian) throws when ioendian.native { this.writeBinary(data, ioendian.native); } - when ioendian.big { - this.writeBinary(data, ioendian.big); - } - when ioendian.little { - this.writeBinary(data, ioendian.little); + otherwise { + throw new IllegalArgumentError("writeBinary() currently only supports 'ioendian.native'"); } } } @@ -8776,7 +8773,7 @@ proc fileWriter.writeBinary(const ref data: [] ?t, endian:ioendian) throws @chpldoc.nodoc proc fileWriter.writeBinary(const ref data: [] ?t, endian:ioendian) throws { - compilerError("writeBinary() does not currently support this type of array"); + compilerError("writeBinary() only supports local, rectangular, non-strided arrays"); } /* @@ -9046,9 +9043,9 @@ proc fileReader.readBinary(ref data: [?d] ?t, param endian = ioendian.native): i try this.lock(); defer { this.unlock(); } if !data._value.isDataContiguous(d._value) { - throw new IllegalArgumentError("array data must be contiguous"); + throw new IllegalArgumentError("readBinary() array data must be contiguous"); } else if data.locale != this._home { - throw new IllegalArgumentError("array data must be on same locale as 'fileReader'"); + throw new IllegalArgumentError("readBinary() array data must be on same locale as 'fileReader'"); } else { e = qio_channel_read(false, this._channel_internal, data[d.low], (data.size * c_sizeof(data.eltType)) : c_ssize_t, numRead); @@ -9091,11 +9088,8 @@ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):bool throws when ioendian.native { rv = this.readBinary(data, ioendian.native); } - when ioendian.big { - rv = this.readBinary(data, ioendian.big); - } - when ioendian.little { - rv = this.readBinary(data, ioendian.little); + otherwise { + throw new IllegalArgumentError("readBinary() currently only supports 'ioendian.native'"); } } @@ -9131,11 +9125,8 @@ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):int throws when ioendian.native { nr = this.readBinary(data, ioendian.native); } - when ioendian.big { - nr = this.readBinary(data, ioendian.big); - } - when ioendian.little { - nr = this.readBinary(data, ioendian.little); + otherwise { + throw new IllegalArgumentError("readBinary() currently only supports 'ioendian.native'"); } } @@ -9145,13 +9136,13 @@ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):int throws @chpldoc.nodoc proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):int throws { - compilerError("readBinary() does not currently support this type of array"); + compilerError("readBinary() only supports local, rectangular, non-strided arrays"); } @chpldoc.nodoc proc fileReader.readBinary(ref data: [] ?t, param endian = ioendian.native): bool throws { - compilerError("readBinary() does not currently support this type of array"); + compilerError("readBinary() only supports local, rectangular, non-strided arrays"); } From 49f3ce5eb24292ad390862d7d3580e7d884ccf24 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 14 Sep 2023 10:17:00 -0700 Subject: [PATCH 04/27] Adjust error .good file with changes to error wording Signed-off-by: Danila Fedorin --- .../standard/IO/writeBinary/readWriteMultidimArray-block.good | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/library/standard/IO/writeBinary/readWriteMultidimArray-block.good b/test/library/standard/IO/writeBinary/readWriteMultidimArray-block.good index 1d06bdcb1216..0ead49fa3d38 100644 --- a/test/library/standard/IO/writeBinary/readWriteMultidimArray-block.good +++ b/test/library/standard/IO/writeBinary/readWriteMultidimArray-block.good @@ -1 +1 @@ -readWriteMultidimArray.chpl:13: error: writeBinary() does not currently support this type of array +readWriteMultidimArray.chpl:13: error: writeBinary() only supports local, rectangular, non-strided arrays From fd46b0d3b73afaaf3c54b1d3f4046c0b93a97675 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 14 Sep 2023 11:42:22 -0700 Subject: [PATCH 05/27] Restore 'slow loop' for array serialization Signed-off-by: Danila Fedorin --- modules/standard/IO.chpl | 70 ++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/modules/standard/IO.chpl b/modules/standard/IO.chpl index cfecab37a49a..f0ef8b5c6cf3 100644 --- a/modules/standard/IO.chpl +++ b/modules/standard/IO.chpl @@ -8713,9 +8713,6 @@ proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioe { var e : errorCode = 0; - if endian != ioendian.native then - compilerError("writeBinary() currently only supports 'ioendian.native'"); - on this._home { try this.lock(); defer { this.unlock(); } const tSize = c_sizeof(t) : c_ssize_t; @@ -8725,11 +8722,28 @@ proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioe throw new IllegalArgumentError("writeBinary() array data must be contiguous"); } else if data.locale != this._home { throw new IllegalArgumentError("writeBinary() array data must be on same locale as 'fileWriter'"); - } else { + } else if endian == ioendian.native { e = try qio_channel_write_amt(false, this._channel_internal, data[d.low], data.size:c_ssize_t * tSize); if e != 0 then throw createSystemOrChplError(e); + } else { + for b in data { + select (endian) { + when ioendian.native { + compilerError("unreachable"); + } + when ioendian.big { + e = try _write_binary_internal(this._channel_internal, _iokind.big, b); + } + when ioendian.little { + e = try _write_binary_internal(this._channel_internal, _iokind.little, b); + } + } + + if e != 0 then + throw createSystemOrChplError(e); + } } } } @@ -8764,8 +8778,11 @@ proc fileWriter.writeBinary(const ref data: [] ?t, endian:ioendian) throws when ioendian.native { this.writeBinary(data, ioendian.native); } - otherwise { - throw new IllegalArgumentError("writeBinary() currently only supports 'ioendian.native'"); + when ioendian.native { + this.writeBinary(data, ioendian.big); + } + when ioendian.native { + this.writeBinary(data, ioendian.little); } } } @@ -9036,9 +9053,6 @@ proc fileReader.readBinary(ref data: [?d] ?t, param endian = ioendian.native): i var e : errorCode = 0, numRead : c_ssize_t = 0; - if endian != ioendian.native then - compilerError("readBinary() currently only supports 'ioendian.native'"); - on this._home { try this.lock(); defer { this.unlock(); } @@ -9046,10 +9060,32 @@ proc fileReader.readBinary(ref data: [?d] ?t, param endian = ioendian.native): i throw new IllegalArgumentError("readBinary() array data must be contiguous"); } else if data.locale != this._home { throw new IllegalArgumentError("readBinary() array data must be on same locale as 'fileReader'"); - } else { + } else if endian == ioendian.native { e = qio_channel_read(false, this._channel_internal, data[d.low], (data.size * c_sizeof(data.eltType)) : c_ssize_t, numRead); if e != 0 && e != EEOF then throw createSystemOrChplError(e); + } else { + for (i, b) in zip(data.domain, data) { + select (endian) { + when ioendian.native { + compilerError("unreachable"); + } + when ioendian.big { + e = try _read_binary_internal(this._channel_internal, _iokind.big, b); + } + when ioendian.little { + e = try _read_binary_internal(this._channel_internal, _iokind.little, b); + } + } + + if e == EEOF { + break; + } else if e != 0 { + throw createSystemOrChplError(e); + } else { + numRead += 1; + } + } } } @@ -9088,8 +9124,11 @@ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):bool throws when ioendian.native { rv = this.readBinary(data, ioendian.native); } - otherwise { - throw new IllegalArgumentError("readBinary() currently only supports 'ioendian.native'"); + when ioendian.big { + rv = this.readBinary(data, ioendian.big); + } + when ioendian.little { + rv = this.readBinary(data, ioendian.little); } } @@ -9125,8 +9164,11 @@ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):int throws when ioendian.native { nr = this.readBinary(data, ioendian.native); } - otherwise { - throw new IllegalArgumentError("readBinary() currently only supports 'ioendian.native'"); + when ioendian.big { + nr = this.readBinary(data, ioendian.big); + } + when ioendian.little { + nr = this.readBinary(data, ioendian.little); } } From f48c05a06999cebb24e34034f9f01cb59f88e8a4 Mon Sep 17 00:00:00 2001 From: Brad Chamberlain Date: Thu, 14 Sep 2023 11:55:13 -0700 Subject: [PATCH 06/27] Mark new overload "last resort" as Anna suggested --- Signed-off-by: Brad Chamberlain --- modules/internal/ChapelArray.chpl | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/internal/ChapelArray.chpl b/modules/internal/ChapelArray.chpl index e2fe678e6259..3794e01ba34f 100644 --- a/modules/internal/ChapelArray.chpl +++ b/modules/internal/ChapelArray.chpl @@ -2215,6 +2215,7 @@ module ChapelArray { return try! "%?".format(x); } + pragma "last resort" @chpldoc.nodoc operator :(in x: [] ?et, type t: et) where t == et { return x; From 4e80aba51ed529a668abc7f47f416d3d99bfd2a1 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 14 Sep 2023 12:18:47 -0700 Subject: [PATCH 07/27] Add tests of read/writeBinary for slices of a two-dimensional array Signed-off-by: Danila Fedorin --- .../writeBinary/multiDimArraySlices-col.good | 13 ++++++++ .../writeBinary/multiDimArraySlices-row.good | 21 +++++++++++++ .../IO/writeBinary/multiDimArraySlices.chpl | 30 +++++++++++++++++++ .../writeBinary/multiDimArraySlices.compopts | 1 + .../writeBinary/multiDimArraySlices.execopts | 2 ++ 5 files changed, 67 insertions(+) create mode 100644 test/library/standard/IO/writeBinary/multiDimArraySlices-col.good create mode 100644 test/library/standard/IO/writeBinary/multiDimArraySlices-row.good create mode 100644 test/library/standard/IO/writeBinary/multiDimArraySlices.chpl create mode 100644 test/library/standard/IO/writeBinary/multiDimArraySlices.compopts create mode 100644 test/library/standard/IO/writeBinary/multiDimArraySlices.execopts diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices-col.good b/test/library/standard/IO/writeBinary/multiDimArraySlices-col.good new file mode 100644 index 000000000000..81f261083377 --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices-col.good @@ -0,0 +1,13 @@ +100.01 100.02 100.03 100.04 100.05 100.06 100.07 100.08 100.09 100.1 +200.01 200.02 200.03 200.04 200.05 200.06 200.07 200.08 200.09 200.1 +300.01 300.02 300.03 300.04 300.05 300.06 300.07 300.08 300.09 300.1 +400.01 400.02 400.03 400.04 400.05 400.06 400.07 400.08 400.09 400.1 +500.01 500.02 500.03 500.04 500.05 500.06 500.07 500.08 500.09 500.1 +600.01 600.02 600.03 600.04 600.05 600.06 600.07 600.08 600.09 600.1 +700.01 700.02 700.03 700.04 700.05 700.06 700.07 700.08 700.09 700.1 +800.01 800.02 800.03 800.04 800.05 800.06 800.07 800.08 800.09 800.1 +900.01 900.02 900.03 900.04 900.05 900.06 900.07 900.08 900.09 900.1 +1000.01 1000.02 1000.03 1000.04 1000.05 1000.06 1000.07 1000.08 1000.09 1000.1 +uncaught IllegalArgumentError: writeBinary() array data must be contiguous + multiDimArraySlices.chpl:13: thrown here + multiDimArraySlices.chpl:13: uncaught here diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices-row.good b/test/library/standard/IO/writeBinary/multiDimArraySlices-row.good new file mode 100644 index 000000000000..c0221128965c --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices-row.good @@ -0,0 +1,21 @@ +100.01 100.02 100.03 100.04 100.05 100.06 100.07 100.08 100.09 100.1 +200.01 200.02 200.03 200.04 200.05 200.06 200.07 200.08 200.09 200.1 +300.01 300.02 300.03 300.04 300.05 300.06 300.07 300.08 300.09 300.1 +400.01 400.02 400.03 400.04 400.05 400.06 400.07 400.08 400.09 400.1 +500.01 500.02 500.03 500.04 500.05 500.06 500.07 500.08 500.09 500.1 +600.01 600.02 600.03 600.04 600.05 600.06 600.07 600.08 600.09 600.1 +700.01 700.02 700.03 700.04 700.05 700.06 700.07 700.08 700.09 700.1 +800.01 800.02 800.03 800.04 800.05 800.06 800.07 800.08 800.09 800.1 +900.01 900.02 900.03 900.04 900.05 900.06 900.07 900.08 900.09 900.1 +1000.01 1000.02 1000.03 1000.04 1000.05 1000.06 1000.07 1000.08 1000.09 1000.1 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +200.01 200.02 200.03 200.04 200.05 200.06 200.07 200.08 200.09 200.1 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +Matched! diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices.chpl b/test/library/standard/IO/writeBinary/multiDimArraySlices.chpl new file mode 100644 index 000000000000..b41a4883e4e6 --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices.chpl @@ -0,0 +1,30 @@ +use IO; + +config const useCol = false; + +var Dom = {1..10, 1..10}; +var Slice = if useCol then {1..10, 2..2} else {2..2, 1..10}; + +var A: [Dom] real = [(i,j) in Dom] i * 100 + j/100.0; + +writeln(A); + +var outfile = open("out.bin", ioMode.cw); +outfile.writer().writeBinary(A[Slice]); +outfile.close(); + +var B: [Dom] real; + +var infile = open("out.bin", ioMode.r); +infile.reader().readBinary(B[Slice]); +infile.close(); + +writeln(B); + +const equalIn = + reduce (A[Slice] == B[Slice]); +const equalZero = + reduce (B == 0); + +if equalIn + equalZero != Dom.size then + halt("Mismatch between A and B"); +else + writeln("Matched!"); diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices.compopts b/test/library/standard/IO/writeBinary/multiDimArraySlices.compopts new file mode 100644 index 000000000000..9bd1b424a7d8 --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices.compopts @@ -0,0 +1 @@ +-sReadBinaryArrayReturnInt=true diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices.execopts b/test/library/standard/IO/writeBinary/multiDimArraySlices.execopts new file mode 100644 index 000000000000..0974b2a558d9 --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices.execopts @@ -0,0 +1,2 @@ + # multiDimArraySlices-row.good +--useCol=true # multiDimArraySlices-col.good From 3446396bd9cea54bc38ce475f7ca1f10bdac454f Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 14 Sep 2023 12:27:33 -0700 Subject: [PATCH 08/27] Extend testing to block-distributed arrays Signed-off-by: Danila Fedorin --- .../standard/IO/writeBinary/multiDimArraySlices-block.good | 1 + .../standard/IO/writeBinary/multiDimArraySlices-col.good | 4 ++-- .../standard/IO/writeBinary/multiDimArraySlices.chpl | 6 ++++-- .../standard/IO/writeBinary/multiDimArraySlices.compopts | 1 + 4 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 test/library/standard/IO/writeBinary/multiDimArraySlices-block.good diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices-block.good b/test/library/standard/IO/writeBinary/multiDimArraySlices-block.good new file mode 100644 index 000000000000..c6bbba4d9e46 --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices-block.good @@ -0,0 +1 @@ +multiDimArraySlices.chpl:15: error: writeBinary() only supports local, rectangular, non-strided arrays diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices-col.good b/test/library/standard/IO/writeBinary/multiDimArraySlices-col.good index 81f261083377..1f5e43974a14 100644 --- a/test/library/standard/IO/writeBinary/multiDimArraySlices-col.good +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices-col.good @@ -9,5 +9,5 @@ 900.01 900.02 900.03 900.04 900.05 900.06 900.07 900.08 900.09 900.1 1000.01 1000.02 1000.03 1000.04 1000.05 1000.06 1000.07 1000.08 1000.09 1000.1 uncaught IllegalArgumentError: writeBinary() array data must be contiguous - multiDimArraySlices.chpl:13: thrown here - multiDimArraySlices.chpl:13: uncaught here + multiDimArraySlices.chpl:15: thrown here + multiDimArraySlices.chpl:15: uncaught here diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices.chpl b/test/library/standard/IO/writeBinary/multiDimArraySlices.chpl index b41a4883e4e6..7d2e5149a7ca 100644 --- a/test/library/standard/IO/writeBinary/multiDimArraySlices.chpl +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices.chpl @@ -1,8 +1,10 @@ -use IO; +use IO, BlockDist; config const useCol = false; +config param useBlock = false; -var Dom = {1..10, 1..10}; +var Dom = if useBlock then blockDist.createDomain({1..10, 1..10}) + else {1..10, 1..10}; var Slice = if useCol then {1..10, 2..2} else {2..2, 1..10}; var A: [Dom] real = [(i,j) in Dom] i * 100 + j/100.0; diff --git a/test/library/standard/IO/writeBinary/multiDimArraySlices.compopts b/test/library/standard/IO/writeBinary/multiDimArraySlices.compopts index 9bd1b424a7d8..3ef106c10abf 100644 --- a/test/library/standard/IO/writeBinary/multiDimArraySlices.compopts +++ b/test/library/standard/IO/writeBinary/multiDimArraySlices.compopts @@ -1 +1,2 @@ -sReadBinaryArrayReturnInt=true +-sReadBinaryArrayReturnInt=true -suseBlock=true # multiDimArraySlices-block.good From 9a6db39cfb75cf3deba6d60d2f9a6dcb2591b4b6 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 14 Sep 2023 12:31:20 -0700 Subject: [PATCH 09/27] Adjust docs to allow multi-dim arrays, but disallow non-local ones Signed-off-by: Danila Fedorin --- modules/standard/IO.chpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/standard/IO.chpl b/modules/standard/IO.chpl index f0ef8b5c6cf3..9a4626ae5ea1 100644 --- a/modules/standard/IO.chpl +++ b/modules/standard/IO.chpl @@ -8694,7 +8694,7 @@ private proc isSuitableForBinaryReadWrite(arr: _array) param { /* Write an array of binary numbers to a ``fileWriter`` - Note that this routine currently requires a 1D rectangular non-strided array. + Note that this routine currently requires a local rectangular non-strided array. :arg data: an array of numbers to write to the fileWriter :arg endian: :type:`ioendian` compile-time argument that specifies the byte @@ -8758,7 +8758,7 @@ proc fileWriter.writeBinary(const ref data: [?d] ?t, param endian:ioendian = ioe /* Write an array of binary numbers to a ``fileWriter`` - Note that this routine currently requires a 1D rectangular non-strided array. + Note that this routine currently requires a local rectangular non-strided array. :arg data: an array of numbers to write to the fileWriter :arg endian: :type:`ioendian` specifies the byte order in which @@ -9099,7 +9099,7 @@ proc fileReader.readBinary(ref data: [?d] ?t, param endian = ioendian.native): i until ``data`` is full or EOF is reached. An :class:`~OS.UnexpectedEofError` is thrown if EOF is reached before the array is filled. - Note that this routine currently requires a 1D rectangular non-strided array. + Note that this routine currently requires a local rectangular non-strided array. :arg data: an array to read into – existing values are overwritten. :arg endian: :type:`ioendian` specifies the byte order in which @@ -9141,7 +9141,7 @@ proc fileReader.readBinary(ref data: [] ?t, endian: ioendian):bool throws Binary values of the type ``data.eltType`` are consumed from the fileReader until ``data`` is full or EOF is reached. - Note that this routine currently requires a 1D rectangular non-strided array. + Note that this routine currently requires a local rectangular non-strided array. :arg data: an array to read into – existing values are overwritten. :arg endian: :type:`ioendian` specifies the byte order in which From 6bfbb6096fbfff156f7b93edd62ea61d50ede2d5 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 14 Sep 2023 16:12:19 -0700 Subject: [PATCH 10/27] Update mandelbrot tests with writeBinary for 2D arrays. Signed-off-by: Danila Fedorin --- .../examples/benchmarks/shootout/mandelbrot-fast.chpl | 5 ++--- test/release/examples/benchmarks/shootout/mandelbrot.chpl | 5 ++--- test/studies/shootout/mandelbrot/bradc/mandelbrot-blc.chpl | 4 ++-- .../shootout/mandelbrot/ferguson/mandelbrot-opt2.chpl | 5 ++--- .../shootout/mandelbrot/ferguson/mandelbrot-tricks.chpl | 4 ++-- .../shootout/mandelbrot/lydia/mandelbrot-unzipped.chpl | 4 ++-- 6 files changed, 12 insertions(+), 15 deletions(-) diff --git a/test/release/examples/benchmarks/shootout/mandelbrot-fast.chpl b/test/release/examples/benchmarks/shootout/mandelbrot-fast.chpl index 9d987f9dab29..2b81cc04e7dc 100644 --- a/test/release/examples/benchmarks/shootout/mandelbrot-fast.chpl +++ b/test/release/examples/benchmarks/shootout/mandelbrot-fast.chpl @@ -60,13 +60,12 @@ proc main() { } // Get a lock-free, binary fileWriter on 'stdout' - var w = (new file(1)).writer(locking=false, - serializer=new binarySerializer()); + var w = (new file(1)).writer(locking=false); // Write the file header and the image array. w.writef("P4\n"); w.writef("%i %i\n", n, n); - w.write(image); + w.writeBinary(image); } // diff --git a/test/release/examples/benchmarks/shootout/mandelbrot.chpl b/test/release/examples/benchmarks/shootout/mandelbrot.chpl index 35870247190b..ff3ad95bc9f3 100644 --- a/test/release/examples/benchmarks/shootout/mandelbrot.chpl +++ b/test/release/examples/benchmarks/shootout/mandelbrot.chpl @@ -55,10 +55,9 @@ proc main() { // Get a lock-free, binary fileWriter on 'stdout', write the file header, // and the image array. // - var w = (new file(1)).writer(locking=false, - serializer=new binarySerializer()); + var w = (new file(1)).writer(locking=false); w.writef("P4\n"); w.writef("%i %i\n", n, n); - w.write(image); + w.writeBinary(image); } diff --git a/test/studies/shootout/mandelbrot/bradc/mandelbrot-blc.chpl b/test/studies/shootout/mandelbrot/bradc/mandelbrot-blc.chpl index d0b115dc241d..59ed271a4bcf 100644 --- a/test/studies/shootout/mandelbrot/bradc/mandelbrot-blc.chpl +++ b/test/studies/shootout/mandelbrot/bradc/mandelbrot-blc.chpl @@ -60,12 +60,12 @@ proc main() { } // Get a lock-free writer channel on 'stdout' - var w = (new file(1)).writer(serializer=new binarySerializer(), locking=false); + var w = (new file(1)).writer(locking=false); // Write the file header and the image array. w.writef("P4\n"); w.writef("%i %i\n", n, n); - w.write(image); + w.writeBinary(image); } // diff --git a/test/studies/shootout/mandelbrot/ferguson/mandelbrot-opt2.chpl b/test/studies/shootout/mandelbrot/ferguson/mandelbrot-opt2.chpl index f794bf8acffe..8b5ae37f1c62 100644 --- a/test/studies/shootout/mandelbrot/ferguson/mandelbrot-opt2.chpl +++ b/test/studies/shootout/mandelbrot/ferguson/mandelbrot-opt2.chpl @@ -27,8 +27,7 @@ proc main() { var c_im:real = (upper.im - lower.im) * ipart / size + lower.im; var start = datastart+ipart*cols; var end = datastart+(ipart+1)*cols; - var writer = f.writer(serializer=new binarySerializer(), locking=false, - region=start..#end); + var writer = f.writer(locking=false, region=start..#end); for rstart in 0..#cols { @@ -56,7 +55,7 @@ proc main() { if mask == 0 then break; } - writer.write(mask:uint(8)); + writer.writeBinary(mask:uint(8)); } writer.close(); diff --git a/test/studies/shootout/mandelbrot/ferguson/mandelbrot-tricks.chpl b/test/studies/shootout/mandelbrot/ferguson/mandelbrot-tricks.chpl index c0488e03b734..56bda54bc44f 100644 --- a/test/studies/shootout/mandelbrot/ferguson/mandelbrot-tricks.chpl +++ b/test/studies/shootout/mandelbrot/ferguson/mandelbrot-tricks.chpl @@ -71,12 +71,12 @@ proc main() { } // Get a lock-free writer channel on 'stdout' - var w = (new file(1)).writer(serializer=new binarySerializer(), locking=false); + var w = (new file(1)).writer(locking=false); // Write the file header and the image array. w.writef("P4\n"); w.writef("%i %i\n", n, n); - w.write(image); + w.writeBinary(image); } // diff --git a/test/studies/shootout/mandelbrot/lydia/mandelbrot-unzipped.chpl b/test/studies/shootout/mandelbrot/lydia/mandelbrot-unzipped.chpl index 7fbfa73435cb..c4d1d0e483b1 100644 --- a/test/studies/shootout/mandelbrot/lydia/mandelbrot-unzipped.chpl +++ b/test/studies/shootout/mandelbrot/lydia/mandelbrot-unzipped.chpl @@ -49,9 +49,9 @@ proc main() } var f = new file(1); - var w = f.writer(serializer=new binarySerializer(), locking=false); + var w = f.writer(locking=false); w.writef("P4\n%i %i\n", size, size); - w.write(byteArr); + w.writeBinary(byteArr); } From f5f1fb797752705b8040cd14b6f1940bc2b94a1e Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 14 Sep 2023 17:12:26 -0700 Subject: [PATCH 11/27] Fix a dumb bug (incorrect select statement) Signed-off-by: Danila Fedorin --- modules/standard/IO.chpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/standard/IO.chpl b/modules/standard/IO.chpl index 9a4626ae5ea1..cfa5beabfbe0 100644 --- a/modules/standard/IO.chpl +++ b/modules/standard/IO.chpl @@ -8778,10 +8778,10 @@ proc fileWriter.writeBinary(const ref data: [] ?t, endian:ioendian) throws when ioendian.native { this.writeBinary(data, ioendian.native); } - when ioendian.native { + when ioendian.big { this.writeBinary(data, ioendian.big); } - when ioendian.native { + when ioendian.little { this.writeBinary(data, ioendian.little); } } From f9a294f45d67b26a96029fda8dbfbc00f4b13967 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 14 Sep 2023 18:37:47 -0700 Subject: [PATCH 12/27] Notest the multilocale binary array serialization tests Signed-off-by: Danila Fedorin --- test/library/standard/IO/readBinary/multiloc/binaryArray.notest | 1 + test/library/standard/IO/writeBinary/multiloc/writeArray.notest | 1 + 2 files changed, 2 insertions(+) create mode 100644 test/library/standard/IO/readBinary/multiloc/binaryArray.notest create mode 100644 test/library/standard/IO/writeBinary/multiloc/writeArray.notest diff --git a/test/library/standard/IO/readBinary/multiloc/binaryArray.notest b/test/library/standard/IO/readBinary/multiloc/binaryArray.notest new file mode 100644 index 000000000000..ed51dbd48b58 --- /dev/null +++ b/test/library/standard/IO/readBinary/multiloc/binaryArray.notest @@ -0,0 +1 @@ +bug: uncaught exceptions from serialization code seem to cause GASNet segfaults diff --git a/test/library/standard/IO/writeBinary/multiloc/writeArray.notest b/test/library/standard/IO/writeBinary/multiloc/writeArray.notest new file mode 100644 index 000000000000..ed51dbd48b58 --- /dev/null +++ b/test/library/standard/IO/writeBinary/multiloc/writeArray.notest @@ -0,0 +1 @@ +bug: uncaught exceptions from serialization code seem to cause GASNet segfaults From 0abc206a82b9b3469ca6d6ede43a479ff8ee2b38 Mon Sep 17 00:00:00 2001 From: David Longnecker Date: Sat, 13 May 2023 12:23:43 -0700 Subject: [PATCH 13/27] Add the 'DidOpen' notification, adjust protocol type hierarchy Signed-off-by: David Longnecker --- Makefile | 3 + compiler/Makefile | 3 + tools/chpldef/Message.h | 8 +- tools/chpldef/message-compute.cpp | 17 +++- tools/chpldef/message-macro-list.h | 18 +++-- tools/chpldef/protocol-types.cpp | 89 ++++++++++----------- tools/chpldef/protocol-types.h | 124 ++++++++++++++++++----------- 7 files changed, 157 insertions(+), 105 deletions(-) diff --git a/Makefile b/Makefile index 98a7b0363087..c34ab0d14a0f 100644 --- a/Makefile +++ b/Makefile @@ -135,6 +135,9 @@ chpldef: compiler third-party-chpldef-venv @cd third-party && $(MAKE) CHPL_MAKE_HOST_TARGET=--host jemalloc cd compiler && $(MAKE) chpldef +chpldef-fast: + cd compiler && $(MAKE) chpldef-fast + always-build-test-venv: FORCE -@if [ -n "$$CHPL_ALWAYS_BUILD_TEST_VENV" ]; then \ $(MAKE) test-venv; \ diff --git a/compiler/Makefile b/compiler/Makefile index 51a00bc69052..c2e39007f05f 100644 --- a/compiler/Makefile +++ b/compiler/Makefile @@ -210,6 +210,9 @@ $(CHPLDEF): FORCE $(CHPLDEF_OBJS) | $(CHPL_BIN_DIR) $(CHPL) chpldef: FORCE $(CHPLDEF) +chpldef-fast: FORCE + @cd $(COMPILER_BUILD) && $(MAKE) chpldef + $(COMPILER_BUILD): mkdir -p $@ diff --git a/tools/chpldef/Message.h b/tools/chpldef/Message.h index e0f73ef20d9c..ff4b1b211646 100644 --- a/tools/chpldef/Message.h +++ b/tools/chpldef/Message.h @@ -352,10 +352,10 @@ class Request : public BaseRequest { : Request(Message::name__, std::move(id), error, \ std::move(note), \ std::move(p)) { \ - static_assert(std::is_base_of::value, \ - "Must be derived from 'ProtocolType'"); \ - static_assert(std::is_base_of::value, \ - "Must be derived from 'ProtocolType'"); \ + static_assert(std::is_base_of::value, \ + "Must be derived from 'BaseProtocolType'"); \ + static_assert(std::is_base_of::value, \ + "Must be derived from 'BaseProtocolType'"); \ } \ public: \ virtual ~name__() = default; \ diff --git a/tools/chpldef/message-compute.cpp b/tools/chpldef/message-compute.cpp index 25b9422c3688..a49df7a9a6eb 100644 --- a/tools/chpldef/message-compute.cpp +++ b/tools/chpldef/message-compute.cpp @@ -26,9 +26,18 @@ source file with a name that matches the message name. */ namespace chpldef { +static TextDocumentSyncOptions +configureTextDocumentSyncOptions(Server* ctx) { + TextDocumentSyncOptions ret; + ret.openClose = true; + ret.change = TextDocumentSyncOptions::Full; /** TODO: Incremental? */ + return ret; +} + static void doConfigureStaticCapabilities(Server* ctx, ServerCapabilities& x) { x.positionEncoding = "utf-16"; + x.textDocumentSync = configureTextDocumentSyncOptions(ctx); x.hoverProvider = false; x.declarationProvider = false; x.definitionProvider = false; @@ -66,7 +75,7 @@ Initialize::compute(Server* ctx, const Params& p) { // Set the log verbosity level if it was requested. if (auto trace = p.trace) { auto level = trace->level; - ctx->message("Client requested log level: '%s'\n", + ctx->message("Client requested log level '%s'\n", Logger::levelToString(level)); auto &logger = ctx->logger(); if (level < logger.level()) { @@ -109,4 +118,10 @@ Exit::compute(Server* ctx, const Params& p) { return {}; } +DidOpen::ComputedResult +DidOpen::compute(Server* ctx, const Params& p) { + CHPLDEF_TODO(); + return {}; +} + } // end namespace 'chpldef' diff --git a/tools/chpldef/message-macro-list.h b/tools/chpldef/message-macro-list.h index 125390468c8e..4cd6af10f7ae 100644 --- a/tools/chpldef/message-macro-list.h +++ b/tools/chpldef/message-macro-list.h @@ -31,12 +31,22 @@ CHPLDEF_MESSAGE(Initialized, 0, 1, initialized) CHPLDEF_MESSAGE(Shutdown, 0, 0, shutdown) CHPLDEF_MESSAGE(Exit, 0, 1, exit) +/** The document's content is now managed by the client and the server + MUST NOT try to read the document's content using the document's + URI. Open in this sense means that it is managed by the client. An + open notification must not be sent more than once without a close + notification having been sent before. */ +CHPLDEF_MESSAGE(DidOpen, 0, 1, textDocument/didOpen) +// CHPLDEF_MESSAGE(DidChange, 0, 1, textDocument/didChange) +// CHPLDEF_MESSAGE(DidSave, 0, 1, textDocument/didSave) +// CHPLDEF_MESSAGE(DidClose, 0, 1, textDocument/didClose) + + /* CHPLDEF_MESSAGE(RegisterCapability, client/registerCapability) CHPLDEF_MESSAGE(UnregisterCapability, client/unregisterCapability) CHPLDEF_MESSAGE(SetTrace, $/setTrace) CHPLDEF_MESSAGE(LogTrace, $/logTrace) - */ // @@ -53,12 +63,8 @@ CHPLDEF_MESSAGE(Progress, $/progress) // /* -CHPLDEF_MESSAGE(DidOpen, textDocument/didOpen) -CHPLDEF_MESSAGE(DidChange, textDocument/didChange) -CHPLDEF_MESSAGE(WillSave, textDocument/willSave) CHPLDEF_MESSAGE(WillSaveWaitUntil, textDocument/willSaveWaitUntil) -CHPLDEF_MESSAGE(DidSave, textDocument/didSave) -CHPLDEF_MESSAGE(DidClose, textDocument/didClose) +CHPLDEF_MESSAGE(WillSave, textDocument/willSave) */ // diff --git a/tools/chpldef/protocol-types.cpp b/tools/chpldef/protocol-types.cpp index e900dd23508d..d429889f261f 100644 --- a/tools/chpldef/protocol-types.cpp +++ b/tools/chpldef/protocol-types.cpp @@ -21,13 +21,30 @@ #include "./protocol-types.h" #include "./misc.h" #include "llvm/Support/JSON.h" +#include +#include /** Helper to make populating JSON object fields less painful. */ #define FIELD_(name__) { #name__, name__ } +/** Helper to make reading JSON object fields less painful. */ +#define MAP_(m__, name__) (m__.map(#name__, name__)) + namespace chpldef { -std::string ProtocolType::toString() const { +bool BaseProtocolType::fromJson(const JsonValue& j, JsonPath p) { + std::cerr << "Called 'BaseProtocolTypefromJson'!" << std::endl; + std::abort(); + return false; +} + +JsonValue BaseProtocolType::toJson() const { + std::cerr << "Called 'BaseProtocolType::toJson'!" << std::endl; + std::abort(); + return nullptr; +} + +std::string BaseProtocolType::toString() const { auto ret = jsonToString(toJson()); return ret; } @@ -37,8 +54,7 @@ bool EmptyProtocolType::fromJson(const JsonValue& j, JsonPath p) { } JsonValue EmptyProtocolType::toJson() const { - JsonValue ret(nullptr); - return ret; + return nullptr; } bool ClientInfo::fromJson(const JsonValue& j, JsonPath p) { @@ -49,29 +65,14 @@ bool ClientInfo::fromJson(const JsonValue& j, JsonPath p) { return true; } -JsonValue ClientInfo::toJson() const { - CHPLDEF_IMPOSSIBLE(); - return nullptr; -} - bool ChpldefInit::fromJson(const JsonValue& j, JsonPath p) { return true; } -JsonValue ChpldefInit::toJson() const { - CHPLDEF_IMPOSSIBLE(); - return nullptr; -} - bool ClientCapabilities::fromJson(const JsonValue& j, JsonPath p) { return true; } -JsonValue ClientCapabilities::toJson() const { - CHPLDEF_IMPOSSIBLE(); - return nullptr; -} - bool InitializeParams::fromJson(const JsonValue& j, JsonPath p) { JsonMapper m(j, p); if (!m) return false; @@ -87,16 +88,6 @@ bool InitializeParams::fromJson(const JsonValue& j, JsonPath p) { return true; } -JsonValue InitializeParams::toJson() const { - CHPLDEF_IMPOSSIBLE(); - return nullptr; -} - -bool InitializeResult::fromJson(const JsonValue& j, JsonPath p) { - CHPLDEF_IMPOSSIBLE(); - return true; -} - JsonValue InitializeResult::toJson() const { JsonObject ret { FIELD_(capabilities), @@ -121,36 +112,16 @@ bool TraceLevel::fromJson(const JsonValue& j, JsonPath p) { return false; } -JsonValue TraceLevel::toJson() const { - CHPLDEF_IMPOSSIBLE(); - return nullptr; -} - bool WorkspaceFolder::fromJson(const JsonValue& j, JsonPath p) { CHPLDEF_TODO(); return true; } -JsonValue WorkspaceFolder::toJson() const { - CHPLDEF_IMPOSSIBLE(); - return nullptr; -} - -bool ServerInfo::fromJson(const JsonValue& j, JsonPath p) { - CHPLDEF_IMPOSSIBLE(); - return true; -} - JsonValue ServerInfo::toJson() const { JsonObject ret { FIELD_(name), FIELD_(version) }; return ret; } -bool ServerCapabilities::fromJson(const JsonValue& j, JsonPath p) { - CHPLDEF_IMPOSSIBLE(); - return true; -} - JsonValue ServerCapabilities::toJson() const { JsonObject ret { FIELD_(positionEncoding), @@ -192,4 +163,26 @@ JsonValue ServerCapabilities::toJson() const { return ret; } +JsonValue TextDocumentSyncOptions::toJson() const { + JsonObject ret; + if (openClose) ret["openClose"] = *openClose; + if (change) ret["change"] = static_cast(*change); + return ret; +} + +bool TextDocumentItem::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + bool ret = m; + ret &= MAP_(m, uri); + ret &= MAP_(m, languageId); + ret &= MAP_(m, version); + ret &= MAP_(m, text); + return ret; +} + +bool DidOpenParams::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, textDocument); +} + } // end namespace 'chpldef' diff --git a/tools/chpldef/protocol-types.h b/tools/chpldef/protocol-types.h index 1fe3f3b45711..b0208e73386e 100644 --- a/tools/chpldef/protocol-types.h +++ b/tools/chpldef/protocol-types.h @@ -23,16 +23,7 @@ #include "./Logger.h" #include "./misc.h" -#include - -/** Bunch up some redundant overrides for protocol structs into a macro. */ -#define CHPLDEF_PROTOCOL_TYPE_OVERRIDES() \ - virtual bool fromJson(const JsonValue& j, JsonPath p) override; \ - virtual JsonValue toJson() const override; - -/** Use this to declare protocol types that are empty. */ -#define CHPLDEF_PROTOCOL_EMPTY_TYPE(name__) \ - struct name__ : EmptyProtocolType {} +#include /** This header contains types which help form the Microsoft language server protocol. The types attempt to follow the specification as faithfully @@ -47,32 +38,47 @@ namespace chpldef { using OPT_TODO_TYPE = opt; -struct ProtocolType { - virtual bool fromJson(const JsonValue& j, JsonPath p) = 0; - virtual JsonValue toJson() const = 0; +struct BaseProtocolType { + virtual bool fromJson(const JsonValue& j, JsonPath p); + virtual JsonValue toJson() const; /** By default, convert to JSON and then print the JSON. */ virtual std::string toString() const; - virtual ~ProtocolType() = default; + virtual ~BaseProtocolType() = default; }; -struct EmptyProtocolType : ProtocolType { +struct EmptyProtocolType : BaseProtocolType { virtual bool fromJson(const JsonValue& j, JsonPath p) override; virtual JsonValue toJson() const override; virtual ~EmptyProtocolType() = default; }; -/** Information about the client. */ -struct ClientInfo : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct ProtocolTypeSend : BaseProtocolType { + virtual JsonValue toJson() const override = 0; + virtual ~ProtocolTypeSend() = default; +}; + +struct ProtocolTypeRecv : BaseProtocolType { + virtual bool fromJson(const JsonValue& j, JsonPath p) override = 0; + virtual ~ProtocolTypeRecv() = default; +}; + +struct ProtocolType : BaseProtocolType { + virtual bool fromJson(const JsonValue& j, JsonPath p) override = 0; + virtual JsonValue toJson() const override = 0; + virtual ~ProtocolType() = default; +}; + +struct ClientInfo : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; std::string name; opt version; }; /** TODO: Used to store 'chpldef' specific initialization options. */ -struct ChpldefInit : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct ChpldefInit : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; /** As defined by the spec, this structure is deeply nested and absolutely @@ -84,27 +90,27 @@ struct ChpldefInit : ProtocolType { TODO: If you add a field here, then adjust the (de)serializer methods. */ -struct ClientCapabilities : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct ClientCapabilities : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; -struct WorkspaceFolder : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct WorkspaceFolder : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; std::string uri; std::string name; }; -struct TraceLevel : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct TraceLevel : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; Logger::Level level; }; -struct InitializeParams : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct InitializeParams : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; - opt processId; + opt processId; opt clientInfo; opt locale; opt rootPath; /** Deprecated -> 'rootUri'. */ @@ -115,17 +121,27 @@ struct InitializeParams : ProtocolType { opt> workspaceFolders; }; -struct TextDocumentSyncOptions : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct TextDocumentSyncOptions : ProtocolTypeSend { + virtual JsonValue toJson() const override; + + /** Valid 'change' values. */ + enum Change { + None = 0, + Full = 1, + Incremental = 2 + }; + + opt openClose; + opt change; }; /** Some of the 'provider' queries have more advanced types we can swap in to configure further -- see 'DeclarationRegistrationOptions'. */ -struct ServerCapabilities : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct ServerCapabilities : ProtocolTypeSend { + virtual JsonValue toJson() const override; opt positionEncoding; - OPT_TODO_TYPE textDocumentSync; + opt textDocumentSync; OPT_TODO_TYPE notebookDocumentSync; OPT_TODO_TYPE completionProvider; opt hoverProvider; @@ -161,39 +177,55 @@ struct ServerCapabilities : ProtocolType { OPT_TODO_TYPE experimental; }; -struct ServerInfo : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct ServerInfo : ProtocolTypeSend { + virtual JsonValue toJson() const override; std::string name; opt version; }; -struct InitializeResult : ProtocolType { - CHPLDEF_PROTOCOL_TYPE_OVERRIDES(); +struct InitializeResult : ProtocolTypeSend { + virtual JsonValue toJson() const override; ServerCapabilities capabilities; opt serverInfo; }; -CHPLDEF_PROTOCOL_EMPTY_TYPE(InitializedParams); -CHPLDEF_PROTOCOL_EMPTY_TYPE(InitializedResult); +struct InitializedParams : EmptyProtocolType {}; +struct InitializedResult : EmptyProtocolType {}; + +struct ShutdownParams : EmptyProtocolType {}; +struct ShutdownResult : EmptyProtocolType {}; -CHPLDEF_PROTOCOL_EMPTY_TYPE(ShutdownParams); -CHPLDEF_PROTOCOL_EMPTY_TYPE(ShutdownResult); +struct ExitParams : EmptyProtocolType {}; +struct ExitResult : EmptyProtocolType {}; -CHPLDEF_PROTOCOL_EMPTY_TYPE(ExitParams); -CHPLDEF_PROTOCOL_EMPTY_TYPE(ExitResult); +struct TextDocumentItem : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + + std::string uri; + std::string languageId; + int64_t version; + std::string text; +}; + +struct DidOpenResult : EmptyProtocolType {}; +struct DidOpenParams : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + + TextDocumentItem textDocument; +}; /** Instantiate only if 'T' is derived from 'ProtocolType'. */ template -CHPLDEF_ENABLE_IF_DERIVED(T, ProtocolType, bool) +CHPLDEF_ENABLE_IF_DERIVED(T, BaseProtocolType, bool) fromJSON(const JsonValue& j, T& x, JsonPath p) { return x.fromJson(j, p); } /** Instantiate only if 'T' is derived from 'ProtocolType'. */ template -CHPLDEF_ENABLE_IF_DERIVED(T, ProtocolType, JsonValue) +CHPLDEF_ENABLE_IF_DERIVED(T, BaseProtocolType, JsonValue) toJSON(const T& x) { return x.toJson(); } From 66e8addba8ceddc9a52517e37a5adf9b18949aba Mon Sep 17 00:00:00 2001 From: David Longnecker Date: Wed, 17 May 2023 13:16:30 -0700 Subject: [PATCH 14/27] Rewrite 'chpldef' make target to stop building the compiler Signed-off-by: David Longnecker --- Makefile | 4 ++-- compiler/Makefile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index c34ab0d14a0f..4e2eb86c1407 100644 --- a/Makefile +++ b/Makefile @@ -129,11 +129,11 @@ chpldoc: third-party-chpldoc-venv @cd modules && $(MAKE) @test -r Makefile.devel && $(MAKE) man-chpldoc || echo "" -chpldef: compiler third-party-chpldef-venv +chpldef: third-party-chpldef-venv FORCE @echo "Making chpldef..." @cd third-party && $(MAKE) llvm - @cd third-party && $(MAKE) CHPL_MAKE_HOST_TARGET=--host jemalloc cd compiler && $(MAKE) chpldef + @cd modules && $(MAKE) chpldef-fast: cd compiler && $(MAKE) chpldef-fast diff --git a/compiler/Makefile b/compiler/Makefile index c2e39007f05f..cb70e5ab4d7e 100644 --- a/compiler/Makefile +++ b/compiler/Makefile @@ -205,7 +205,7 @@ $(CHPLDOC): FORCE $(COMPILER_BUILD) $(MAKEALLCHPLDOCSUBDIRS) | $(CHPL_BIN_DIR) chpldoc: FORCE $(CHPLDOC) -$(CHPLDEF): FORCE $(CHPLDEF_OBJS) | $(CHPL_BIN_DIR) $(CHPL) +$(CHPLDEF): FORCE $(CHPL_BIN_DIR) @cd $(COMPILER_BUILD) && $(CMAKE) $(CHPL_MAKE_HOME) $(CMAKE_FLAGS) && $(MAKE) chpldef chpldef: FORCE $(CHPLDEF) From 3dd6f413979ffa432a44a0093e3afe4336473072 Mon Sep 17 00:00:00 2001 From: David Longnecker Date: Tue, 30 May 2023 12:59:11 -0700 Subject: [PATCH 15/27] Support safe access to Chapel context, improve Message subclasses Signed-off-by: David Longnecker --- tools/chpldef/Message.cpp | 2 +- tools/chpldef/Message.h | 30 +++++++++--- tools/chpldef/Server.cpp | 40 +++++++++++++++- tools/chpldef/Server.h | 69 ++++++++++++++++++++++++---- tools/chpldef/chpldef.cpp | 54 +++++++++++++--------- tools/chpldef/command-line-flags.cpp | 43 ++++++++++++----- tools/chpldef/command-line-flags.h | 17 ++++++- tools/chpldef/message-compute.cpp | 54 +++++++++++++++++++++- tools/chpldef/message-macro-list.h | 6 +-- tools/chpldef/protocol-types.cpp | 20 ++++++++ tools/chpldef/protocol-types.h | 36 +++++++++------ 11 files changed, 297 insertions(+), 74 deletions(-) diff --git a/tools/chpldef/Message.cpp b/tools/chpldef/Message.cpp index 24fe75a44eab..c19ac104d9b8 100644 --- a/tools/chpldef/Message.cpp +++ b/tools/chpldef/Message.cpp @@ -354,7 +354,7 @@ doHandleRequest(Server* ctx, M* msg, const P& p, CR& cr) { if (msg->status() == Message::PROGRESSING) CHPLDEF_TODO(); if (msg->status() != Message::PENDING) return false; - ctx->message("Handling request '%s' with ID %s\n", + ctx->message("Handling request '%s' with ID '%s'\n", msg->tagToString(), msg->idToString().c_str()); diff --git a/tools/chpldef/Message.h b/tools/chpldef/Message.h index ff4b1b211646..adf4281647b3 100644 --- a/tools/chpldef/Message.h +++ b/tools/chpldef/Message.h @@ -24,10 +24,8 @@ #include "./misc.h" #include "./protocol-types.h" #include "./Server.h" - #include "llvm/Support/FileSystem.h" #include "llvm/Support/JSON.h" - #include #include #include @@ -40,7 +38,10 @@ template class Request; class Response; /** Forward declare requests. */ -#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) class name__; +#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ + struct name__##Params; \ + struct name__##Result; \ + class name__; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE @@ -282,6 +283,14 @@ class Request : public BaseRequest { error(Message::OK), result(r) {} + ComputedResult(bool isProgressingCallAgain, Message::Error error, + std::string note, + Result result) + : isProgressingCallAgain(isProgressingCallAgain), + error(error), + note(std::move(note)), + result(std::move(result)) {} + ComputedResult() = default; }; @@ -341,10 +350,16 @@ class Request : public BaseRequest { to look up the message, as that is unambiguous. */ #define CHPLDEF_MESSAGE(name__, outbound__, notification__, rpc__) \ - class name__ : public Request { \ + namespace name__##Impl { \ + constexpr bool notify = notification__; \ + using E = EmptyProtocolType; \ + using P = name__##Params; \ + using R = std::conditional::type; \ + } \ + class name__ : public Request { \ public: \ - using Params = name__##Params; \ - using Result = name__##Result; \ + using Params = name__##Impl::P; \ + using Result = name__##Impl::R; \ using ComputedResult = Request::ComputedResult; \ private: \ name__(JsonValue id, Message::Error error, std::string note, \ @@ -354,7 +369,8 @@ class Request : public BaseRequest { std::move(p)) { \ static_assert(std::is_base_of::value, \ "Must be derived from 'BaseProtocolType'"); \ - static_assert(std::is_base_of::value, \ + static_assert(std::is_same::value || \ + std::is_base_of::value, \ "Must be derived from 'BaseProtocolType'"); \ } \ public: \ diff --git a/tools/chpldef/Server.cpp b/tools/chpldef/Server.cpp index 61503f7bf299..1cb4e865fd3d 100644 --- a/tools/chpldef/Server.cpp +++ b/tools/chpldef/Server.cpp @@ -44,8 +44,6 @@ namespace chpldef { -Server::Server() {} - void Server::setLogger(Logger&& logger) { this->logger_ = std::move(logger); } @@ -67,4 +65,42 @@ void Server::trace(const char* fmt, ...) { VPRINTF_FORWARD_(fmt, logger_.vtrace); } +Server::Server(Server::Configuration config) : config_(std::move(config)) { + chapel_ = createCompilerContext(); + + Logger logger; + if (!config_.logFile.empty()) { + logger = Logger::createForFile(config_.logFile); + if (!logger.isLogging()) { + std::cerr << "Failed to open log file!" << std::endl; + logger = Logger(); + std::cerr << "Using '" << logger.filePath() << "'" << std::endl; + } + } + + // Set the logger verbosity level. + logger.setLevel(config_.logLevel); + + this->setLogger(std::move(logger)); +} + +chpl::owned Server::createCompilerContext() { + chpl::Context::Configuration chplConfig; + chplConfig.chplHome = config_.chplHome; + auto ret = chpl::toOwned(new chpl::Context(std::move(chplConfig))); + return ret; +} + +bool Server::shouldGarbageCollect() { + auto f = config_.garbageCollectionFrequency; + if (f == 0) return false; + return (revision_ % f) == 0; +} + +bool Server::shouldPrepareToGarbageCollect() { + auto f = config_.garbageCollectionFrequency; + if (f == 0) return false; + return (revision_ % f) == 1; +} + } // end namespace 'chpldef' diff --git a/tools/chpldef/Server.h b/tools/chpldef/Server.h index 2927b56c4435..3b4c11ab219b 100644 --- a/tools/chpldef/Server.h +++ b/tools/chpldef/Server.h @@ -23,13 +23,10 @@ #include "./Logger.h" #include "./misc.h" - #include "chpl/framework/Context.h" #include "chpl/framework/UniqueString.h" - #include "llvm/ADT/Optional.h" #include "llvm/Support/JSON.h" - #include #include #include @@ -40,9 +37,14 @@ namespace chpldef { class Initialize; class Initialized; class Shutdown; +class DidOpen; class Server { public: + static constexpr const char* NAME = "chpldef"; + static constexpr const char* VERSION = "0.0.0"; + static constexpr int DEFAULT_GC_FREQUENCY = 64; + enum State { UNINIT, /** Client has not sent 'Initialize' yet. */ INIT, /** We have responded to 'Initialize'. */ @@ -50,28 +52,53 @@ class Server { SHUTDOWN /** Client has sent us 'Shutdown'. */ }; - class Configuration {}; + // TODO: Use this to configure server capabilities, etc. + struct Configuration { + std::string logFile; + Logger::Level logLevel = Logger::OFF; + std::string chplHome; + int garbageCollectionFrequency = DEFAULT_GC_FREQUENCY; + bool warnUnstable = false; + bool enableStandardLibrary = false; + bool compilerDebugTrace = false; + }; + + struct TextEntry { + int64_t version = -1; + int64_t lastRevisionContentsUpdated = -1; + bool isOpen = false; + }; + + using TextRegistry = std::map; private: State state_ = UNINIT; Logger logger_; - chpl::owned chplctx_ = nullptr; - int revision_; + chpl::owned chapel_ = nullptr; + Configuration config_; + TextRegistry textRegistry_; + int64_t revision_; + + chpl::owned createCompilerContext(); + bool shouldGarbageCollect(); + bool shouldPrepareToGarbageCollect(); protected: - friend class chpldef::Shutdown; friend class chpldef::Initialize; friend class chpldef::Initialized; + friend class chpldef::Shutdown; + friend class chpldef::DidOpen; inline void setState(State state) { state_ = state; } + inline TextRegistry& textRegistry() { return textRegistry_; }; public: - Server(); + Server(Configuration config); ~Server() = default; inline State state() const { return state_; } - inline int revision() const { return revision_; } - inline const chpl::Context* chplctx() const { return chplctx_.get(); } + inline int64_t revision() const { return revision_; } + inline const chpl::Context* chapel() const { return chapel_.get(); } void setLogger(Logger&& logger); inline Logger& logger() { return logger_; } @@ -80,6 +107,28 @@ class Server { CHPLDEF_PFMT(2, 3, void message(const char* fmt, ...)); CHPLDEF_PFMT(2, 3, void verbose(const char* fmt, ...)); CHPLDEF_PFMT(2, 3, void trace(const char* fmt, ...)); + + enum WithChapelContextConfig { + CHPL_NO_MASK = 0, + CHPL_BUMP_REVISION = 1, + }; + + /** Execute code with controlled access to the Chapel context. */ + template + T withChapelContext(F& f, Ns... ns) { + return withChapelContext(CHPL_NO_MASK, f, ns...); + } + + /** Execute code with controlled access to the Chapel context. */ + template + T withChapelContext(WithChapelContextConfig c, F&& f, Ns... ns) { + if (shouldGarbageCollect()) chapel_->collectGarbage(); + if (c & CHPL_BUMP_REVISION) { + chapel_->advanceToNextRevision(shouldPrepareToGarbageCollect()); + ++revision_; + } + return f(chapel_.get(), ns...); + } }; } // end namespace 'chpldef' diff --git a/tools/chpldef/chpldef.cpp b/tools/chpldef/chpldef.cpp index 8d43c2b1c50d..2a90bc5d0675 100644 --- a/tools/chpldef/chpldef.cpp +++ b/tools/chpldef/chpldef.cpp @@ -26,32 +26,42 @@ using namespace chpldef; -int main(int argc, char** argv) { - Server context; - Server* ctx = &context; +static Server::Configuration prepareServerConfig(int argc, char** argv) { + Server::Configuration ret; - cmd::doParseOptions(ctx, argc, argv); + cmd::doParseOptions(argc, argv); - // Configure the logger instance that the context will use. - auto setupLogger = !cmd::logFile.empty() - ? Logger::createForFile(cmd::logFile) - : Logger(); - if (!setupLogger.isLogging()) { - std::cerr << "Failed to open log file!" << std::endl; + if (!cmd::chplHome.empty()) { + ret.chplHome = cmd::chplHome; + } else if (const char* chplHomeEnv = getenv("CHPL_HOME")) { + ret.chplHome = chplHomeEnv; } else { - ctx->setLogger(std::move(setupLogger)); + std::cerr << "No value for '$CHPL_HOME'!" << std::endl; } - // Get the logger and set the verbosity level. - auto& logger = ctx->logger(); - logger.setLevel(cmd::logLevel); + // TODO: Wire these up to command-line flags. + ret.logFile = cmd::logFile; + ret.logLevel = cmd::logLevel; + ret.garbageCollectionFrequency = 0; + ret.warnUnstable = false; + ret.enableStandardLibrary = false; + ret.compilerDebugTrace = false; + + return ret; +} + +int main(int argc, char** argv) { + auto config = prepareServerConfig(argc, argv); + Server context(std::move(config)); + Server* ctx = &context; // Flush every log message immediately to avoid losing info on crash. - logger.setFlushImmediately(true); + auto& log = ctx->logger(); + log.setFlushImmediately(true); ctx->message("Logging to '%s' with level '%s'\n", - logger.filePath().c_str(), - logger.levelToString()); + log.filePath().c_str(), + log.levelToString()); int run = 1; int ret = 0; @@ -59,7 +69,7 @@ int main(int argc, char** argv) { while (run) { ctx->message("Server awaiting message...\n"); - auto json = JsonValue(nullptr); + JsonValue json(nullptr); // TODO: This operation blocks. We'd like non-blocking IO. There are a // few ways to accomplish this. Ideally we find some sort of high-level, @@ -77,7 +87,7 @@ int main(int argc, char** argv) { bool ok = Transport::readJsonBlocking(ctx, std::cin, json); CHPL_ASSERT(ok); - if (logger.level() == Logger::TRACE) { + if (log.level() == Logger::TRACE) { ctx->trace("Incoming JSON is %s\n", jsonToString(json).c_str()); } @@ -101,7 +111,7 @@ int main(int argc, char** argv) { // We have an immediate response, so send it. if (auto optRsp = Message::handle(ctx, msg.get())) { auto pack = optRsp->pack(); - if (logger.level() == Logger::TRACE) { + if (log.level() == Logger::TRACE) { auto str = jsonToString(pack); ctx->trace("Outgoing JSON is %s\n", str.c_str()); } @@ -117,11 +127,11 @@ int main(int argc, char** argv) { } // Flush the log in case something goes wrong. - logger.flush(); + log.flush(); } ctx->message("Server exiting with code '%d'\n", ret); - logger.flush(); + log.flush(); return ret; } diff --git a/tools/chpldef/command-line-flags.cpp b/tools/chpldef/command-line-flags.cpp index f7c6160c4018..638530523523 100644 --- a/tools/chpldef/command-line-flags.cpp +++ b/tools/chpldef/command-line-flags.cpp @@ -20,6 +20,7 @@ #include "./command-line-flags.h" #include "llvm/Support/CommandLine.h" +#include namespace chpldef { namespace cmd { @@ -36,19 +37,35 @@ Flag logLevel("log-level", clEnumValN(Logger::VERBOSE, "verbose", "Messages and details"), clEnumValN(Logger::TRACE, "trace", "Full trace of execution"))); -template -static bool isSameMemoryLocation(const llvm::cl::opt& f1, - const llvm::cl::Option* f2) { - static_assert(std::is_base_of>::value); - auto ptr = static_cast(&f1); - return ptr == f2; -} +Flag chplHome("chpl-home", + llvm::cl::desc("Specify the location of Chapel home"), + llvm::cl::value_desc("A string specifying a path")); + +Flag warnUnstable("warn-unstable", + llvm::cl::desc("Set to have the Chapel compiler emit unstable warnings")); + +Flag garbageCollectionFrequency("gc-frequency", + llvm::cl::init(Server::DEFAULT_GC_FREQUENCY), + llvm::cl::desc("Set the garbage collection frequency"), + llvm::cl::value_desc("An integer specifying a revision interval")); + +Flag enableStandardLibrary("enable-std", + llvm::cl::init(false), + llvm::cl::desc("Set to enable use of the standard library")); + +Flag compilerDebugTrace("debug-trace", + llvm::cl::init(false), + llvm::cl::desc("Set to enable high verbosity query debug tracing")); + +namespace { +static std::set flagAddresses = { + &logFile, &logLevel, &chplHome, &warnUnstable, &garbageCollectionFrequency, + &enableStandardLibrary, + &compilerDebugTrace +}; -// TODO: Get these things in an array so we don't have O(n) code? static bool isChpldefRegisteredFlag(const llvm::cl::Option* f) { - if (isSameMemoryLocation(logFile, f)) return true; - if (isSameMemoryLocation(logLevel, f)) return true; - return false; + return flagAddresses.find(f) != flagAddresses.end(); } static void disableNonChpldefOptions() { @@ -62,7 +79,9 @@ static void disableNonChpldefOptions() { } } -void doParseOptions(Server* ctx, int argc, char** argv) { +} // end anonymous namespace + +void doParseOptions(int argc, char** argv) { disableNonChpldefOptions(); llvm::cl::ParseCommandLineOptions(argc, argv); } diff --git a/tools/chpldef/command-line-flags.h b/tools/chpldef/command-line-flags.h index 70c97cefd432..b5dcae4800c9 100644 --- a/tools/chpldef/command-line-flags.h +++ b/tools/chpldef/command-line-flags.h @@ -34,7 +34,7 @@ namespace cmd { /** Call this in 'main' to parse command-line options. Only the options added by this program will be shown. */ -void doParseOptions(Server* ctx, int argc, char** argv); +void doParseOptions(int argc, char** argv); /** Alias for LLVM's command-line option type. */ template using Flag = llvm::cl::opt; @@ -45,6 +45,21 @@ extern Flag logFile; /** The log level. Defaults to 'Logger::OFF'. */ extern Flag logLevel; +/** The path to 'CHPL_HOME'. Overridden by the 'CHPL_HOME' env var. */ +extern Flag chplHome; + +/** Whether or not unstable warnings should be emitted. */ +extern Flag warnUnstable; + +/** The GC frequency. Defaults to a server-decided value. */ +extern Flag garbageCollectionFrequency; + +/** If the standard library should be enabled. Defaults to 'false'. */ +extern Flag enableStandardLibrary; + +/** Enable debug tracing for the compiler. Defaults to 'false'. */ +extern Flag compilerDebugTrace; + } // end namespace 'cmd' } // end namespace 'chpldef' diff --git a/tools/chpldef/message-compute.cpp b/tools/chpldef/message-compute.cpp index a49df7a9a6eb..36f84806f3a0 100644 --- a/tools/chpldef/message-compute.cpp +++ b/tools/chpldef/message-compute.cpp @@ -19,6 +19,8 @@ */ #include "./Message.h" +#include "./Server.h" +#include "chpl/parsing/parsing-queries.h" /** This is one file where message handlers can be implemented. However, if a particular message's handler grows to be very large in size (e.g., @@ -31,6 +33,10 @@ configureTextDocumentSyncOptions(Server* ctx) { TextDocumentSyncOptions ret; ret.openClose = true; ret.change = TextDocumentSyncOptions::Full; /** TODO: Incremental? */ + ret.willSave = true; + ret.willSaveWaitUntil = true; + ret.save = SaveOptions(); + ret.save->includeText = true; return ret; } @@ -64,8 +70,8 @@ doConfigureStaticCapabilities(Server* ctx, ServerCapabilities& x) { static ServerInfo configureServerInfo(Server* ctx) { ServerInfo ret; - ret.name = "chpldef"; - ret.version = "0.0.0"; + ret.name = Server::NAME; + ret.version = Server::VERSION; return ret; } @@ -120,6 +126,50 @@ Exit::compute(Server* ctx, const Params& p) { DidOpen::ComputedResult DidOpen::compute(Server* ctx, const Params& p) { + auto& tdi = p.textDocument; + auto& e = ctx->textRegistry()[tdi.uri]; + + if (e.isOpen) { + CHPLDEF_TODO(); + return fail(); + } + + CHPL_ASSERT(tdi.version > e.version); + + // NOTE: I think we always have to bump the revision here. This is + // because this file may have been implicitly parsed from disk as + // as result of resolving a use/import. The contents are considered + // to have changed and the "truth of the file's contents" are determined + // by the client as long as it has the file open. Cannot implicitly + // read from disk, so have to bump the revision to ensure correctness. + ctx->withChapelContext(Server::CHPL_BUMP_REVISION, + [&](auto chapel) { + chpl::parsing::setFileText(chapel, tdi.uri, tdi.text); + auto& fc = chpl::parsing::fileText(chapel, tdi.uri); + CHPL_ASSERT(!fc.error()); + CHPL_ASSERT(fc.text() == tdi.text); + e.version = tdi.version; + e.lastRevisionContentsUpdated = ctx->revision(); + e.isOpen = true; + }); + + return {}; +} + +DidChange::ComputedResult +DidChange::compute(Server* ctx, const Params& p) { + CHPLDEF_TODO(); + return {}; +} + +DidSave::ComputedResult +DidSave::compute(Server* ctx, const Params& p) { + CHPLDEF_TODO(); + return {}; +} + +DidClose::ComputedResult +DidClose::compute(Server* ctx, const Params& p) { CHPLDEF_TODO(); return {}; } diff --git a/tools/chpldef/message-macro-list.h b/tools/chpldef/message-macro-list.h index 4cd6af10f7ae..b2a8a0771d89 100644 --- a/tools/chpldef/message-macro-list.h +++ b/tools/chpldef/message-macro-list.h @@ -37,9 +37,9 @@ CHPLDEF_MESSAGE(Exit, 0, 1, exit) open notification must not be sent more than once without a close notification having been sent before. */ CHPLDEF_MESSAGE(DidOpen, 0, 1, textDocument/didOpen) -// CHPLDEF_MESSAGE(DidChange, 0, 1, textDocument/didChange) -// CHPLDEF_MESSAGE(DidSave, 0, 1, textDocument/didSave) -// CHPLDEF_MESSAGE(DidClose, 0, 1, textDocument/didClose) +CHPLDEF_MESSAGE(DidChange, 0, 1, textDocument/didChange) +CHPLDEF_MESSAGE(DidSave, 0, 1, textDocument/didSave) +CHPLDEF_MESSAGE(DidClose, 0, 1, textDocument/didClose) /* diff --git a/tools/chpldef/protocol-types.cpp b/tools/chpldef/protocol-types.cpp index d429889f261f..80da84f32916 100644 --- a/tools/chpldef/protocol-types.cpp +++ b/tools/chpldef/protocol-types.cpp @@ -185,4 +185,24 @@ bool DidOpenParams::fromJson(const JsonValue& j, JsonPath p) { return m && MAP_(m, textDocument); } +bool DidSaveParams::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, textDocument); +} + +bool DidCloseParams::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, textDocument); +} + +bool DidChangeParams::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, textDocument); +} + +JsonValue SaveOptions::toJson() const { + JsonObject ret { FIELD_(includeText) }; + return ret; +} + } // end namespace 'chpldef' diff --git a/tools/chpldef/protocol-types.h b/tools/chpldef/protocol-types.h index b0208e73386e..2d1f327e6314 100644 --- a/tools/chpldef/protocol-types.h +++ b/tools/chpldef/protocol-types.h @@ -71,7 +71,6 @@ struct ProtocolType : BaseProtocolType { struct ClientInfo : ProtocolTypeRecv { virtual bool fromJson(const JsonValue& j, JsonPath p) override; - std::string name; opt version; }; @@ -96,20 +95,17 @@ struct ClientCapabilities : ProtocolTypeRecv { struct WorkspaceFolder : ProtocolTypeRecv { virtual bool fromJson(const JsonValue& j, JsonPath p) override; - std::string uri; std::string name; }; struct TraceLevel : ProtocolTypeRecv { virtual bool fromJson(const JsonValue& j, JsonPath p) override; - Logger::Level level; }; struct InitializeParams : ProtocolTypeRecv { virtual bool fromJson(const JsonValue& j, JsonPath p) override; - opt processId; opt clientInfo; opt locale; @@ -121,25 +117,30 @@ struct InitializeParams : ProtocolTypeRecv { opt> workspaceFolders; }; -struct TextDocumentSyncOptions : ProtocolTypeSend { +struct SaveOptions : ProtocolTypeSend { virtual JsonValue toJson() const override; + opt includeText; +}; +struct TextDocumentSyncOptions : ProtocolTypeSend { + virtual JsonValue toJson() const override; /** Valid 'change' values. */ enum Change { None = 0, Full = 1, Incremental = 2 }; - opt openClose; opt change; + opt willSave; + opt willSaveWaitUntil; + opt save; }; /** Some of the 'provider' queries have more advanced types we can swap in to configure further -- see 'DeclarationRegistrationOptions'. */ struct ServerCapabilities : ProtocolTypeSend { virtual JsonValue toJson() const override; - opt positionEncoding; opt textDocumentSync; OPT_TODO_TYPE notebookDocumentSync; @@ -179,7 +180,6 @@ struct ServerCapabilities : ProtocolTypeSend { struct ServerInfo : ProtocolTypeSend { virtual JsonValue toJson() const override; - std::string name; opt version; }; @@ -192,27 +192,35 @@ struct InitializeResult : ProtocolTypeSend { }; struct InitializedParams : EmptyProtocolType {}; -struct InitializedResult : EmptyProtocolType {}; - struct ShutdownParams : EmptyProtocolType {}; struct ShutdownResult : EmptyProtocolType {}; - struct ExitParams : EmptyProtocolType {}; -struct ExitResult : EmptyProtocolType {}; struct TextDocumentItem : ProtocolTypeRecv { virtual bool fromJson(const JsonValue& j, JsonPath p) override; - std::string uri; std::string languageId; int64_t version; std::string text; }; -struct DidOpenResult : EmptyProtocolType {}; struct DidOpenParams : ProtocolTypeRecv { virtual bool fromJson(const JsonValue& j, JsonPath p) override; + TextDocumentItem textDocument; +}; + +struct DidChangeParams : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + TextDocumentItem textDocument; +}; + +struct DidSaveParams : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + TextDocumentItem textDocument; +}; +struct DidCloseParams : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; TextDocumentItem textDocument; }; From 9b99aa6884e8e0ba854eaf498b7bb747297a6e93 Mon Sep 17 00:00:00 2001 From: David Longnecker Date: Wed, 31 May 2023 12:51:04 -0700 Subject: [PATCH 16/27] Tweak Makefile target rules and 'add_subdirectory' guard in CMakeLists Signed-off-by: David Longnecker --- CMakeLists.txt | 13 ++++++++----- compiler/Makefile | 4 +++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d992bcfe39f9..f9cff1f60dbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -349,11 +349,14 @@ add_subdirectory(compiler) add_subdirectory(frontend) add_subdirectory(tools/chpldoc) -# We are not ready to include 'chpldef' in an official release just yet. -if (NOT CMAKE_BUILD_TYPE STREQUAL "Release") - if (EXISTS tools/chpldef) - add_subdirectory(tools/chpldef) - endif() +# We are not ready to include 'chpldef' in an official release just yet. This +# check works because the 'chpldef' source files are currently not included +# in source releases. +# message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}") +# message(STATUS "CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}") +if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tools/chpldef) + message(STATUS "Added chpldef subdirectory...") + add_subdirectory(tools/chpldef) endif() # Adjust the install rpath for chpl and chpldoc diff --git a/compiler/Makefile b/compiler/Makefile index cb70e5ab4d7e..cd6070bcd9e0 100644 --- a/compiler/Makefile +++ b/compiler/Makefile @@ -205,7 +205,9 @@ $(CHPLDOC): FORCE $(COMPILER_BUILD) $(MAKEALLCHPLDOCSUBDIRS) | $(CHPL_BIN_DIR) chpldoc: FORCE $(CHPLDOC) -$(CHPLDEF): FORCE $(CHPL_BIN_DIR) +MAKEALLCHPLDEFSUBDIRS = $(CHPLDEF_SUBDIRS:%=%.makedir) + +$(CHPLDEF): FORCE $(COMPILER_BUILD) $(MAKEALLCHPLDEFSUBDIRS) | $(CHPL_BIN_DIR) @cd $(COMPILER_BUILD) && $(CMAKE) $(CHPL_MAKE_HOME) $(CMAKE_FLAGS) && $(MAKE) chpldef chpldef: FORCE $(CHPLDEF) From 81eebc4f34acbad2613b6f1604165579d7a0264e Mon Sep 17 00:00:00 2001 From: David Longnecker Date: Fri, 2 Jun 2023 17:24:33 -0700 Subject: [PATCH 17/27] Add initial support for the 'Declaration' request Signed-off-by: David Longnecker --- frontend/include/chpl/framework/ID.h | 2 + frontend/include/chpl/framework/Location.h | 14 + frontend/include/chpl/framework/query-impl.h | 10 +- tools/chpldef/CMakeLists.txt | 4 +- tools/chpldef/Message.h | 1 + tools/chpldef/Server.cpp | 35 +++ tools/chpldef/Server.h | 71 ++++- tools/chpldef/chpldef.cpp | 38 ++- tools/chpldef/compute-goto-entity.cpp | 263 ++++++++++++++++++ ...sage-compute.cpp => compute-lifecycle.cpp} | 116 +++----- tools/chpldef/compute-synchronization.cpp | 81 ++++++ tools/chpldef/message-macro-list.h | 4 +- tools/chpldef/protocol-types.cpp | 185 ++++++++---- tools/chpldef/protocol-types.h | 66 +++++ 14 files changed, 731 insertions(+), 159 deletions(-) create mode 100644 tools/chpldef/compute-goto-entity.cpp rename tools/chpldef/{message-compute.cpp => compute-lifecycle.cpp} (52%) create mode 100644 tools/chpldef/compute-synchronization.cpp diff --git a/frontend/include/chpl/framework/ID.h b/frontend/include/chpl/framework/ID.h index 0d554c5972de..008ef28a3a4c 100644 --- a/frontend/include/chpl/framework/ID.h +++ b/frontend/include/chpl/framework/ID.h @@ -217,6 +217,8 @@ class ID final { return symbolPath_.isEmpty(); } + explicit operator bool() const { return !isEmpty(); } + size_t hash() const { (void)numChildIds_; // this field is intentionally not hashed std::hash hasher; diff --git a/frontend/include/chpl/framework/Location.h b/frontend/include/chpl/framework/Location.h index 8841e6063122..7d0c4a1b04cb 100644 --- a/frontend/include/chpl/framework/Location.h +++ b/frontend/include/chpl/framework/Location.h @@ -86,6 +86,20 @@ class Location final { return chpl::hash(path_, firstLine_, firstColumn_, lastLine_, lastColumn_); } + /** True if this contains 'loc' or if this and 'loc' are equal. */ + bool contains(const Location& loc) { + return firstLine() <= loc.firstLine() && + firstColumn() <= loc.firstColumn() && + lastLine() >= loc.lastLine() && + lastColumn() >= loc.lastColumn(); + } + + inline bool operator>(const Location& rhs) { + if (firstLine() > rhs.firstLine()) return true; + return firstLine() == rhs.firstLine() && + firstColumn() > rhs.firstColumn(); + } + void stringify(std::ostream& os, StringifyKind stringifyKind) const; // TODO: We could probably save some space by using a variable byte diff --git a/frontend/include/chpl/framework/query-impl.h b/frontend/include/chpl/framework/query-impl.h index 32fc6ba81317..4a5b8dcf4aae 100644 --- a/frontend/include/chpl/framework/query-impl.h +++ b/frontend/include/chpl/framework/query-impl.h @@ -565,9 +565,9 @@ Context::querySetterUpdateResult( #if CHPL_QUERY_TIMING_AND_TRACE_ENABLED -#define QUERY_BEGIN_TIMING() \ - context->queryBeginTrace(BEGIN_QUERY_FUNC_NAME, BEGIN_QUERY_ARGS); \ - auto QUERY_STOPWATCH = context->makeQueryTimingStopwatch(BEGIN_QUERY_MAP) +#define QUERY_BEGIN_TIMING(context__) \ + context__->queryBeginTrace(BEGIN_QUERY_FUNC_NAME, BEGIN_QUERY_ARGS); \ + auto QUERY_STOPWATCH = context__->makeQueryTimingStopwatch(BEGIN_QUERY_MAP) #else @@ -590,7 +590,7 @@ Context::querySetterUpdateResult( return QUERY_GET_SAVED(); \ } \ auto QUERY_RECOMPUTATION_MARKER = context->markRecomputing(false); \ - QUERY_BEGIN_TIMING(); + QUERY_BEGIN_TIMING(context); /** QUERY_BEGIN_INPUT is like QUERY_BEGIN but should be used @@ -602,7 +602,7 @@ Context::querySetterUpdateResult( return QUERY_GET_SAVED(); \ } \ auto QUERY_RECOMPUTATION_MARKER = context->markRecomputing(false); \ - QUERY_BEGIN_TIMING(); + QUERY_BEGIN_TIMING(context); /** Write diff --git a/tools/chpldef/CMakeLists.txt b/tools/chpldef/CMakeLists.txt index 14aafef2a4fd..90529801780f 100644 --- a/tools/chpldef/CMakeLists.txt +++ b/tools/chpldef/CMakeLists.txt @@ -18,9 +18,11 @@ add_executable(chpldef chpldef.cpp command-line-flags.cpp + compute-goto-entity.cpp + compute-lifecycle.cpp + compute-synchronization.cpp Logger.cpp Message.cpp - message-compute.cpp misc.cpp protocol-types.cpp Server.cpp diff --git a/tools/chpldef/Message.h b/tools/chpldef/Message.h index adf4281647b3..127f7c4b6913 100644 --- a/tools/chpldef/Message.h +++ b/tools/chpldef/Message.h @@ -375,6 +375,7 @@ class Request : public BaseRequest { } \ public: \ virtual ~name__() = default; \ + static chpl::owned create(JsonValue id, const Params& p); \ static chpl::owned \ create(JsonValue id, const JsonValue& j); \ virtual void handle(Server* ctx) override; \ diff --git a/tools/chpldef/Server.cpp b/tools/chpldef/Server.cpp index 1cb4e865fd3d..9687423f73ff 100644 --- a/tools/chpldef/Server.cpp +++ b/tools/chpldef/Server.cpp @@ -23,6 +23,7 @@ #include "chpl/framework/Context.h" #include "chpl/framework/UniqueString.h" #include "chpl/framework/query-impl.h" +#include "chpl/parsing/parsing-queries.h" #include "chpl/util/filesystem.h" #include "chpl/util/printf.h" @@ -44,6 +45,12 @@ namespace chpldef { +void Server::ErrorHandler::report(chpl::Context* chapel, + const chpl::ErrorBase* err) { + auto p = std::make_pair(err->clone(), ctx_->revision()); + errors_.push_back(std::move(p)); +} + void Server::setLogger(Logger&& logger) { this->logger_ = std::move(logger); } @@ -68,6 +75,12 @@ void Server::trace(const char* fmt, ...) { Server::Server(Server::Configuration config) : config_(std::move(config)) { chapel_ = createCompilerContext(); + // Install the server error handler. + auto handler = toOwned(new ErrorHandler(this)); + errorHandler_ = handler.get(); + chapel_->installErrorHandler(std::move(handler)); + + // Open the server log. Logger logger; if (!config_.logFile.empty()) { logger = Logger::createForFile(config_.logFile); @@ -103,4 +116,26 @@ bool Server::shouldPrepareToGarbageCollect() { return (revision_ % f) == 1; } +void Server::fmtImpl(std::stringstream& ss, FormatDetail dt, + const chpl::uast::AstNode* t) { + if (t == nullptr) { + ss << ""; + return; + } + + withChapel([&](auto chapel) { + ss << chpl::uast::asttags::tagToString(t->tag()) << " "; + if (auto nd = t->toNamedDecl()) ss << "'" << nd->name().c_str() << "' "; + auto loc = chpl::parsing::locateAst(chapel, t); + ss << "[" << loc.path().c_str() << ":" << loc.firstLine(); + ss << ":" << loc.firstColumn() << "]"; + }); +} + +void Server::fmtImpl(std::stringstream& ss, FormatDetail dt, + const chpl::Location& t) { + ss << t.path().c_str() << ":" << t.firstLine() << ":" << t.firstColumn(); + ss << "-" << t.lastLine() << ":" << t.lastColumn(); +} + } // end namespace 'chpldef' diff --git a/tools/chpldef/Server.h b/tools/chpldef/Server.h index 3b4c11ab219b..4c53d163f2a3 100644 --- a/tools/chpldef/Server.h +++ b/tools/chpldef/Server.h @@ -24,7 +24,9 @@ #include "./Logger.h" #include "./misc.h" #include "chpl/framework/Context.h" +#include "chpl/framework/ErrorBase.h" #include "chpl/framework/UniqueString.h" +#include "chpl/uast/AstNode.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/JSON.h" #include @@ -71,6 +73,23 @@ class Server { using TextRegistry = std::map; + class ErrorHandler : public chpl::Context::ErrorHandler { + public: + using Error = chpl::ErrorBase; + using ErrorAndRevision = std::pair, int64_t>; + using ErrorList = std::vector; + private: + ErrorList errors_; + Server* ctx_ = nullptr; + public: + ErrorHandler(Server* ctx) : ctx_(ctx) {} + ~ErrorHandler() = default; + virtual void report(chpl::Context* chapel, const Error* err) override; + inline const ErrorList& errors() const { return errors_; } + inline void clear() { errors_.clear(); } + inline size_t numErrors() { return errors_.size(); } + }; + private: State state_ = UNINIT; Logger logger_; @@ -78,11 +97,16 @@ class Server { Configuration config_; TextRegistry textRegistry_; int64_t revision_; + ErrorHandler* errorHandler_ = nullptr; chpl::owned createCompilerContext(); bool shouldGarbageCollect(); bool shouldPrepareToGarbageCollect(); + inline bool isLogLevel(Logger::Level level) const { + return logger_.level() == level; + } + protected: friend class chpldef::Initialize; friend class chpldef::Initialized; @@ -90,38 +114,37 @@ class Server { friend class chpldef::DidOpen; inline void setState(State state) { state_ = state; } - inline TextRegistry& textRegistry() { return textRegistry_; }; + inline TextRegistry& textRegistry() { return textRegistry_; } public: Server(Configuration config); ~Server() = default; + inline ErrorHandler* errorHandler() { return errorHandler_; } inline State state() const { return state_; } inline int64_t revision() const { return revision_; } inline const chpl::Context* chapel() const { return chapel_.get(); } void setLogger(Logger&& logger); inline Logger& logger() { return logger_; } - void sleep(int msec); - + inline bool isLogMessage() const { return isLogLevel(Logger::MESSAGES); } + inline bool isLogVerbose() const { return isLogLevel(Logger::VERBOSE); } + inline bool isLogTrace() const { return isLogLevel(Logger::TRACE); } CHPLDEF_PFMT(2, 3, void message(const char* fmt, ...)); CHPLDEF_PFMT(2, 3, void verbose(const char* fmt, ...)); CHPLDEF_PFMT(2, 3, void trace(const char* fmt, ...)); - enum WithChapelContextConfig { + void sleep(int msec); + + enum WithChapelConfig { CHPL_NO_MASK = 0, CHPL_BUMP_REVISION = 1, }; /** Execute code with controlled access to the Chapel context. */ - template - T withChapelContext(F& f, Ns... ns) { - return withChapelContext(CHPL_NO_MASK, f, ns...); - } - - /** Execute code with controlled access to the Chapel context. */ - template - T withChapelContext(WithChapelContextConfig c, F&& f, Ns... ns) { + template + auto withChapel(WithChapelConfig c, F&& f, Ns... ns) + -> decltype(f(chapel_.get(), ns...)) { if (shouldGarbageCollect()) chapel_->collectGarbage(); if (c & CHPL_BUMP_REVISION) { chapel_->advanceToNextRevision(shouldPrepareToGarbageCollect()); @@ -129,6 +152,30 @@ class Server { } return f(chapel_.get(), ns...); } + + /** Execute code with controlled access to the Chapel context. */ + template + auto withChapel(F&& f, Ns... ns) + -> decltype(withChapel(CHPL_NO_MASK, f, ns...)) { + return withChapel(CHPL_NO_MASK, f, ns...); + } + + enum FormatDetail { + DEFAULT = 0 + }; + +private: + void fmtImpl(std::stringstream& ss, FormatDetail dt, + const chpl::uast::AstNode* t); + void fmtImpl(std::stringstream& ss, FormatDetail dt, + const chpl::Location& t); +public: + template + std::string fmt(const T& t, FormatDetail dt=FormatDetail::DEFAULT) { + std::stringstream ss; + fmtImpl(ss, dt, t); + return ss.str(); + } }; } // end namespace 'chpldef' diff --git a/tools/chpldef/chpldef.cpp b/tools/chpldef/chpldef.cpp index 2a90bc5d0675..6cf750fcf82a 100644 --- a/tools/chpldef/chpldef.cpp +++ b/tools/chpldef/chpldef.cpp @@ -39,17 +39,20 @@ static Server::Configuration prepareServerConfig(int argc, char** argv) { std::cerr << "No value for '$CHPL_HOME'!" << std::endl; } - // TODO: Wire these up to command-line flags. ret.logFile = cmd::logFile; ret.logLevel = cmd::logLevel; - ret.garbageCollectionFrequency = 0; - ret.warnUnstable = false; - ret.enableStandardLibrary = false; - ret.compilerDebugTrace = false; + ret.garbageCollectionFrequency = cmd::garbageCollectionFrequency; + ret.warnUnstable = cmd::warnUnstable; + ret.enableStandardLibrary = cmd::enableStandardLibrary; + ret.compilerDebugTrace = cmd::compilerDebugTrace; return ret; } +static chpl::owned maybePublishDiagnostics(Server* ctx) { + return nullptr; +} + int main(int argc, char** argv) { auto config = prepareServerConfig(argc, argv); Server context(std::move(config)); @@ -103,14 +106,18 @@ int main(int argc, char** argv) { CHPL_ASSERT(msg->status() == Message::PENDING); CHPL_ASSERT(!msg->isOutbound()); + // Prepare to exit if we received the 'Exit' message. if (msg->tag() == Message::Exit) { CHPL_ASSERT(ctx->state() == Server::SHUTDOWN); run = 0; } - // We have an immediate response, so send it. - if (auto optRsp = Message::handle(ctx, msg.get())) { - auto pack = optRsp->pack(); + // Handle the request that was read. + auto rsp = Message::handle(ctx, msg.get()); + + // Maybe send a response. + if (rsp) { + auto pack = rsp->pack(); if (log.level() == Logger::TRACE) { auto str = jsonToString(pack); ctx->trace("Outgoing JSON is %s\n", str.c_str()); @@ -120,10 +127,17 @@ int main(int argc, char** argv) { CHPL_ASSERT(ok); // Response was delayed. - } else { - if (!msg->isNotification()) { - CHPLDEF_FATAL(ctx, "Handler response should not be delayed!"); - } + } else if (!msg->isNotification()) { + CHPLDEF_FATAL(ctx, "Handler response should not be delayed!"); + } + + // TODO: Enqueue all messages onto an event queue instead. + if (auto notify = maybePublishDiagnostics(ctx)) { + CHPL_ASSERT(notify->isNotification()); + CHPL_ASSERT(notify->isOutbound()); + auto pack = notify->pack(); + ok = Transport::sendJsonBlocking(ctx, std::cout, std::move(pack)); + CHPL_ASSERT(ok); } // Flush the log in case something goes wrong. diff --git a/tools/chpldef/compute-goto-entity.cpp b/tools/chpldef/compute-goto-entity.cpp new file mode 100644 index 000000000000..bc6ee9cc4d39 --- /dev/null +++ b/tools/chpldef/compute-goto-entity.cpp @@ -0,0 +1,263 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "./Message.h" +#include "./Server.h" +#include "chpl/framework/query-impl.h" +#include "chpl/parsing/parsing-queries.h" +#include "chpl/resolution/resolution-queries.h" +#include "chpl/resolution/resolution-types.h" + +namespace chpldef { + +static const chpl::uast::BuilderResult& +parseFromPath(chpl::Context* chapel, chpl::UniqueString path) { + using namespace chpl::parsing; + chpl::UniqueString empty; + auto& ret = parseFileToBuilderResultAndCheck(chapel, path, empty); + return ret; +} + +namespace { + using LineToIdsMap = std::map>; +} + +static bool isSymbolOfInterest(const chpl::uast::AstNode* ast) { + if (ast->isIdentifier()) return true; + if (ast->isDot()) return true; + return false; +} + +// Preorder traversal to collect identifiers in lexical order. +static void doMapLinesInModule(chpl::Context* chapel, + const chpl::uast::BuilderResult& br, + LineToIdsMap& m, + const chpl::uast::AstNode* node) { + for (auto ast : node->children()) { + if (ast->isComment()) continue; + + if (isSymbolOfInterest(ast)) { + auto loc = br.idToLocation(ast->id(), chpl::UniqueString()); + CHPL_ASSERT(!loc.isEmpty()); + auto& v = m[loc.firstLine()]; + v.push_back(ast->id()); + } + + bool recurse = !ast->isModule() && !ast->isLeaf(); + if (recurse) { + doMapLinesInModule(chapel, br, m, ast); + } + } +} + +static LineToIdsMap +mapLinesToIdsInModule(chpl::Context* chapel, chpl::UniqueString path) { + auto& br = parseFromPath(chapel, path); + LineToIdsMap ret; + auto it = br.topLevelExpressions(); + for (auto ast : it) doMapLinesInModule(chapel, br, ret, ast); + return ret; +} + +static const LineToIdsMap& +mapLinesToIdsInModuleQuery(chpl::Context* chapel, chpl::UniqueString path) { + using namespace chpl; + QUERY_BEGIN(mapLinesToIdsInModuleQuery, chapel, path); + auto ret = mapLinesToIdsInModule(chapel, path); + return QUERY_END(ret); +} + +static chpl::Location +chplLocFromUriAndPos(chpl::Context* chapel, const std::string& uri, + Position pos) { + auto path = chpl::UniqueString::get(chapel, uri); + int l1 = pos.line + 1; + int c1 = pos.character + 1; + int l2 = pos.line + 1; + int c2 = pos.character + 1; + return chpl::Location(path, l1, c1, l2, c2); +} + +static const chpl::uast::AstNode* +chooseAstClosestToLocation(chpl::Context* chapel, + const chpl::uast::AstNode* astOld, + const chpl::uast::AstNode* astNew, + chpl::Location loc) { + using namespace chpl::parsing; + CHPL_ASSERT(astNew); + CHPL_ASSERT(astOld != astNew); + + auto locOld = astOld ? locateAst(chapel, astOld) : chpl::Location(); + auto locNew = locateAst(chapel, astNew); + if (!locNew.contains(loc)) return astOld; + if (!astOld) return astNew; + + // Prefer the location furthest towards the end of the range. + auto ret = (locNew > locOld) ? astNew : astOld; + return ret; +} + +static const chpl::uast::AstNode* +astAtLocation(Server* ctx, chpl::Location loc) { + const auto& m = ctx->withChapel(mapLinesToIdsInModuleQuery, + loc.path()); + auto it = m.find(loc.firstLine()); + if (it == m.end()) return nullptr; + + ctx->trace("Found '%zu' AST nodes on line '%d'\n", + it->second.size(), + loc.firstLine()); + + const chpl::uast::AstNode* ret = nullptr; + for (auto id : it->second) { + auto last = ret; + ctx->withChapel([&](auto chapel) { + auto ast = chpl::parsing::idToAst(chapel, id); + + if (ctx->isLogTrace()) { + ctx->trace("Considering '%s' and '%s'\n", + ctx->fmt(ret).c_str(), + ctx->fmt(ast).c_str()); + } + + ret = chooseAstClosestToLocation(chapel, ret, ast, loc); + + if (ctx->isLogTrace()) { + ctx->trace("Chose '%s'\n", ctx->fmt(ret).c_str()); + } + + }); + if (ret == last) break; + } + + return ret; +} + +static const chpl::uast::Module* +closestModule(chpl::Context* chapel, const chpl::uast::AstNode* ast) { + if (auto mod = ast->toModule()) return mod; + if (auto id = chpl::parsing::idToParentModule(chapel, ast->id())) { + auto mod = chpl::parsing::idToAst(chapel, id); + return mod->toModule(); + } + return nullptr; +} + +static const chpl::resolution::ResolutionResultByPostorderID& +resolveModule(Server* ctx, chpl::ID idMod) { + return ctx->withChapel(chpl::resolution::resolveModule, idMod); +} + +static std::vector +sourceAstToIds(Server* ctx, const chpl::uast::AstNode* ast) { + std::vector ret; + + if (!ast) return ret; + + auto mod = ctx->withChapel(closestModule, ast); + CHPL_ASSERT(mod); + + if (mod == ast) CHPLDEF_TODO(); + + ctx->trace("Target '%s' in containing module '%s'\n", + ctx->fmt(ast).c_str(), + ctx->fmt(mod).c_str()); + + auto& rr = resolveModule(ctx, mod->id()); + auto& re = rr.byAst(ast); + + if (re.isBuiltin()) CHPLDEF_TODO(); + + ctx->trace("Encountered '%zu' errors resolving\n", + ctx->errorHandler()->numErrors()); + + // First do a simple check for target location. + if (auto idTarget = re.toId()) { + ret.push_back(idTarget); + + // Else, more work is required... + } else { + CHPLDEF_TODO(); + } + + return ret; +} + +static bool chplLocIsValid(const chpl::Location& loc) { + return !loc.isEmpty() && loc.firstLine() >= 1 && + loc.firstColumn() >= 1 && + loc.lastLine() >= 1 && + loc.lastColumn() >= 1; +} + +static std::variant +computeDeclarationPoints(Server* ctx, const TextDocumentPositionParams& p) { + auto& uri = p.textDocument.uri; + auto& pos = p.position; + auto loc = ctx->withChapel(chplLocFromUriAndPos, uri, pos); + + ctx->verbose("Looking for AST at '%s'\n", ctx->fmt(loc).c_str()); + + auto ast = astAtLocation(ctx, loc); + auto ids = sourceAstToIds(ctx, ast); + + ctx->verbose("Found '%zu' target IDs\n", ids.size()); + + // TODO: Use this info to construct a 'LocationLinkArray' instead. + LocationArray ret; + for (auto& id : ids) { + auto loc = ctx->withChapel(chpl::parsing::locateId, id); + + ctx->trace("Found entity at '%s'\n", ctx->fmt(loc).c_str()); + + if (chplLocIsValid(loc)) { + Position start = { static_cast(loc.firstLine()-1), + static_cast(loc.firstColumn()-1) }; + Position end = { static_cast(loc.lastLine()-1), + static_cast(loc.lastColumn()-1) }; + Location out; + out.range = { start, end }; + out.uri = loc.path().str(); + + ret.push_back(std::move(out)); + } + } + + return ret; +} + +Declaration::ComputedResult +Declaration::compute(Server* ctx, const Params& p) { + DeclarationResult ret; + ret.result = computeDeclarationPoints(ctx, p); + return ret; +} + +/** This functionality is turned off in the server capabilities tab, + because Chapel does not distinguish between a definition and a + declaration in the way that some languages such as C/C++ do. */ +Definition::ComputedResult +Definition::compute(Server* ctx, const Params& p) { + DefinitionResult ret; + ret.result = computeDeclarationPoints(ctx, p); + return ret; +} + +} // end namespace 'chpldef' diff --git a/tools/chpldef/message-compute.cpp b/tools/chpldef/compute-lifecycle.cpp similarity index 52% rename from tools/chpldef/message-compute.cpp rename to tools/chpldef/compute-lifecycle.cpp index 36f84806f3a0..e87a302c35af 100644 --- a/tools/chpldef/message-compute.cpp +++ b/tools/chpldef/compute-lifecycle.cpp @@ -22,6 +22,12 @@ #include "./Server.h" #include "chpl/parsing/parsing-queries.h" +namespace { + static constexpr bool UNSUPPORTED_DELIBERATELY = false; + static constexpr bool UNSUPPORTED_TODO = false; + static constexpr bool SUPPORTED = true; +} + /** This is one file where message handlers can be implemented. However, if a particular message's handler grows to be very large in size (e.g., perhaps 500+ lines), then we should feel free to move it to a different @@ -29,43 +35,43 @@ namespace chpldef { static TextDocumentSyncOptions -configureTextDocumentSyncOptions(Server* ctx) { +configureTextDocumentSync(Server* ctx) { TextDocumentSyncOptions ret; - ret.openClose = true; - ret.change = TextDocumentSyncOptions::Full; /** TODO: Incremental? */ - ret.willSave = true; - ret.willSaveWaitUntil = true; - ret.save = SaveOptions(); + ret.openClose = SUPPORTED; + ret.change = TextDocumentSyncOptions::Full; + ret.willSave = SUPPORTED; + ret.willSaveWaitUntil = SUPPORTED; + ret.save = SaveOptions(); ret.save->includeText = true; return ret; } static void doConfigureStaticCapabilities(Server* ctx, ServerCapabilities& x) { - x.positionEncoding = "utf-16"; - x.textDocumentSync = configureTextDocumentSyncOptions(ctx); - x.hoverProvider = false; - x.declarationProvider = false; - x.definitionProvider = false; - x.typeDefinitionProvider = false; - x.implementationProvider = false; - x.referencesProvider = false; - x.documentHighlightProvider = false; - x.documentSymbolProvider = false; - x.codeActionProvider = false; - x.colorProvider = false; - x.documentFormattingProvider = false; - x.documentRangeFormattingProvider = false; - x.renameProvider = false; - x.foldingRangeProvider = false; - x.selectionRangeProvider = false; - x.linkEditingRangeProvider = false; - x.callHierarchyProvider = false; - x.monikerProvider = false; - x.typeHierarchyProvider = false; - x.inlineValueProvider = false; - x.inlayHintProvider = false; - x.workspaceSymbolProvider = false; + x.positionEncoding = "utf-16"; + x.textDocumentSync = configureTextDocumentSync(ctx); + x.hoverProvider = UNSUPPORTED_TODO; + x.declarationProvider = SUPPORTED; + x.definitionProvider = UNSUPPORTED_DELIBERATELY; + x.typeDefinitionProvider = UNSUPPORTED_TODO; + x.implementationProvider = UNSUPPORTED_TODO; + x.referencesProvider = UNSUPPORTED_TODO; + x.documentHighlightProvider = UNSUPPORTED_TODO; + x.documentSymbolProvider = UNSUPPORTED_TODO; + x.codeActionProvider = UNSUPPORTED_TODO; + x.colorProvider = UNSUPPORTED_TODO; + x.documentFormattingProvider = UNSUPPORTED_TODO; + x.documentRangeFormattingProvider = UNSUPPORTED_TODO; + x.renameProvider = UNSUPPORTED_TODO; + x.foldingRangeProvider = UNSUPPORTED_TODO; + x.selectionRangeProvider = UNSUPPORTED_TODO; + x.linkEditingRangeProvider = UNSUPPORTED_TODO; + x.callHierarchyProvider = UNSUPPORTED_TODO; + x.monikerProvider = UNSUPPORTED_TODO; + x.typeHierarchyProvider = UNSUPPORTED_TODO; + x.inlineValueProvider = UNSUPPORTED_TODO; + x.inlayHintProvider = UNSUPPORTED_TODO; + x.workspaceSymbolProvider = UNSUPPORTED_TODO; } static ServerInfo configureServerInfo(Server* ctx) { @@ -124,54 +130,4 @@ Exit::compute(Server* ctx, const Params& p) { return {}; } -DidOpen::ComputedResult -DidOpen::compute(Server* ctx, const Params& p) { - auto& tdi = p.textDocument; - auto& e = ctx->textRegistry()[tdi.uri]; - - if (e.isOpen) { - CHPLDEF_TODO(); - return fail(); - } - - CHPL_ASSERT(tdi.version > e.version); - - // NOTE: I think we always have to bump the revision here. This is - // because this file may have been implicitly parsed from disk as - // as result of resolving a use/import. The contents are considered - // to have changed and the "truth of the file's contents" are determined - // by the client as long as it has the file open. Cannot implicitly - // read from disk, so have to bump the revision to ensure correctness. - ctx->withChapelContext(Server::CHPL_BUMP_REVISION, - [&](auto chapel) { - chpl::parsing::setFileText(chapel, tdi.uri, tdi.text); - auto& fc = chpl::parsing::fileText(chapel, tdi.uri); - CHPL_ASSERT(!fc.error()); - CHPL_ASSERT(fc.text() == tdi.text); - e.version = tdi.version; - e.lastRevisionContentsUpdated = ctx->revision(); - e.isOpen = true; - }); - - return {}; -} - -DidChange::ComputedResult -DidChange::compute(Server* ctx, const Params& p) { - CHPLDEF_TODO(); - return {}; -} - -DidSave::ComputedResult -DidSave::compute(Server* ctx, const Params& p) { - CHPLDEF_TODO(); - return {}; -} - -DidClose::ComputedResult -DidClose::compute(Server* ctx, const Params& p) { - CHPLDEF_TODO(); - return {}; -} - } // end namespace 'chpldef' diff --git a/tools/chpldef/compute-synchronization.cpp b/tools/chpldef/compute-synchronization.cpp new file mode 100644 index 000000000000..62e70f713d7d --- /dev/null +++ b/tools/chpldef/compute-synchronization.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "./Message.h" +#include "./Server.h" +#include "chpl/parsing/parsing-queries.h" + +/** This is one file where message handlers can be implemented. However, if + a particular message's handler grows to be very large in size (e.g., + perhaps 500+ lines), then we should feel free to move it to a different + source file with a name that matches the message name. */ +namespace chpldef { + +DidOpen::ComputedResult +DidOpen::compute(Server* ctx, const Params& p) { + auto& tdi = p.textDocument; + auto& e = ctx->textRegistry()[tdi.uri]; + + if (e.isOpen) { + CHPLDEF_TODO(); + return fail(); + } + + CHPL_ASSERT(tdi.version > e.version); + + // NOTE: I think we always have to bump the revision here. This is + // because this file may have been implicitly parsed from disk as + // as result of resolving a use/import. The contents are considered + // to have changed and the "truth of the file's contents" are determined + // by the client as long as it has the file open. Cannot implicitly + // read from disk, so have to bump the revision to ensure correctness. + ctx->withChapel(Server::CHPL_BUMP_REVISION, + [&](auto chapel) { + chpl::parsing::setFileText(chapel, tdi.uri, tdi.text); + auto& fc = chpl::parsing::fileText(chapel, tdi.uri); + CHPL_ASSERT(!fc.error()); + CHPL_ASSERT(fc.text() == tdi.text); + e.version = tdi.version; + e.lastRevisionContentsUpdated = ctx->revision(); + e.isOpen = true; + }); + + return {}; +} + +DidChange::ComputedResult +DidChange::compute(Server* ctx, const Params& p) { + CHPLDEF_TODO(); + return {}; +} + +DidSave::ComputedResult +DidSave::compute(Server* ctx, const Params& p) { + CHPLDEF_TODO(); + return {}; +} + +DidClose::ComputedResult +DidClose::compute(Server* ctx, const Params& p) { + CHPLDEF_TODO(); + return {}; +} + +} // end namespace 'chpldef' diff --git a/tools/chpldef/message-macro-list.h b/tools/chpldef/message-macro-list.h index b2a8a0771d89..1ca8d5055743 100644 --- a/tools/chpldef/message-macro-list.h +++ b/tools/chpldef/message-macro-list.h @@ -41,6 +41,8 @@ CHPLDEF_MESSAGE(DidChange, 0, 1, textDocument/didChange) CHPLDEF_MESSAGE(DidSave, 0, 1, textDocument/didSave) CHPLDEF_MESSAGE(DidClose, 0, 1, textDocument/didClose) +CHPLDEF_MESSAGE(Declaration, 0, 0, textDocument/declaration) +CHPLDEF_MESSAGE(Definition, 0, 0, textDocument/definition) /* CHPLDEF_MESSAGE(RegisterCapability, client/registerCapability) @@ -83,8 +85,6 @@ CHPLDEF_MESSAGE(NotebookDidClose, notebookDocument/didClose) // /* -CHPLDEF_MESSAGE(GotoDeclaration, textDocument/declaration) -CHPLDEF_MESSAGE(GotoDefinition, textDocument/definition) CHPLDEF_MESSAGE(GotoTypeDefinition, textDocument/typeDefinition) CHPLDEF_MESSAGE(GotoImplementation, textDocument/implementation) CHPLDEF_MESSAGE(FindReferences, textDocument/references) diff --git a/tools/chpldef/protocol-types.cpp b/tools/chpldef/protocol-types.cpp index 80da84f32916..5c961d48a85b 100644 --- a/tools/chpldef/protocol-types.cpp +++ b/tools/chpldef/protocol-types.cpp @@ -24,8 +24,21 @@ #include #include +template +static void doAddField(chpldef::JsonObject& obj, const char* name, + const T& field) { + obj[name] = field; +} + +template +static void doAddField(chpldef::JsonObject& obj, const char* name, + const chpldef::opt& field) { + if (field) obj[name] = field; +} + /** Helper to make populating JSON object fields less painful. */ -#define FIELD_(name__) { #name__, name__ } +#define FIELD_(obj__, name__) \ + do { doAddField(obj__, #name__, name__); } while (0) /** Helper to make reading JSON object fields less painful. */ #define MAP_(m__, name__) (m__.map(#name__, name__)) @@ -89,10 +102,9 @@ bool InitializeParams::fromJson(const JsonValue& j, JsonPath p) { } JsonValue InitializeResult::toJson() const { - JsonObject ret { - FIELD_(capabilities), - FIELD_(serverInfo) - }; + JsonObject ret; + FIELD_(ret, capabilities); + FIELD_(ret, serverInfo); return ret; } @@ -113,59 +125,60 @@ bool TraceLevel::fromJson(const JsonValue& j, JsonPath p) { } bool WorkspaceFolder::fromJson(const JsonValue& j, JsonPath p) { - CHPLDEF_TODO(); - return true; + JsonMapper m(j, p); + return m && MAP_(m, uri) && MAP_(m, name); } JsonValue ServerInfo::toJson() const { - JsonObject ret { FIELD_(name), FIELD_(version) }; + JsonObject ret; + FIELD_(ret, name); + FIELD_(ret, version); return ret; } JsonValue ServerCapabilities::toJson() const { - JsonObject ret { - FIELD_(positionEncoding), - FIELD_(textDocumentSync), - FIELD_(notebookDocumentSync), - FIELD_(completionProvider), - FIELD_(hoverProvider), - FIELD_(signatureHelpProvider), - FIELD_(declarationProvider), - FIELD_(definitionProvider), - FIELD_(typeDefinitionProvider), - FIELD_(implementationProvider), - FIELD_(referencesProvider), - FIELD_(documentHighlightProvider), - FIELD_(documentSymbolProvider), - FIELD_(codeActionProvider), - FIELD_(codeLensProvider), - FIELD_(documentLinkProvider), - FIELD_(colorProvider), - FIELD_(documentFormattingProvider), - FIELD_(documentRangeFormattingProvider), - FIELD_(documentOnTypeFormattingProvider), - FIELD_(renameProvider), - FIELD_(foldingRangeProvider), - FIELD_(executeCommandProvider), - FIELD_(selectionRangeProvider), - FIELD_(linkEditingRangeProvider), - FIELD_(callHierarchyProvider), - FIELD_(semanticTokensProvider), - FIELD_(monikerProvider), - FIELD_(typeHierarchyProvider), - FIELD_(inlineValueProvider), - FIELD_(inlayHintProvider), - FIELD_(diagnosticProvider), - FIELD_(workspaceSymbolProvider), - FIELD_(workspace), - FIELD_(experimental) - }; + JsonObject ret; + FIELD_(ret, positionEncoding); + FIELD_(ret, textDocumentSync); + FIELD_(ret, notebookDocumentSync); + FIELD_(ret, completionProvider); + FIELD_(ret, hoverProvider); + FIELD_(ret, signatureHelpProvider); + FIELD_(ret, declarationProvider); + FIELD_(ret, definitionProvider); + FIELD_(ret, typeDefinitionProvider); + FIELD_(ret, implementationProvider); + FIELD_(ret, referencesProvider); + FIELD_(ret, documentHighlightProvider); + FIELD_(ret, documentSymbolProvider); + FIELD_(ret, codeActionProvider); + FIELD_(ret, codeLensProvider); + FIELD_(ret, documentLinkProvider); + FIELD_(ret, colorProvider); + FIELD_(ret, documentFormattingProvider); + FIELD_(ret, documentRangeFormattingProvider); + FIELD_(ret, documentOnTypeFormattingProvider); + FIELD_(ret, renameProvider); + FIELD_(ret, foldingRangeProvider); + FIELD_(ret, executeCommandProvider); + FIELD_(ret, selectionRangeProvider); + FIELD_(ret, linkEditingRangeProvider); + FIELD_(ret, callHierarchyProvider); + FIELD_(ret, semanticTokensProvider); + FIELD_(ret, monikerProvider); + FIELD_(ret, typeHierarchyProvider); + FIELD_(ret, inlineValueProvider); + FIELD_(ret, inlayHintProvider); + FIELD_(ret, diagnosticProvider); + FIELD_(ret, workspaceSymbolProvider); + FIELD_(ret, workspace); + FIELD_(ret, experimental); return ret; } JsonValue TextDocumentSyncOptions::toJson() const { JsonObject ret; - if (openClose) ret["openClose"] = *openClose; + FIELD_(ret, openClose); if (change) ret["change"] = static_cast(*change); return ret; } @@ -201,8 +214,86 @@ bool DidChangeParams::fromJson(const JsonValue& j, JsonPath p) { } JsonValue SaveOptions::toJson() const { - JsonObject ret { FIELD_(includeText) }; + JsonObject ret; + FIELD_(ret, includeText); + return ret; +} + +bool TextDocumentIdentifier::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, uri); +} + +bool Position::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, line) && MAP_(m, character); +} + +JsonValue Position::toJson() const { + JsonObject ret; + FIELD_(ret, line); + FIELD_(ret, character); + return ret; +} + +bool TextDocumentPositionParams::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, textDocument) && MAP_(m, position); +} + +bool Location::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, uri) && MAP_(m, range); +} + +JsonValue Location::toJson() const { + JsonObject ret; + FIELD_(ret, uri); + FIELD_(ret, range); return ret; } +bool LocationLink::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, originSelectionRange) && + MAP_(m, targetUri) && + MAP_(m, targetRange) && + MAP_(m, targetSelectionRange); +} + +JsonValue LocationLink::toJson() const { + JsonObject ret; + FIELD_(ret, originSelectionRange); + FIELD_(ret, targetUri); + FIELD_(ret, targetRange); + FIELD_(ret, targetSelectionRange); + return ret; +} + +bool Range::fromJson(const JsonValue& j, JsonPath p) { + JsonMapper m(j, p); + return m && MAP_(m, start) && MAP_(m, end); +} + +JsonValue Range::toJson() const { + JsonObject ret; + FIELD_(ret, start); + FIELD_(ret, end); + return ret; +} + +JsonValue DeclarationResult::toJson() const { + if (!result) return nullptr; + if (auto v = std::get_if(&*result)) return *v; + if (auto v = std::get_if>(&*result)) return *v; + return nullptr; +} + +JsonValue DefinitionResult::toJson() const { + if (!result) return nullptr; + if (auto v = std::get_if>(&*result)) return *v; + if (auto v = std::get_if>(&*result)) return *v; + return nullptr; +} + } // end namespace 'chpldef' diff --git a/tools/chpldef/protocol-types.h b/tools/chpldef/protocol-types.h index 2d1f327e6314..65b11d35fe03 100644 --- a/tools/chpldef/protocol-types.h +++ b/tools/chpldef/protocol-types.h @@ -24,6 +24,8 @@ #include "./Logger.h" #include "./misc.h" #include +#include +#include /** This header contains types which help form the Microsoft language server protocol. The types attempt to follow the specification as faithfully @@ -224,6 +226,70 @@ struct DidCloseParams : ProtocolTypeRecv { TextDocumentItem textDocument; }; +struct TextDocumentIdentifier : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + std::string uri; +}; + +struct Position : ProtocolType { + uint64_t line = 0; /** Zero-based position. */ + uint64_t character = 0; /** Zero-based position. */ + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + virtual JsonValue toJson() const override; + Position() = default; + Position(uint64_t line, uint64_t character) + : line(line), character(character) {} +}; + +struct Range : ProtocolType { + Position start; + Position end; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + virtual JsonValue toJson() const override; + Range() = default; + Range(Position start, Position end) : start(start), end(end) {} +}; + +struct TextDocumentPositionParams : ProtocolTypeRecv { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + TextDocumentIdentifier textDocument; + Position position; +}; + +struct Location : ProtocolType { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + virtual JsonValue toJson() const override; + std::string uri; + Range range; +}; + +struct LocationLink : ProtocolType { + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + virtual JsonValue toJson() const override; + opt originSelectionRange; + std::string targetUri; + Range targetRange; + Range targetSelectionRange; +}; + +using LocationArray = std::vector; +using LocationLinkArray = std::vector; + +struct DeclarationParams : TextDocumentPositionParams {}; +struct DefinitionParams : TextDocumentPositionParams {}; + +struct DeclarationResult : ProtocolTypeSend { + virtual JsonValue toJson() const override; + opt> result; +}; + +struct DefinitionResult : ProtocolTypeSend { + virtual JsonValue toJson() const override; + opt> result; +}; + /** Instantiate only if 'T' is derived from 'ProtocolType'. */ template CHPLDEF_ENABLE_IF_DERIVED(T, BaseProtocolType, bool) From e6fcf5f597d4b053f3dd6d8439f0b6524ae41944 Mon Sep 17 00:00:00 2001 From: David Longnecker Date: Sun, 4 Jun 2023 00:11:40 -0700 Subject: [PATCH 18/27] Set up unit tests for 'chpldef' by copying 'dyno' unit tests Signed-off-by: David Longnecker --- Makefile | 9 ++--- Makefile.devel | 3 ++ compiler/Makefile | 8 +++++ third-party/Makefile | 3 -- third-party/chpl-venv/Makefile | 2 -- tools/chpldef/CMakeLists.txt | 27 +++++++++++--- ...ntity.cpp => compute-goto-declaration.cpp} | 0 tools/chpldef/compute-lifecycle.cpp | 4 --- tools/chpldef/compute-synchronization.cpp | 4 --- tools/chpldef/test/CMakeLists.txt | 36 +++++++++++++++++++ tools/chpldef/test/test-lifecycle.cpp | 23 ++++++++++++ tools/chpldef/test/utility.cpp | 20 +++++++++++ tools/chpldef/test/utility.h | 24 +++++++++++++ 13 files changed, 138 insertions(+), 25 deletions(-) rename tools/chpldef/{compute-goto-entity.cpp => compute-goto-declaration.cpp} (100%) create mode 100644 tools/chpldef/test/CMakeLists.txt create mode 100644 tools/chpldef/test/test-lifecycle.cpp create mode 100644 tools/chpldef/test/utility.cpp create mode 100644 tools/chpldef/test/utility.h diff --git a/Makefile b/Makefile index 4e2eb86c1407..90de852e535c 100644 --- a/Makefile +++ b/Makefile @@ -111,11 +111,6 @@ third-party-chpldoc-venv: FORCE cd third-party && $(MAKE) chpldoc-venv; \ fi -third-party-chpldef-venv: FORCE - @if [ -z "$$CHPL_DONT_BUILD_CHPLDEF_VENV" ]; then \ - cd third-party && $(MAKE) chpldef-venv; \ - fi - third-party-c2chapel-venv: FORCE @if [ -z "$$CHPL_DONT_BUILD_C2CHAPEL_VENV" ]; then \ cd third-party && $(MAKE) c2chapel-venv; \ @@ -129,13 +124,13 @@ chpldoc: third-party-chpldoc-venv @cd modules && $(MAKE) @test -r Makefile.devel && $(MAKE) man-chpldoc || echo "" -chpldef: third-party-chpldef-venv FORCE +chpldef: FORCE @echo "Making chpldef..." @cd third-party && $(MAKE) llvm cd compiler && $(MAKE) chpldef @cd modules && $(MAKE) -chpldef-fast: +chpldef-fast: FORCE cd compiler && $(MAKE) chpldef-fast always-build-test-venv: FORCE diff --git a/Makefile.devel b/Makefile.devel index b7c4862b75e7..ef0b60ee4345 100644 --- a/Makefile.devel +++ b/Makefile.devel @@ -74,6 +74,9 @@ run-frontend-linters: FORCE run-dyno-linters: run-frontend-linters FORCE +test-chpldef: FORCE + @cd compiler && $(MAKE) test-chpldef + SPECTEST_DIR = ./test/release/examples/spec spectests: FORCE rm -rf $(SPECTEST_DIR) diff --git a/compiler/Makefile b/compiler/Makefile index cd6070bcd9e0..b7523191c109 100644 --- a/compiler/Makefile +++ b/compiler/Makefile @@ -215,6 +215,14 @@ chpldef: FORCE $(CHPLDEF) chpldef-fast: FORCE @cd $(COMPILER_BUILD) && $(MAKE) chpldef +test-chpldef: FORCE $(CHPLDEF) + @echo "Making and running the chpldef tests..." + @cd $(COMPILER_BUILD) && $(CMAKE) $(CHPL_MAKE_HOME) $(CMAKE_FLAGS_NO_NDEBUG) && $(MAKE) chpldef-tests + JOBSFLAG=`echo "$$MAKEFLAGS" | sed -n 's/.*\(-j\|--jobs=\) *\([0-9][0-9]*\).*/-j\2/p'` ; \ + cd $(COMPILER_BUILD)/tools/chpldef/test && ctest $$JOBSFLAG . ; + @echo "Making symbolic link to chpldef tests in build/chpldef-test" + @cd ../build && rm -f chpldef-test && ln -s $(COMPILER_BUILD)/tools/chpldef/test chpldef-test + $(COMPILER_BUILD): mkdir -p $@ diff --git a/third-party/Makefile b/third-party/Makefile index 62d9a8a86248..970e218093cd 100644 --- a/third-party/Makefile +++ b/third-party/Makefile @@ -87,9 +87,6 @@ test-venv: FORCE chpldoc-venv: FORCE cd chpl-venv && $(MAKE) chpldoc-venv -chpldef-venv: FORCE - cd chpl-venv && $(MAKE) chpldef-venv - c2chapel-venv: FORCE cd chpl-venv && $(MAKE) c2chapel-venv diff --git a/third-party/chpl-venv/Makefile b/third-party/chpl-venv/Makefile index 50ece1506a17..107cddb90f70 100644 --- a/third-party/chpl-venv/Makefile +++ b/third-party/chpl-venv/Makefile @@ -90,8 +90,6 @@ test-venv: install-chpldeps chpldoc-venv: install-chpldeps -chpldef-venv: install-chpldeps - c2chapel-venv: install-chpldeps install-requirements: install-chpldeps diff --git a/tools/chpldef/CMakeLists.txt b/tools/chpldef/CMakeLists.txt index 90529801780f..ccea20997d8f 100644 --- a/tools/chpldef/CMakeLists.txt +++ b/tools/chpldef/CMakeLists.txt @@ -15,10 +15,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -add_executable(chpldef - chpldef.cpp +# Put all the binary source files into an object blob. +add_library(chpldef-objects command-line-flags.cpp - compute-goto-entity.cpp + compute-goto-declaration.cpp compute-lifecycle.cpp compute-synchronization.cpp Logger.cpp @@ -27,9 +27,26 @@ add_executable(chpldef protocol-types.cpp Server.cpp Transport.cpp) -set_property(TARGET chpldef PROPERTY CXX_STANDARD 17) -target_link_libraries(chpldef ChplFrontend) + +# Link the Chapel frontend to the object blob. +target_link_libraries(chpldef-objects ChplFrontend) +target_include_directories(chpldef-objects PRIVATE + ${CHPL_MAIN_INCLUDE_DIR} + ${CHPL_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}) +set_property(TARGET chpldef-objects PROPERTY CXX_STANDARD 17) + +# Create the 'chpldef' executable. +add_executable(chpldef chpldef.cpp) +target_link_libraries(chpldef chpldef-objects) target_include_directories(chpldef PRIVATE ${CHPL_MAIN_INCLUDE_DIR} ${CHPL_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) +set_property(TARGET chpldef PROPERTY CXX_STANDARD 17) + +# Enable testing if the test folder exists. +if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test") + enable_testing() + add_subdirectory(test) +endif() diff --git a/tools/chpldef/compute-goto-entity.cpp b/tools/chpldef/compute-goto-declaration.cpp similarity index 100% rename from tools/chpldef/compute-goto-entity.cpp rename to tools/chpldef/compute-goto-declaration.cpp diff --git a/tools/chpldef/compute-lifecycle.cpp b/tools/chpldef/compute-lifecycle.cpp index e87a302c35af..e2959488e392 100644 --- a/tools/chpldef/compute-lifecycle.cpp +++ b/tools/chpldef/compute-lifecycle.cpp @@ -28,10 +28,6 @@ namespace { static constexpr bool SUPPORTED = true; } -/** This is one file where message handlers can be implemented. However, if - a particular message's handler grows to be very large in size (e.g., - perhaps 500+ lines), then we should feel free to move it to a different - source file with a name that matches the message name. */ namespace chpldef { static TextDocumentSyncOptions diff --git a/tools/chpldef/compute-synchronization.cpp b/tools/chpldef/compute-synchronization.cpp index 62e70f713d7d..4dae02e04c57 100644 --- a/tools/chpldef/compute-synchronization.cpp +++ b/tools/chpldef/compute-synchronization.cpp @@ -22,10 +22,6 @@ #include "./Server.h" #include "chpl/parsing/parsing-queries.h" -/** This is one file where message handlers can be implemented. However, if - a particular message's handler grows to be very large in size (e.g., - perhaps 500+ lines), then we should feel free to move it to a different - source file with a name that matches the message name. */ namespace chpldef { DidOpen::ComputedResult diff --git a/tools/chpldef/test/CMakeLists.txt b/tools/chpldef/test/CMakeLists.txt new file mode 100644 index 000000000000..facfea21098f --- /dev/null +++ b/tools/chpldef/test/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright 2021-2023 Hewlett Packard Enterprise Development LP +# Other additional copyright holders may be indicated within. +# +# The entirety of this work is licensed under the Apache License, +# Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. +# +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_library(chpldef-test-utility OBJECT EXCLUDE_FROM_ALL + utility.cpp) +target_link_libraries(chpldef-test-utility chpldef-objects) + +add_custom_target(chpldef-tests) +set(CHPLDEF_TEST_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}) + +function(chpldef_compile_test target) + add_executable(${target} EXCLUDE_FROM_ALL ${target}.cpp) + set_target_properties(${target} PROPERTIES LINK_DEPENDS_NO_SHARED true + RUNTIME_OUTPUT_DIRECTORY . ) + target_link_libraries(${target} chpldef-objects chpldef-test-utility) + add_test(NAME ${target} COMMAND ${target}) + set_tests_properties(${target} PROPERTIES ENVIRONMENT + "CHPL_HOME=${CHPL_HOME}") + add_dependencies(chpldef-tests ${target}) +endfunction(chpldef_compile_test) + +chpldef_compile_test(test-lifecycle) diff --git a/tools/chpldef/test/test-lifecycle.cpp b/tools/chpldef/test/test-lifecycle.cpp new file mode 100644 index 000000000000..d7609e50675a --- /dev/null +++ b/tools/chpldef/test/test-lifecycle.cpp @@ -0,0 +1,23 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +int main(int argc, char** argv) { + return 0; +} diff --git a/tools/chpldef/test/utility.cpp b/tools/chpldef/test/utility.cpp new file mode 100644 index 000000000000..113b75d63d86 --- /dev/null +++ b/tools/chpldef/test/utility.cpp @@ -0,0 +1,20 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + diff --git a/tools/chpldef/test/utility.h b/tools/chpldef/test/utility.h new file mode 100644 index 000000000000..b0e26790e517 --- /dev/null +++ b/tools/chpldef/test/utility.h @@ -0,0 +1,24 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CHPL_TOOLS_CHPLDEF_TEST_UTILITY_H +#define CHPL_TOOLS_CHPLDEF_TEST_UTILITY_H + +#endif From 6af0d72d800ad1a61d46b05005216e8bb74a5fbf Mon Sep 17 00:00:00 2001 From: David Longnecker Date: Mon, 31 Jul 2023 18:30:01 -0700 Subject: [PATCH 19/27] Refactor language server to use templates, add events and event queue Signed-off-by: David Longnecker --- compiler/Makefile | 4 +- frontend/include/chpl/parsing/FileContents.h | 5 +- tools/chpldef/CMakeLists.txt | 6 +- tools/chpldef/Format.cpp | 48 ++ tools/chpldef/Format.h | 65 +++ tools/chpldef/Logger.cpp | 13 +- tools/chpldef/Logger.h | 14 +- tools/chpldef/Makefile.include | 20 +- tools/chpldef/Message.cpp | 453 ++++++++++++------- tools/chpldef/Message.h | 424 ++++++++++------- tools/chpldef/Server.cpp | 176 +++++-- tools/chpldef/Server.h | 159 +++++-- tools/chpldef/Transport.cpp | 101 +++-- tools/chpldef/Transport.h | 66 ++- tools/chpldef/chpldef.cpp | 89 +--- tools/chpldef/compiler-gadgets.cpp | 108 +++++ tools/chpldef/compiler-gadgets.h | 68 +++ tools/chpldef/compute-goto-declaration.cpp | 112 ++--- tools/chpldef/compute-lifecycle.cpp | 26 +- tools/chpldef/compute-synchronization.cpp | 25 +- tools/chpldef/events.cpp | 113 +++++ tools/chpldef/events.h | 65 +++ tools/chpldef/misc.cpp | 10 + tools/chpldef/misc.h | 3 + tools/chpldef/protocol-types.cpp | 35 ++ tools/chpldef/protocol-types.h | 117 +++-- tools/chpldef/test/.utility.cpp.swp | Bin 0 -> 24576 bytes tools/chpldef/test/CMakeLists.txt | 11 +- tools/chpldef/test/TestClient.cpp | 248 ++++++++++ tools/chpldef/test/TestClient.h | 117 +++++ tools/chpldef/test/test-declaration.cpp | 112 +++++ tools/chpldef/test/utility.cpp | 20 - tools/chpldef/test/utility.h | 24 - 33 files changed, 2127 insertions(+), 730 deletions(-) create mode 100644 tools/chpldef/Format.cpp create mode 100644 tools/chpldef/Format.h create mode 100644 tools/chpldef/compiler-gadgets.cpp create mode 100644 tools/chpldef/compiler-gadgets.h create mode 100644 tools/chpldef/events.cpp create mode 100644 tools/chpldef/events.h create mode 100644 tools/chpldef/test/.utility.cpp.swp create mode 100644 tools/chpldef/test/TestClient.cpp create mode 100644 tools/chpldef/test/TestClient.h create mode 100644 tools/chpldef/test/test-declaration.cpp delete mode 100644 tools/chpldef/test/utility.cpp delete mode 100644 tools/chpldef/test/utility.h diff --git a/compiler/Makefile b/compiler/Makefile index b7523191c109..1a740c37aa5d 100644 --- a/compiler/Makefile +++ b/compiler/Makefile @@ -218,10 +218,10 @@ chpldef-fast: FORCE test-chpldef: FORCE $(CHPLDEF) @echo "Making and running the chpldef tests..." @cd $(COMPILER_BUILD) && $(CMAKE) $(CHPL_MAKE_HOME) $(CMAKE_FLAGS_NO_NDEBUG) && $(MAKE) chpldef-tests - JOBSFLAG=`echo "$$MAKEFLAGS" | sed -n 's/.*\(-j\|--jobs=\) *\([0-9][0-9]*\).*/-j\2/p'` ; \ - cd $(COMPILER_BUILD)/tools/chpldef/test && ctest $$JOBSFLAG . ; @echo "Making symbolic link to chpldef tests in build/chpldef-test" @cd ../build && rm -f chpldef-test && ln -s $(COMPILER_BUILD)/tools/chpldef/test chpldef-test + JOBSFLAG=`echo "$$MAKEFLAGS" | sed -n 's/.*\(-j\|--jobs=\) *\([0-9][0-9]*\).*/-j\2/p'` ; \ + cd $(COMPILER_BUILD)/tools/chpldef/test && ctest $$JOBSFLAG . ; $(COMPILER_BUILD): mkdir -p $@ diff --git a/frontend/include/chpl/parsing/FileContents.h b/frontend/include/chpl/parsing/FileContents.h index 124b4d60647b..aeace742e621 100644 --- a/frontend/include/chpl/parsing/FileContents.h +++ b/frontend/include/chpl/parsing/FileContents.h @@ -28,9 +28,6 @@ #include namespace chpl { - -class ErrorBase; - namespace parsing { @@ -73,7 +70,7 @@ class FileContents { std::swap(error_, other.error_); } static bool update(FileContents& keep, FileContents& addin) { - return defaultUpdate(keep, addin); + return chpl::defaultUpdate(keep, addin); } void mark(Context* context) const { if (error_ != nullptr) error_->mark(context); diff --git a/tools/chpldef/CMakeLists.txt b/tools/chpldef/CMakeLists.txt index ccea20997d8f..e382a97c6413 100644 --- a/tools/chpldef/CMakeLists.txt +++ b/tools/chpldef/CMakeLists.txt @@ -18,9 +18,12 @@ # Put all the binary source files into an object blob. add_library(chpldef-objects command-line-flags.cpp + compiler-gadgets.cpp compute-goto-declaration.cpp compute-lifecycle.cpp compute-synchronization.cpp + events.cpp + Format.cpp Logger.cpp Message.cpp misc.cpp @@ -39,10 +42,11 @@ set_property(TARGET chpldef-objects PROPERTY CXX_STANDARD 17) # Create the 'chpldef' executable. add_executable(chpldef chpldef.cpp) target_link_libraries(chpldef chpldef-objects) +set(CHPLDEF_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(chpldef PRIVATE ${CHPL_MAIN_INCLUDE_DIR} ${CHPL_INCLUDE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}) + ${CHPLDEF_INCLUDE_DIR}) set_property(TARGET chpldef PROPERTY CXX_STANDARD 17) # Enable testing if the test folder exists. diff --git a/tools/chpldef/Format.cpp b/tools/chpldef/Format.cpp new file mode 100644 index 000000000000..2a8a2b019941 --- /dev/null +++ b/tools/chpldef/Format.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "events.h" +#include "Server.h" +#include "chpl/parsing/parsing-queries.h" +#include + +namespace chpldef { + +void Format::writeit(const chpl::uast::AstNode& t) { + ctx_->withChapel([&](auto chapel) { + ss_ << chpl::uast::asttags::tagToString(t.tag()) << " "; + if (auto nd = t.toNamedDecl()) ss_ << "'" << nd->name().c_str() << "' "; + auto loc = chpl::parsing::locateAst(chapel, &t); + ss_ << "[" << loc.path().c_str() << ":" << loc.firstLine(); + ss_ << ":" << loc.firstColumn() << "]"; + }); +} + +void Format::writeit(const chpl::Location& t) { + ss_ << t.path().c_str() << ":" << t.firstLine() << ":" << t.firstColumn(); + ss_ << "-" << t.lastLine() << ":" << t.lastColumn(); +} + +void Format::writeit(const chpldef::Message& msg) { + ss_ << Message::tagToString(msg.tag()); + if (!msg.isNotification()) ss_ << " (" << msg.idToString() << ")"; +} + +} // end namespace 'chpldef' diff --git a/tools/chpldef/Format.h b/tools/chpldef/Format.h new file mode 100644 index 000000000000..7e3ff5e3059f --- /dev/null +++ b/tools/chpldef/Format.h @@ -0,0 +1,65 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CHPL_TOOLS_CHPLDEF_FORMAT_H +#define CHPL_TOOLS_CHPLDEF_FORMAT_H + +#include "Message.h" +#include "chpl/uast/AstNode.h" +#include "chpl/framework/Location.h" +#include "chpl/framework/UniqueString.h" +#include + +namespace chpldef { + +class Format { +public: + enum Detail { + DEFAULT = 0 + }; +private: + std::stringstream ss_; + Server* ctx_; + Detail dt_; + + void writeit(const chpl::uast::AstNode& t); + void writeit(const chpl::Location& t); + void writeit(const chpldef::Message& t); +public: + Format(Server* ctx, Detail dt) : ctx_(ctx), dt_(dt) {} + ~Format() = default; + + inline std::string read() const { return ss_.str(); } + + template + void write(const T& t) { + std::ignore = dt_; writeit(t); + } + + template + void write(T* const& t) { + if (t == nullptr) { ss_ << ""; return; } + auto& tx = *t; write(tx); + } +}; + +} // end namespace 'chpldef' + +#endif diff --git a/tools/chpldef/Logger.cpp b/tools/chpldef/Logger.cpp index eeea926a562e..8c9e84f3b17b 100644 --- a/tools/chpldef/Logger.cpp +++ b/tools/chpldef/Logger.cpp @@ -69,6 +69,7 @@ Logger& Logger::operator=(Logger&& other) noexcept { filePath_ = other.filePath_; flushImmediately_ = other.flushImmediately_; level_ = other.level_; + header_ = other.header_; stream_.swap(other.stream_); if (other.stream_.is_open()) other.stream_.close(); return *this; @@ -127,14 +128,10 @@ void Logger::flush() { void Logger::logit(const char* msg) { if (!isLogging()) return; - switch (output_) { - case STDERR: std::cerr << msg; break; - case STDOUT: std::cout << msg; break; - case FILEPATH: stream_ << msg; break; - } - if (flushImmediately_) { - flush(); - } + auto& out = stream(); + if (!header_.empty()) out << header_ << " "; + out << msg; + if (flushImmediately_) flush(); } void Logger::logit(std::string msg) { diff --git a/tools/chpldef/Logger.h b/tools/chpldef/Logger.h index 4732fffe26b3..3d02b3de96ce 100644 --- a/tools/chpldef/Logger.h +++ b/tools/chpldef/Logger.h @@ -21,11 +21,9 @@ #ifndef CHPL_TOOLS_CHPLDEF_LOGGER_H #define CHPL_TOOLS_CHPLDEF_LOGGER_H -#include "./misc.h" - +#include "misc.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/JSON.h" - #include #include #include @@ -47,6 +45,7 @@ class Logger { std::ofstream stream_; bool flushImmediately_ = false; Level level_ = MESSAGES; + std::string header_; Logger(Output output, std::string filePath); @@ -70,6 +69,15 @@ class Logger { inline void setLevel(Level level) { this->level_ = level; } void setFlushImmediately(bool flushImmediately); inline bool flushImmediately() const { return flushImmediately_; } + void setHeader(std::string header) { header_ = std::move(header); } + inline const std::string& header() const { return header_; } + + /** Get a mutable handle to the underlying output stream. */ + inline auto& stream() { + return output_ == STDERR ? std::cerr : output_ == STDOUT ? std::cout + : stream_; + } + /** Returns 'true' if the log was started. */ bool start(); void stop(); diff --git a/tools/chpldef/Makefile.include b/tools/chpldef/Makefile.include index 6f1fb120001c..be26645bafa0 100644 --- a/tools/chpldef/Makefile.include +++ b/tools/chpldef/Makefile.include @@ -19,15 +19,17 @@ DYNO_CHPLDEF_OBJDIR = $(COMPILER_BUILD)/chpldef DYNO_CHPLDEF_SRCS = \ - chpldef.cpp \ - command-line-flags.cpp \ - Logger.cpp \ - Message.cpp \ - message-handle.cpp \ - misc.cpp \ - protocol-types.cpp \ - Server.cpp \ - Transport.cpp + chpldef.cpp \ + command-line-flags.cpp \ + compute-goto-declaration.cpp \ + compute-lifecycle.cpp \ + compute-synchronization.cpp \ + Logger.cpp \ + Message.cpp \ + misc.cpp \ + protocol-types.cpp \ + Server.cpp \ + Transport.cpp SRCS = $(DYNO_CHPLDEF_SRCS) diff --git a/tools/chpldef/Message.cpp b/tools/chpldef/Message.cpp index c19ac104d9b8..e5a760b96b83 100644 --- a/tools/chpldef/Message.cpp +++ b/tools/chpldef/Message.cpp @@ -18,7 +18,8 @@ * limitations under the License. */ -#include "./Message.h" +#include "Message.h" +#include "Server.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/JSON.h" #include @@ -26,16 +27,43 @@ namespace chpldef { +/** Forward declare specializations so that they are instantiated. */ +#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ + template chpl::owned name__::create(JsonValue id, Params p); \ + template chpl::owned \ + name__::createFromJson(JsonValue id, JsonValue j); \ + template opt name__::pack() const; \ + template void name__::handle(Server* ctx); \ + template void name__::handle(Server* ctx, Response* r); \ + template void name__::handle(Server* ctx, Result r); \ + template<> name__::ComputeResult \ + name__::compute(Server* ctx, ComputeParams p); \ + template name__::~name__(); +#include "message-macro-list.h" +#undef CHPLDEF_MESSAGE + bool Message::isIdValid(const JsonValue& id) { return id.kind() == JsonValue::Number || id.kind() == JsonValue::String || id.kind() == JsonValue::Null; } +Message::Behavior Message::behavior(Tag tag) { + switch (tag) { + #define CHPLDEF_MESSAGE(name__, outbound__, notify__, rpc__) \ + case MessageTag::name__: \ + return determineBehavior(outbound__, notify__); + #include "message-macro-list.h" + #undef CHPLDEF_MESSAGE + default: break; + } + return Message::NO_BEHAVIOR; +} + // TODO: Need to build an error message or record what went wrong. Create // an 'Invalid' subclass and use that to record any error that occurs past // the point of parsing the message ID. -chpl::owned Message::request(Server* ctx, const JsonValue& j) { +chpl::owned Message::create(Server* ctx, JsonValue j) { auto objPtr = j.getAsObject(); if (!objPtr) { ctx->verbose("Failed to unpack JSON object from %s\n", @@ -44,7 +72,7 @@ chpl::owned Message::request(Server* ctx, const JsonValue& j) { } // Determine what method is to be called. - Message::Tag tag = Message::UNSET; + auto tag = MessageTag::UNSET; if (auto optMethod = objPtr->getString("method")) { auto str = optMethod->str(); @@ -52,15 +80,14 @@ chpl::owned Message::request(Server* ctx, const JsonValue& j) { tag = Message::jsonRpcMethodNameToTag(str); // TODO: We can create an 'Invalid' request to represent this failure. - const bool hasTag = tag != Message::UNSET && tag != Message::INVALID; - + bool hasTag = tag != MessageTag::UNSET && tag != MessageTag::INVALID; if (!hasTag) { CHPLDEF_FATAL(ctx, "Unrecognized method '%s'\n", str.c_str()); return nullptr; } } - CHPL_ASSERT(tag != Message::RESPONSE); + if (tag == MessageTag::RESPONSE) CHPLDEF_TODO(); ctx->verbose("Constructing message with tag '%s'\n", tagToString(tag)); @@ -83,21 +110,23 @@ chpl::owned Message::request(Server* ctx, const JsonValue& j) { } // Before building a specific request, get the params. - const JsonValue params(nullptr); - const JsonValue* p = ¶ms; - if (auto ptr = objPtr->get("params")) { - auto k = ptr->kind(); + JsonValue params(nullptr); + if (auto fieldPtr = objPtr->get("params")) { + auto& field = *fieldPtr; + auto k = field.kind(); if (k != JsonValue::Array && k != JsonValue::Object) { CHPLDEF_TODO(); return nullptr; } else { - p = ptr; + std::swap(field, params); } } switch (tag) { #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - case name__: return name__::create(std::move(id), *p); + case MessageTag::name__: { \ + return name__::createFromJson(std::move(id), std::move(params)); \ + } break; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE default: break; @@ -108,66 +137,86 @@ chpl::owned Message::request(Server* ctx, const JsonValue& j) { return nullptr; } -// TODO: Is outbound format the same as for incoming requests? -template -JsonValue Request::pack() const { - return p.toJson(); +template +static opt packIncomingRequest(const M* msg) { + if (auto r = msg->result()) { + if (msg->hasError()) { + Response rsp(msg->id(), msg->error(), msg->note(), nullptr); + return rsp.pack(); + } else { + auto data = r->toJson(); + Response rsp(msg->id(), Message::OK, std::string(), std::move(data)); + return rsp.pack(); + } + } + return {}; } -template -static Response createSuccessfulResponse(const T* t) { - CHPL_ASSERT(t && t->isCompleted() && !t->hasError()); - auto r = t->result(); - CHPL_ASSERT(r); - auto data = r->toJson(); - auto ret = Response::create(t->id(), std::move(data)); - return ret; +template +static opt packIncomingNotify(const M* msg) { + return {}; } -opt Message::response(Server* ctx, const Message* msg) { - opt ret; +template +static opt packOutboundRequest(const M* msg) { + JsonObject obj { + { "jsonrpc", "2.0" }, + { "id", msg->id() }, + }; - if (msg->tag() == Message::UNSET || msg->tag() == Message::INVALID || - msg->tag() == Message::RESPONSE) { + if (auto p = msg->params()) { + obj["params"] = p->toJson(); + JsonValue ret(std::move(obj)); return ret; } - switch (msg->status()) { - case COMPLETED: { - CHPL_ASSERT(!msg->hasError()); - switch (msg->tag()) { - #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - case name__: return createSuccessfulResponse(msg->to##name__()); - #include "./message-macro-list.h" - #undef CHPLDEF_MESSAGE - default: break; - } - } break; - case FAILED: { - CHPL_ASSERT(msg->hasError()); - JsonValue data(nullptr); - ret = Response::create(msg->id(), msg->error(), msg->note(), - std::move(data)); - } break; + return {}; +} + +template +static opt packOutboundNotify(const M* msg) { + if (auto ret = packOutboundRequest(msg)) { + if (auto obj = ret->getAsObject()) { + obj->erase("id"); + } + return ret; + } + return {}; +} + +template +opt TemplatedMessage::pack() const { + switch (behavior()) { + case INCOMING_REQUEST: return packIncomingRequest(this); + case INCOMING_NOTIFY: return packIncomingNotify(this); + case OUTBOUND_REQUEST: return packOutboundRequest(this); + case OUTBOUND_NOTIFY: return packOutboundNotify(this); default: break; } - return ret; + // All subclasses of 'TemplatedMessage' should have a behavior. + CHPLDEF_IMPOSSIBLE(); + return {}; } -bool Message::isOutbound() const { - if (tag_ == Message::RESPONSE) return true; +bool Message::isOutbound(Tag tag) { + if (tag == MessageTag::RESPONSE) return true; #define CHPLDEF_MESSAGE(name__, outbound__, x2__, x3__) \ - if (tag_ == Message::name__) return outbound__; + if (tag == MessageTag::name__) return outbound__; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE return false; } +bool Message::isIncoming(Tag tag) { + if (tag == MessageTag::RESPONSE) return true; + return !isOutbound(tag); +} + bool Message::isNotification(Tag tag) { - if (tag == Message::RESPONSE) return true; + if (tag == MessageTag::RESPONSE) return false; #define CHPLDEF_MESSAGE(name__, x1__, notification__, x3__) \ - if (tag == Message::name__) return notification__; + if (tag == MessageTag::name__) return notification__; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE return false; @@ -180,7 +229,7 @@ const char* Message::jsonRpcMethodName() const { const char* Message::tagToJsonRpcMethodName(Message::Tag tag) { switch (tag) { #define CHPLDEF_MESSAGE(name__, x1__, x2__, rpc__) \ - case name__: return #rpc__; + case MessageTag::name__: return #rpc__; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE default: break; @@ -192,10 +241,10 @@ const char* Message::tagToJsonRpcMethodName(Message::Tag tag) { // TODO: Can use a trie if this becomes a performance bottleneck. Message::Tag Message::jsonRpcMethodNameToTag(const char* str) { #define CHPLDEF_MESSAGE(name__, x1__, x2__, rpc__) \ - if (!strcmp(#rpc__, str)) return name__; + if (!strcmp(#rpc__, str)) return MessageTag::name__; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE - return Message::INVALID; + return MessageTag::INVALID; } Message::Tag Message::jsonRpcMethodNameToTag(std::string str) { @@ -205,38 +254,60 @@ Message::Tag Message::jsonRpcMethodNameToTag(std::string str) { std::string Message::idToString() const { if (auto optStr = id().getAsString()) return optStr->str(); if (auto optInt = id().getAsInteger()) return std::to_string(*optInt); - if (id().kind() == JsonValue::Null) return "<>"; + if (id().kind() == JsonValue::Null) return ""; CHPLDEF_IMPOSSIBLE(); return {}; } -template -Message::Error Request::unpack(const JsonValue& j, P& p, - std::string* note) { +template +Message::Error +TemplatedMessage::unpack(JsonValue j, Params& p, std::string* note) { llvm::json::Path::Root root; if (p.fromJson(j, root)) return Message::OK; - *note = "Failed to unpack JSON"; + *note = "Failed to unpack JSON for parameters"; return Message::ERR_INVALID_PARAMS; } -/** Define factory functions that unpack and construct the message. */ -#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - chpl::owned \ - name__::create(JsonValue id, const JsonValue& j) { \ - name__::Params p; \ - std::string note; \ - auto error = unpack(j, p, ¬e); \ - auto req = new name__(std::move(id), error, std::move(note), \ - std::move(p)); \ - auto ret = chpl::toOwned(req); \ - return ret; \ - } -#include "./message-macro-list.h" -#undef CHPLDEF_MESSAGE +template +Message::Error +TemplatedMessage::unpack(JsonValue j, Result& r, std::string* note) { + llvm::json::Path::Root root; + if (r.fromJson(j, root)) return Message::OK; + *note = "Failed to unpack JSON for result"; + return Message::ERR_INVALID_PARAMS; +} + +template +Message::Error +TemplatedMessage::unpack(Response* rsp, Result& r, std::string* note) { + CHPLDEF_TODO(); + return Message::OK; +} + +template +chpl::owned> +TemplatedMessage::create(JsonValue id, Params p) { + auto msg = new TemplatedMessage(K, std::move(id), Message::OK, {}, + std::move(p)); + auto ret = chpl::toOwned<>(msg); + return ret; +} + +template +chpl::owned> +TemplatedMessage::createFromJson(JsonValue id, JsonValue j) { + Params p = {}; + std::string note; + auto error = unpack(std::move(j), p, ¬e); + auto msg = new TemplatedMessage(K, std::move(id), error, + std::move(note), + std::move(p)); + auto ret = chpl::toOwned<>(msg); + return ret; +} static JsonObject -createResponseError(Message::Error e, std::string note, - JsonValue data) { +packResponseError(Message::Error e, std::string note, JsonValue data) { JsonObject ret { { "code", Message::errorToInt(e) }, { "message", std::move(note) } @@ -246,66 +317,72 @@ createResponseError(Message::Error e, std::string note, return ret; } -JsonValue Response::pack() const { - JsonObject ret { +opt Response::pack() const { + JsonObject obj { { "jsonrpc", "2.0" }, { "id", this->id() } }; if (hasError()) { - ret["error"] = createResponseError(error(), note(), data()); + obj["error"] = packResponseError(error(), note(), data()); } else { - ret["result"] = data(); + obj["result"] = data(); } + JsonValue ret(std::move(obj)); return ret; } -Response Response::create(JsonValue id, JsonValue data) { +chpl::owned Response::create(JsonValue id, JsonValue data) { CHPL_ASSERT(isIdValid(id)); - auto ret = Response(std::move(id), Message::OK, std::string(), - std::move(data)); + auto rsp = new Response(std::move(id), Message::OK, std::string(), + std::move(data)); + auto ret = chpl::toOwned(rsp); return ret; } -Response Response::create(JsonValue id, Message::Error error, - std::string note, - JsonValue data) { +chpl::owned Response::create(JsonValue id, Message::Error error, + std::string note, + JsonValue data) { CHPL_ASSERT(isIdValid(id) && error != Message::OK); - auto ret = Response(std::move(id), error, std::move(note), - std::move(data)); + auto rsp = new Response(std::move(id), error, std::move(note), + std::move(data)); + auto ret = chpl::toOwned(rsp); return ret; } const Response* Message::toResponse() const { - if (tag_ == Message::RESPONSE) return static_cast(this); + if (tag_ == MessageTag::RESPONSE) + return static_cast(this); return nullptr; } Response* Message::toResponse() { - if (tag_ == Message::RESPONSE) return static_cast(this); + if (tag_ == MessageTag::RESPONSE) + return static_cast(this); return nullptr; } #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - const class name__* Message::to##name__() const { \ - if (tag_ == Message::name__) \ - return static_cast(this); \ + const name__* Message::to##name__() const { \ + if (tag_ == MessageTag::name__) \ + return static_cast(this); \ return nullptr; \ } \ - class name__* Message::to##name__() { \ - if (tag_ == Message::name__) return static_cast(this); \ + name__* Message::to##name__() { \ + if (tag_ == MessageTag::name__) \ + return static_cast(this); \ return nullptr; \ } #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE const char* Message::tagToString(Tag tag) { - if (tag == Message::UNSET) return "Unset"; - if (tag == Message::INVALID) return "Invalid"; - if (tag == Message::RESPONSE) return "Response"; + if (tag == MessageTag::UNSET) return "Unset"; + if (tag == MessageTag::INVALID) return "Invalid"; + if (tag == MessageTag::RESPONSE) return "Response"; #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - if (tag == name__) return #name__; + if (tag == MessageTag::name__) return #name__; #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE return nullptr; @@ -331,86 +408,138 @@ const char* Message::errorToString(Error error) { return nullptr; } -template -static bool -doHandleNotification(Server* ctx, M* msg, const P& p, CR& cr) { - if (msg->status() == Message::PROGRESSING) CHPLDEF_TODO(); - if (msg->status() != Message::PENDING) return false; - - ctx->message("Handling notification '%s'\n", msg->tagToString()); - - cr = M::compute(ctx, p); - - if (cr.isProgressingCallAgain) CHPLDEF_TODO(); - - ctx->message("Notification complete...\n"); +const char* Message::behaviorToString(Message::Behavior b) { + switch (b) { + case Message::INCOMING_REQUEST: return "incoming request"; + case Message::INCOMING_NOTIFY: return "incoming notification"; + case Message::OUTBOUND_REQUEST: return "outbound request"; + case Message::OUTBOUND_NOTIFY: return "outbound notification"; + case Message::NO_BEHAVIOR: return "none"; + } - return true; + return nullptr; } -template -static bool -doHandleRequest(Server* ctx, M* msg, const P& p, CR& cr) { - if (msg->status() == Message::PROGRESSING) CHPLDEF_TODO(); - if (msg->status() != Message::PENDING) return false; - - ctx->message("Handling request '%s' with ID '%s'\n", - msg->tagToString(), - msg->idToString().c_str()); - - cr = M::compute(ctx, p); +// Use this pattern so that we may implement the TemplatedMessage class with +// an unbounded number of implementation methods that are not documented +// in the header. Because the handler is marked 'friend', it can access the +// message state. +template +class TemplatedMessageHandler { +public: + static constexpr auto BEHAVIOR = M::BEHAVIOR; + using Params = typename M::Params; + using Result = typename M::Result; + using ComputeResult = typename M::ComputeResult; +private: + Server* ctx_; + M* msg_; + const char* dsc_ = description(); + std::string fmt_ = ctx_->fmt(static_cast(msg_)); +public: + TemplatedMessageHandler(Server* ctx, M* msg) : ctx_(ctx), msg_(msg) {} + TemplatedMessageHandler() = default; + + inline const char* description() const { + constexpr bool hasBehavior = BEHAVIOR != Message::NO_BEHAVIOR; + auto ret = hasBehavior ? Message::behaviorToString(BEHAVIOR) : "message"; + return ret; + } - if (cr.isProgressingCallAgain) CHPLDEF_TODO(); + void logComputationPrelude() { + ctx_->message("Handling %s '%s'\n", dsc_, fmt_.c_str()); + } - if (cr.error != Message::OK) { - auto cstr = Message::errorToString(cr.error); - ctx->message("Request failed with code '%s'\n", cstr); - return false; + void logComputationEpilogue(const ComputeResult& cr) { + if (cr.error != Message::OK) { + ctx_->message("The %s %s failed with code '%s'\n", dsc_, fmt_.c_str(), + Message::errorToString(cr.error)); + } else { + ctx_->message("The %s '%s' is complete...\n", dsc_, fmt_.c_str()); + } } - ctx->message("Request '%s' complete...\n", msg->idToString().c_str()); + template + void issueComputationCall() {} + + template <> + void issueComputationCall() { + logComputationPrelude(); + auto cr = M::compute(ctx_, msg_->p); + logComputationEpilogue(cr); + if (cr.isProgressingCallAgain) CHPLDEF_TODO(); + if (cr.error != Message::OK) { + msg_->markFailed(cr.error, std::move(cr.note)); + } else { + msg_->r = std::move(cr.result); + msg_->markCompleted(); + } + } - return true; -} + template <> + void issueComputationCall() { + issueComputationCall(); + } -template -static bool -doHandleMessage(Server* ctx, M* msg, const P& p, R& r) { - if (msg->isNotification()) return doHandleNotification(ctx, msg, p, r); - return doHandleRequest(ctx, msg, p, r); -} + template + void issueComputationCall(Result r) {} + + template <> + void issueComputationCall(Result r) { + logComputationPrelude(); + msg_->r = std::move(r); + auto cr = M::compute(ctx_, msg_->r); + logComputationEpilogue(cr); + if (cr.isProgressingCallAgain) CHPLDEF_TODO(); + if (cr.error != Message::OK) { + msg_->markFailed(cr.error, std::move(cr.note)); + } else { + msg_->markCompleted(); + } + } -/** Call the request handler above and then mark completed or failed. */ -#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - void name__::handle(Server* ctx) { \ - ComputedResult r; \ - if (doHandleMessage(ctx, this, p, r)) { \ - this->r = std::move(r.result); \ - this->markCompleted(); \ - } else { \ - this->markFailed(r.error, std::move(r.note)); \ - } \ + void handle() { + if (msg_->isOutbound() || msg_->isDone()) return; + if (msg_->status() == Message::PROGRESSING) CHPLDEF_TODO(); + issueComputationCall(); } -#include "./message-macro-list.h" -#undef CHPLDEF_MESSAGE -opt Message::handle(Server* ctx, Message* msg) { - if (!ctx || !msg || msg->isResponse()) return {}; - if (msg->status() != Message::PENDING) return {}; + void handle(Response* rsp) { + CHPL_ASSERT(rsp && rsp->id() == msg_->id()); + if (rsp->status() == Message::FAILED) CHPLDEF_TODO(); + std::string note; + Result r; + auto error = M::unpack(rsp, r, ¬e); + if (error != Message::OK) { + msg_->markFailed(error, std::move(note)); + } else { + handle(std::move(r)); + } + } - msg->handle(ctx); + void handle(Result r) { + if (msg_->isOutbound() || msg_->isDone()) return; + if (msg_->status() == Message::PROGRESSING) CHPLDEF_TODO(); + issueComputationCall(std::move(r)); + } +}; - CHPL_ASSERT(msg->status() != Message::PENDING); +template +void TemplatedMessage::handle(Server* ctx) { + TemplatedMessageHandler> tmh(ctx, this); + tmh.handle(); +} - if (!msg->isNotification() && msg->status() != Message::PROGRESSING) { - if (auto ret = Message::response(ctx, msg)) { - CHPL_ASSERT(ret->id() == msg->id()); - CHPL_ASSERT(ret->status() == Message::COMPLETED); - return ret; - } - } +template +void TemplatedMessage::handle(Server* ctx, Response* rsp) { + TemplatedMessageHandler> tmh(ctx, this); + tmh.handle(rsp); +} - return {}; +template +void TemplatedMessage::handle(Server* ctx, Result r) { + TemplatedMessageHandler> tmh(ctx, this); + tmh.handle(r); } } // end namespace 'chpldef' diff --git a/tools/chpldef/Message.h b/tools/chpldef/Message.h index 127f7c4b6913..d39bf9db3c04 100644 --- a/tools/chpldef/Message.h +++ b/tools/chpldef/Message.h @@ -21,9 +21,8 @@ #ifndef CHPL_TOOLS_CHPLDEF_MESSAGE_H #define CHPL_TOOLS_CHPLDEF_MESSAGE_H -#include "./misc.h" -#include "./protocol-types.h" -#include "./Server.h" +#include "misc.h" +#include "protocol-types.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/JSON.h" #include @@ -32,24 +31,31 @@ namespace chpldef { -/** Forward declare some message subclasses. */ -class BaseRequest; -template class Request; class Response; +class Server; +class Message; + +/** These tags are used to do dynamic casts to message types at runtime. */ +namespace MessageTag { +enum Kind { + UNSET = 0, + INVALID = 1, + RESPONSE = 2, + // Expand the message macros. + #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) name__ , + #include "./message-macro-list.h" + #undef CHPLDEF_MESSAGE + NUM_MESSAGES +}; +} -/** Forward declare requests. */ -#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - struct name__##Params; \ - struct name__##Result; \ - class name__; -#include "./message-macro-list.h" -#undef CHPLDEF_MESSAGE +template class TemplatedMessage; /** Attempts to model a LSP message. A message may be either incoming or - outgoing (most are incoming). -*/ + outgoing (most are incoming). */ class Message { public: + using Tag = MessageTag::Kind; /** Error codes are listed in order according to the LSP spec. */ enum Error { @@ -74,21 +80,17 @@ class Message { FAILED /** Handling of this message failed. */ }; - enum Tag { - UNSET = 0, - INVALID = 1, - RESPONSE = 2, - - // Expand the message macros. - #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) name__ , - #include "./message-macro-list.h" - #undef CHPLDEF_MESSAGE - NUM_MESSAGES_INNER, - NUM_MESSAGES = (NUM_MESSAGES_INNER - 1) + /** Behavior is a combination of message direction and return value. */ + enum Behavior { + NO_BEHAVIOR, + INCOMING_REQUEST, + INCOMING_NOTIFY, + OUTBOUND_REQUEST, + OUTBOUND_NOTIFY }; private: - Tag tag_ = Message::UNSET; + Tag tag_ = MessageTag::UNSET; JsonValue id_ = nullptr; Error error_ = Message::OK; Status status_ = Message::PENDING; @@ -103,6 +105,7 @@ class Message { note_(std::move(note)) { CHPL_ASSERT(isIdValid(id_)); if (id.kind() == JsonValue::Null) CHPL_ASSERT(isNotification()); + if (error != Message::OK) status_ = Message::FAILED; } inline void markProgressing() { status_ = Message::PROGRESSING; } @@ -119,11 +122,7 @@ class Message { virtual ~Message() = default; /** Create a request given a JSON value. */ - static chpl::owned request(Server* ctx, const JsonValue& j); - - /** Create a response to a handled message. The status of the message - must be COMPLETED or FAILED, or else nothing is returned. */ - static opt response(Server* ctx, const Message* msg); + static chpl::owned create(Server* ctx, JsonValue j); /** The tag for this message. */ inline Tag tag() const { return tag_; } @@ -163,11 +162,26 @@ class Message { /** Returns 'true' if this message is marked 'COMPLETED'. */ inline bool isCompleted() const { return status_ == Message::COMPLETED; } + /** Returns 'true' if this message is marked 'FAILED'. */ + inline bool isFailed() const { return status_ == Message::FAILED; } + + /** Returns 'true' if this message is marked 'COMPLETED' or 'FAILED'. */ + inline bool isDone() const { return isCompleted() || isFailed(); } + /** If 'true', this message is a response. */ - inline bool isResponse() const { return tag_ == Message::RESPONSE; } + inline bool isResponse() const { return tag_ == MessageTag::RESPONSE; } + + /** If 'true', then 'tag' is for a message being sent to the server. */ + static bool isOutbound(Tag tag); /** If 'true', the server is sending this message to the client. */ - bool isOutbound() const; + inline bool isOutbound() const { return isOutbound(tag_); } + + /** If 'true', then 'tag' is for a message being sent to the server. */ + static bool isIncoming(Tag tag); + + /** If 'true', the client is sending this message to the server. */ + inline bool isIncoming() const { return isIncoming(tag_); } /** If 'true', then the Tag 'tag' does not need a response. */ static bool isNotification(Tag tag); @@ -175,6 +189,15 @@ class Message { /** If 'true', then this message does not need a response. */ inline bool isNotification() const { return isNotification(tag_); } + /** Return the behavior of a message tag. */ + static Behavior behavior(Tag tag); + + /** Return the behavior of this message. */ + inline Behavior behavior() const { return behavior(tag_); } + + /** Print a behavior value as a string. */ + static const char* behaviorToString(Behavior b); + /** Returns the expected JSON-RPC method name for this message. */ const char* jsonRpcMethodName() const; @@ -201,8 +224,8 @@ class Message { /** Dynamic cast to other message subclasses, const and non-const. */ #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - const class name__* to##name__() const; \ - class name__* to##name__(); + const TemplatedMessage* to##name__() const; \ + TemplatedMessage* to##name__(); #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE @@ -222,7 +245,9 @@ class Message { /** Default visitor body is trivial enough that we define it here. */ #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - virtual T visit(const class name__* req) { T ret; return ret; } + virtual T visit(const TemplatedMessage* req) { \ + T ret; return ret; \ + } #include "./message-macro-list.h" #undef CHPLDEF_MESSAGE @@ -233,6 +258,14 @@ class Message { Server* ctx() const; }; + /** Determine a message's behavior as a compile-time expression. */ + static inline constexpr Message::Behavior + determineBehavior(bool outbound, bool notify) { + return outbound + ? (notify ? Message::OUTBOUND_NOTIFY : Message::OUTBOUND_REQUEST) + : (notify ? Message::INCOMING_NOTIFY : Message::INCOMING_REQUEST); + } + /** Dispatch a visitor using a message as the receiver. */ template inline T accept(Message::Visitor& v) { return v.dispatch(*this); } @@ -241,204 +274,275 @@ class Message { virtual bool defer() const { return false; } /** Pack this message into a JSON value. */ - virtual JsonValue pack() const = 0; - - /** May handle a message, but does not send or receive. */ - static opt handle(Server* ctx, Message* msg); + virtual opt pack() const = 0; - /** Compute results and store them in this request for later use. */ + /** For incoming requests, compute a result using the input parameters. */ virtual void handle(Server* ctx) = 0; + + /** For outbound requests, compute using a response sent by the client. */ + virtual void handle(Server* ctx, Response* rsp) = 0; }; -class BaseRequest : public Message { -protected: - BaseRequest(Message::Tag tag, JsonValue id, Message::Error error, - std::string note) - : Message(tag, std::move(id), error, std::move(note)) {} -public: - virtual JsonValue pack() const override = 0; - virtual void handle(Server* ctx) override = 0; +/** Stores computation results for a message. */ +template +struct ComputationOutput { + bool isProgressingCallAgain = false; + Message::Error error = Message::OK; + std::string note; + R result; + + /** For convenience when implementing 'compute' for a request. */ + ComputationOutput(R&& result) + : isProgressingCallAgain(false), + error(Message::OK), + result(result) {} + + ComputationOutput(bool isProgressingCallAgain, Message::Error error, + std::string note, + R result) + : isProgressingCallAgain(isProgressingCallAgain), + error(error), + note(std::move(note)), + result(std::move(result)) {} + + ComputationOutput() = default; }; -/** Most messages are incoming and are modelled as a 'Request'. A request - consists of a set of parameter values, that can be used to compute - a result (it is analogous to a function call). Some requests can be - sent by the server to the client, in which case they are 'sendable', - and should provide a definition for the 'pack()' method. -*/ -template -class Request : public BaseRequest { -public: +/** Message specializations return this in absence of another value. */ +using Empty = EmptyProtocolType; - /** Stores computation results for this request. */ - struct ComputedResult { - bool isProgressingCallAgain = false; - Message::Error error = Message::OK; - std::string note; - Result result; +template +struct Computation {}; - /** For convenience when implementing 'compute' for a request. */ - ComputedResult(Result&& r) - : isProgressingCallAgain(false), - error(Message::OK), - result(r) {} +/** Computation details for incoming requests. */ +template +struct Computation { + static constexpr auto BEHAVIOR = Message::INCOMING_REQUEST; + using Params = P; + using Result = R; + using FunctionParams = const Params&; + using FunctionResult = ComputationOutput; +}; - ComputedResult(bool isProgressingCallAgain, Message::Error error, - std::string note, - Result result) - : isProgressingCallAgain(isProgressingCallAgain), - error(error), - note(std::move(note)), - result(std::move(result)) {} +/** Computation details for incoming notifications. */ +template +struct Computation { + static constexpr auto BEHAVIOR = Message::INCOMING_NOTIFY; + using Params = P; + using Result = Empty; + using FunctionParams = const Params&; + using FunctionResult = ComputationOutput; +}; - ComputedResult() = default; +/** Computation details for outbound requests. */ +template +struct Computation { + static constexpr auto BEHAVIOR = Message::OUTBOUND_REQUEST; + using Params = P; + using Result = R; + using FunctionParams = const Result&; + using FunctionResult = ComputationOutput; +}; + +/** Details for outbound notifications. Most fields are an empty struct + because outbound notifications should never be computed (they are + only ever created then sent off to the client. Any validation must be + done by the entity that created one). */ +template +struct Computation { + static constexpr auto BEHAVIOR = Message::OUTBOUND_NOTIFY; + using Params = P; + using Result = Empty; + using FunctionParams = Empty; + using FunctionResult = ComputationOutput; +}; + +template +struct ComputationByTag {}; + +/** Specialize computation details for each tag. */ +#define CHPLDEF_MESSAGE(name__, outbound__, notify__, x3__) \ + template <> struct ComputationByTag { \ + static constexpr \ + auto B = Message::determineBehavior(outbound__, notify__); \ + using type = Computation; \ }; +#include "message-macro-list.h" +#undef CHPLDEF_MESSAGE + +template +class TemplatedMessageHandler; +template +class TemplatedMessage : public Message { +public: + using Computation = typename ComputationByTag::type; + static constexpr auto BEHAVIOR = Computation::BEHAVIOR; + using Params = typename Computation::Params; + using Result = typename Computation::Result; + using ComputeParams = typename Computation::FunctionParams; + using ComputeResult = typename Computation::FunctionResult; + using Self = TemplatedMessage; protected: + friend TemplatedMessageHandler>; + const Params p; Result r = {}; - Request(Message::Tag tag, JsonValue id, Message::Error error, - std::string note, - Params params) - : BaseRequest(tag, std::move(id), error, std::move(note)), - p(std::move(params)) {} + TemplatedMessage(Message::Tag tag, JsonValue id, Message::Error error, + std::string note, + Params p) + : Message(tag, std::move(id), error, std::move(note)), + p(std::move(p)) {} - static Message::Error unpack(const JsonValue& j, Params& p, - std::string* note); + static Message::Error unpack(JsonValue j, Params& p, std::string* note); + static Message::Error unpack(JsonValue j, Result& r, std::string* note); + static Message::Error unpack(Response* rsp, Result& r, std::string* note); /** Use in message handlers to return failure. */ - static ComputedResult fail(Error error=Message::ERR_REQUEST_FAILED, - std::string note=std::string()) { + static ComputeResult fail(Error error=Message::ERR_REQUEST_FAILED, + std::string note=std::string()) { return { false, error, std::move(note), {} }; } /** Use in message handlers to delay. */ - static ComputedResult delay() { + static ComputeResult delay() { return { true, Message::OK, {}, {} }; } public: - virtual ~Request() = default; + virtual ~TemplatedMessage() = default; + + /** Create a message using a JSON id and parameters. */ + static chpl::owned create(JsonValue id, Params p); + + /** Create a message using a JSON id, error code and optional note. */ + static chpl::owned createFromJson(JsonValue id, JsonValue j); + + /** Pack this request into a JSON value. The contents of the JSON value + depend on the behavior of this message. There are four modes: + a message can either be a notification or a request, and it can either + be incoming (client-to-server) or outbound (server-to-client). + + A message should only be packed if it is marked 'COMPLETE'. Otherwise, + the contents of the JSON may not be valid. + + For incoming requests, the request result is implicitly wrapped in a + message of type 'Response', and then that Response is packed into + JSON. This is to conform with the LSP protocol (it is as if we are + sending the return value of a function call to the client). + + For outgoing requests and notifications, the message parameters are + packed directly into JSON. - /** Pack the _parameters_ of this request into a JSON value. To pack - the result of a request into a JSON value, either do so manually - or create a Response. This is method is used for sending outbound - requests from server to client. */ - virtual JsonValue pack() const override; + For incoming notifications, a JSON 'null' value is returned, as there + is nothing to pack. + */ + virtual opt pack() const final; - /** Compute results and save for later use. */ - virtual void handle(Server* ctx) override = 0; + /** For incoming messages, compute a result using the input parameters. */ + virtual void handle(Server* ctx) final; - /** Get the parameters of this request if they were deserialized. */ - const Params* params() const; + /** For outbound messages, compute using a response sent by the client. */ + virtual void handle(Server* ctx, Response* r) final; - /** If computed, get the result of this request. */ + /** For outbound messages, compute using results sent by the client. */ + void handle(Server* ctx, Result r); + + /** Get the parameters of this message if they are valid. */ + inline const Params* params() const { + if (status() == Message::FAILED) return nullptr; + return &this->p; + } + + /** Get the result of this message if it was computed. */ inline const Result* result() const { if (status() != Message::COMPLETED) return nullptr; return &this->r; } -}; -/** Expand each LSP message into its own subclass of Request. Each message - 'Foo' has a parameter type and return type, 'FooParams' and 'FooResult', - which can be found in the 'protocol-types.h' header. These names are - internally consistent but may not always match the spec - a small set of - protocol types defined in the LSP spec do not always match this pattern. + /** This function performs the actual computation. */ + static ComputeResult compute(Server* ctx, ComputeParams p); +}; - When in doubt and referring to the spec, use the "JSON-RPC method name" - to look up the message, as that is unambiguous. -*/ -#define CHPLDEF_MESSAGE(name__, outbound__, notification__, rpc__) \ - namespace name__##Impl { \ - constexpr bool notify = notification__; \ - using E = EmptyProtocolType; \ - using P = name__##Params; \ - using R = std::conditional::type; \ - } \ - class name__ : public Request { \ - public: \ - using Params = name__##Impl::P; \ - using Result = name__##Impl::R; \ - using ComputedResult = Request::ComputedResult; \ - private: \ - name__(JsonValue id, Message::Error error, std::string note, \ - Params p) \ - : Request(Message::name__, std::move(id), error, \ - std::move(note), \ - std::move(p)) { \ - static_assert(std::is_base_of::value, \ - "Must be derived from 'BaseProtocolType'"); \ - static_assert(std::is_same::value || \ - std::is_base_of::value, \ - "Must be derived from 'BaseProtocolType'"); \ - } \ - public: \ - virtual ~name__() = default; \ - static chpl::owned create(JsonValue id, const Params& p); \ - static chpl::owned \ - create(JsonValue id, const JsonValue& j); \ - virtual void handle(Server* ctx) override; \ - static ComputedResult compute(Server* ctx, const Params& p); \ - }; -#include "./message-macro-list.h" +/** Create aliases for each specialization over a message tag. */ +#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ + template <> class TemplatedMessage; \ + using name__ = TemplatedMessage; +#include "message-macro-list.h" #undef CHPLDEF_MESSAGE -/** One response is created for every request message. The structure of a - response is simple and rigid when compared to inbound (client-to-server) - requests. Because there is a 1-to-1 relationship between messages and - responses, a message/response pair must share IDs. Responses are always - outbound and cannot be handled by the server (there is no work to do - other than send the response). +/** Deliberately specialize these tags to be empty. */ +template <> +class TemplatedMessage {}; +template <> +class TemplatedMessage {}; +template <> +class TemplatedMessage {}; + +/** The structure of a response is simple and rigid. When a incoming request + is packed into JSON, it is implicitly wrapped in a response. When the + server sends the client an outbound request, it registers the request + and awaits a response. */ class Response : public Message { private: JsonValue data_; +public: Response(JsonValue id, Message::Error error, std::string note, JsonValue data) - : Message(Message::RESPONSE, std::move(id), error, std::move(note)), + : Message(MessageTag::RESPONSE, std::move(id), error, std::move(note)), data_(data) { + CHPL_ASSERT(this->id().kind() != JsonValue::Null); CHPL_ASSERT(isResponse() && isOutbound()); this->markCompleted(); } -public: + virtual ~Response() = default; + /** Create a response given an ID and a result value. */ - static Response create(JsonValue id, JsonValue data=nullptr); + static chpl::owned create(JsonValue id, JsonValue data=nullptr); /** Create a response given an ID, an error code, and optional details. If 'data' is 'nullptr' then it will not be included in the JSON created when this message is packed. Notes will be included even if they are the empty string, as this is required by the protocol. */ - static Response create(JsonValue id, Message::Error error, - std::string note=std::string(), - JsonValue data=nullptr); + static chpl::owned create(JsonValue id, Message::Error error, + std::string note=std::string(), + JsonValue data=nullptr); - virtual ~Response() = default; + /** Create a response by unpacking it from JSON. */ + static chpl::owned create(JsonValue j); /** Get the JSON data this response stores. */ inline const JsonValue& data() const { return data_; } - /** Pack this response into a JSON value. */ - virtual JsonValue pack() const override; + /** Pack this response into a JSON value. Messages have a rigid format, + so the layout of the JSON sould always be the same sans omission + of optional fields (see the LSP spec section on 'Response' messages). */ + virtual opt pack() const override; /** Handling a response does nothing. */ virtual void handle(Server* ctx) override {} + + /** Handling a response does nothing. */ + virtual void handle(Server* ctx, Response* rsp) override {} }; /** Define visitor dispatch now that all subclasses have been defined. */ template T Message::Visitor::dispatch(const Message* req) { switch (req->tag()) { - case RESPONSE: { + case MessageTag::RESPONSE: { return visit(req->toResponse()); } break; #define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ - case name__: { \ - auto casted = static_cast(req); \ - return visit(casted); \ + case MessageTag::name__: { \ + using F##name__ = TemplatedMessage; \ + auto c = static_cast(req); \ + return visit(c); \ } break; #include "message-macro-list.h" #undef CHPLDEF_MESSAGE diff --git a/tools/chpldef/Server.cpp b/tools/chpldef/Server.cpp index 9687423f73ff..82077b922077 100644 --- a/tools/chpldef/Server.cpp +++ b/tools/chpldef/Server.cpp @@ -18,19 +18,17 @@ * limitations under the License. */ -#include "./Server.h" - +#include "Server.h" +#include "Message.h" +#include "events.h" +#include "Transport.h" #include "chpl/framework/Context.h" #include "chpl/framework/UniqueString.h" -#include "chpl/framework/query-impl.h" -#include "chpl/parsing/parsing-queries.h" #include "chpl/util/filesystem.h" #include "chpl/util/printf.h" - #include "llvm/ADT/Optional.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/JSON.h" - #include #include @@ -72,6 +70,10 @@ void Server::trace(const char* fmt, ...) { VPRINTF_FORWARD_(fmt, logger_.vtrace); } +bool Server::Event::canRun(Server* ctx) const { + return ctx->state() == Server::READY; +} + Server::Server(Server::Configuration config) : config_(std::move(config)) { chapel_ = createCompilerContext(); @@ -91,51 +93,171 @@ Server::Server(Server::Configuration config) : config_(std::move(config)) { } } - // Set the logger verbosity level. + // Configure and install the logger. + logger.setHeader("[chpldef]"); logger.setLevel(config_.logLevel); - this->setLogger(std::move(logger)); + + // Set a quick alias for the transport. + this->transport_ = config_.transport.get(); + + doRegisterEssentialEvents(); } -chpl::owned Server::createCompilerContext() { +chpl::owned Server::createCompilerContext() const { chpl::Context::Configuration chplConfig; chplConfig.chplHome = config_.chplHome; auto ret = chpl::toOwned(new chpl::Context(std::move(chplConfig))); return ret; } -bool Server::shouldGarbageCollect() { +bool Server::shouldGarbageCollect() const { auto f = config_.garbageCollectionFrequency; if (f == 0) return false; return (revision_ % f) == 0; } -bool Server::shouldPrepareToGarbageCollect() { +bool Server::shouldPrepareToGarbageCollect() const { auto f = config_.garbageCollectionFrequency; if (f == 0) return false; return (revision_ % f) == 1; } -void Server::fmtImpl(std::stringstream& ss, FormatDetail dt, - const chpl::uast::AstNode* t) { - if (t == nullptr) { - ss << ""; - return; +void Server::doRegisterEssentialEvents() { + using namespace events; + registerEvent(chpl::toOwned(new ReadMessage())); + registerEvent(chpl::toOwned(new ResolveModules())); + registerEvent(chpl::toOwned(new PreparePublishDiagnostics())); +} + +void Server::doRunEvents(Event::When when, const Message* msg) { + for (auto& e : events_) { + CHPL_ASSERT(e.get()); + const bool run = (e->mask() & when) || (e->mask() & Event::ALWAYS); + if (!run || !e->canRun(this)) continue; + this->trace("Running event '%s'\n", e->name()); + e->run(this, msg, when); + } +} + +chpl::owned Server::dequeueOneMessage() { + if (messages_.empty()) return nullptr; + auto ret = std::move(messages_.front()); + messages_.pop(); + return ret; +} + +void Server::sendMessage(const Message* msg) { + if (transport_ == nullptr) return; + + if (auto j = msg->pack()) { + if (isLogTrace()) { + auto str = jsonToString(*j); + this->trace("Outgoing JSON is: %s\n", str.c_str()); + } + + auto status = transport_->sendJson(this, *j); + if (status != Transport::OK) { + CHPLDEF_FATAL(this, "Failed to send JSON!"); + } + } +} + +void Server::registerEvent(chpl::owned event) { + if (!event.get()) return; + events_.push_back(std::move(event)); +} + +void Server::enqueue(chpl::owned msg) { + if (!msg.get()) return; + messages_.push(std::move(msg)); +} + +bool Server::handle(chpl::owned msg) { + if (!msg.get()) return false; + bool ret = false; + + // Handle a response to an outbound request. + if (auto rsp = msg->toResponse()) { + CHPL_ASSERT(rsp->isCompleted()); + CHPL_ASSERT(msg->id().kind() != JsonValue::Null); + + auto it = idToOutboundRequest_.find(msg->idToString()); + if (it != idToOutboundRequest_.end()) { + auto& req = it->second; + CHPL_ASSERT(req->behavior() == Message::OUTBOUND_REQUEST); + req->handle(this, rsp); + idToOutboundRequest_.erase(it); + } else { + CHPLDEF_TODO(); + } + + return false; + } + + if (msg->status() == Message::PROGRESSING) CHPLDEF_TODO(); + + switch (msg->behavior()) { + case Message::INCOMING_REQUEST: + case Message::INCOMING_NOTIFY: { + if (msg->status() == Message::FAILED) CHPLDEF_TODO(); + msg->handle(this); + if (msg->status() == Message::FAILED) CHPLDEF_TODO(); + if (!msg->isNotification()) sendMessage(msg.get()); + } break; + + case Message::OUTBOUND_NOTIFY: + case Message::OUTBOUND_REQUEST: { + if (msg->status() == Message::FAILED) CHPLDEF_TODO(); + CHPL_ASSERT(msg->status() == Message::PENDING); + sendMessage(msg.get()); + + // Requests are stored for later evaluation. + if (!msg->isNotification()) { + auto idStr = msg->idToString(); + auto it = idToOutboundRequest_.find(idStr); + if (it != idToOutboundRequest_.end()) CHPLDEF_TODO(); + auto p = std::make_pair(idStr, std::move(msg)); + idToOutboundRequest_.emplace(std::move(p)); + } + } break; + + default: { + CHPLDEF_IMPOSSIBLE(); + } break; + } + + // Prepare to exit if we received the 'Exit' message. + if (msg->tag() == MessageTag::Exit) { + CHPL_ASSERT(state() == Server::SHUTDOWN); + + // Should not have any messages or pending requests in the queue. + if (!messages_.empty()) CHPLDEF_TODO(); + if (!idToOutboundRequest_.size()) CHPLDEF_TODO(); + ret = true; } - withChapel([&](auto chapel) { - ss << chpl::uast::asttags::tagToString(t->tag()) << " "; - if (auto nd = t->toNamedDecl()) ss << "'" << nd->name().c_str() << "' "; - auto loc = chpl::parsing::locateAst(chapel, t); - ss << "[" << loc.path().c_str() << ":" << loc.firstLine(); - ss << ":" << loc.firstColumn() << "]"; - }); + return ret; } -void Server::fmtImpl(std::stringstream& ss, FormatDetail dt, - const chpl::Location& t) { - ss << t.path().c_str() << ":" << t.firstLine() << ":" << t.firstColumn(); - ss << "-" << t.lastLine() << ":" << t.lastColumn(); +int Server::run() { + bool running = true; + int ret = 0; + + while (running) { + doRunEvents(Event::LOOP_START, nullptr); + + if (auto msg = dequeueOneMessage()) { + doRunEvents(Event::PRIOR_HANDLE, msg.get()); + const bool isTimeToExit = handle(std::move(msg)); + running = !isTimeToExit; + doRunEvents(Event::AFTER_HANDLE, msg.get()); + } + + logger().flush(); + } + + return ret; } } // end namespace 'chpldef' diff --git a/tools/chpldef/Server.h b/tools/chpldef/Server.h index 4c53d163f2a3..033f24f9e979 100644 --- a/tools/chpldef/Server.h +++ b/tools/chpldef/Server.h @@ -21,8 +21,11 @@ #ifndef CHPL_TOOLS_CHPLDEF_SERVER_H #define CHPL_TOOLS_CHPLDEF_SERVER_H -#include "./Logger.h" -#include "./misc.h" +#include "Format.h" +#include "Logger.h" +#include "Message.h" +#include "misc.h" +#include "Transport.h" #include "chpl/framework/Context.h" #include "chpl/framework/ErrorBase.h" #include "chpl/framework/UniqueString.h" @@ -32,29 +35,25 @@ #include #include #include +#include #include namespace chpldef { -class Initialize; -class Initialized; -class Shutdown; -class DidOpen; - class Server { public: - static constexpr const char* NAME = "chpldef"; - static constexpr const char* VERSION = "0.0.0"; + static constexpr auto NAME = "chpldef"; + static constexpr auto VERSION = "0.0.0"; static constexpr int DEFAULT_GC_FREQUENCY = 64; enum State { - UNINIT, /** Client has not sent 'Initialize' yet. */ - INIT, /** We have responded to 'Initialize'. */ + UNINITIALIZED, /** Client has not sent 'Initialize' yet. */ + SETUP, /** We have responded to 'Initialize'. */ READY, /** Client has sent us 'Initialized'. */ SHUTDOWN /** Client has sent us 'Shutdown'. */ }; - // TODO: Use this to configure server capabilities, etc. + /** This can be used to configure a server when creating it. */ struct Configuration { std::string logFile; Logger::Level logLevel = Logger::OFF; @@ -63,8 +62,10 @@ class Server { bool warnUnstable = false; bool enableStandardLibrary = false; bool compilerDebugTrace = false; + chpl::owned transport = nullptr; }; + /** Document synchronization messages will mutate text entries. */ struct TextEntry { int64_t version = -1; int64_t lastRevisionContentsUpdated = -1; @@ -73,6 +74,7 @@ class Server { using TextRegistry = std::map; + /** This error handler is installed into the Chapel frontend. */ class ErrorHandler : public chpl::Context::ErrorHandler { public: using Error = chpl::ErrorBase; @@ -90,46 +92,135 @@ class Server { inline size_t numErrors() { return errors_.size(); } }; + /** The server runs an event during each cycle of the event loop. */ + class Event { + public: + enum When { + NEVER = 0, + ALWAYS = 1, /** Before and after message handling. */ + LOOP_START = 2, /** Start of server loop iteration. */ + PRIOR_HANDLE = 4, /** Before a message is handled. */ + AFTER_HANDLE = 8, /** After a message is handled. */ + }; + + private: + const char* name_ = nullptr; + int mask_ = 0; + + public: + Event(const char* name, int mask) : name_(name), mask_(mask) {} + virtual ~Event() = default; + + inline const char* name() const { return name_; } + inline int mask() const { return mask_; } + + /** By default, events will not run until server state is 'READY'. */ + virtual bool canRun(Server* ctx) const; + + /** Run the event at the current stage, indicated by 'when'. */ + virtual void run(Server* ctx, const Message* msg, When when) = 0; + }; + private: - State state_ = UNINIT; + State state_ = UNINITIALIZED; Logger logger_; chpl::owned chapel_ = nullptr; Configuration config_; TextRegistry textRegistry_; - int64_t revision_; + int64_t revision_ = 0; ErrorHandler* errorHandler_ = nullptr; + std::queue> messages_; + std::map> idToOutboundRequest_; + std::vector> events_; + Transport* transport_ = nullptr; - chpl::owned createCompilerContext(); - bool shouldGarbageCollect(); - bool shouldPrepareToGarbageCollect(); + inline bool + isLogLevel(Logger::Level level) const { return logger_.level() == level; } - inline bool isLogLevel(Logger::Level level) const { - return logger_.level() == level; - } + chpl::owned createCompilerContext() const; + bool shouldGarbageCollect() const; + bool shouldPrepareToGarbageCollect() const; + void doRegisterEssentialEvents(); + void doRunEvents(Event::When when, const Message* msg=nullptr); + chpl::owned dequeueOneMessage(); + void sendMessage(const Message* msg); protected: - friend class chpldef::Initialize; - friend class chpldef::Initialized; - friend class chpldef::Shutdown; - friend class chpldef::DidOpen; + friend chpldef::Initialize; + friend chpldef::Initialized; + friend chpldef::Shutdown; + friend chpldef::DidOpen; inline void setState(State state) { state_ = state; } - inline TextRegistry& textRegistry() { return textRegistry_; } + inline TextRegistry& mutableTextRegistry() { return textRegistry_; } public: Server(Configuration config); ~Server() = default; + /** Event order matters, and events can be registered more than once. */ + void registerEvent(chpl::owned event); + + /** Enqueue a message to be worked on by the server. */ + virtual void enqueue(chpl::owned msg); + + /** Handling depends on the behavior of the message. There are four modes: + a message can either be a notification or a request, and it can either + be incoming (client-to-server) or outbound (server-to-client). + + For incoming requests, if the parameters of the request are unpacked, + then they are used to compute a result. The state of the message is + updated with the computation results. The message can then be packed + into JSON so that the results can be sent to the client. + + For incoming notifications, if the parameters of the notification + are unpacked, then they are used to compute a state change within + the server. Notifications do not compute a result, and no response + is returned to the client. + + For outbound requests, the server will input parameters to create the + request. The request is then packed into JSON and sent to the + client. The client is expected to send a separate response to the + server with an ID that matches the originating request. When the + response is received, it is given to the originating request. The + result contained in the response is used to compute a state change + within the server. + + Outbound notifications function similiarly to outbound requests, + except that the server does not expect a response from the client + (and none can be sent). + + This method returns 'true' if the handled message was the 'Exit' + message. This indicates that the server should cease execution. + */ + virtual bool handle(chpl::owned msg); + + /** Run the server main loop until exit. Return value is exit code. */ + int run(); + + /** Run the server main loop for 'n' iterations. If 'n' is '0' then the + server loop will run forever. */ + opt run(size_t n); + + /** Get a handle to the current transport layer. */ + inline Transport* transport() { return transport_; } + inline ErrorHandler* errorHandler() { return errorHandler_; } + inline State state() const { return state_; } + inline int64_t revision() const { return revision_; } + inline const chpl::Context* chapel() const { return chapel_.get(); } + inline const TextRegistry& textRegistry() const { return textRegistry_; } + void setLogger(Logger&& logger); inline Logger& logger() { return logger_; } inline bool isLogMessage() const { return isLogLevel(Logger::MESSAGES); } inline bool isLogVerbose() const { return isLogLevel(Logger::VERBOSE); } inline bool isLogTrace() const { return isLogLevel(Logger::TRACE); } + CHPLDEF_PFMT(2, 3, void message(const char* fmt, ...)); CHPLDEF_PFMT(2, 3, void verbose(const char* fmt, ...)); CHPLDEF_PFMT(2, 3, void trace(const char* fmt, ...)); @@ -160,21 +251,11 @@ class Server { return withChapel(CHPL_NO_MASK, f, ns...); } - enum FormatDetail { - DEFAULT = 0 - }; - -private: - void fmtImpl(std::stringstream& ss, FormatDetail dt, - const chpl::uast::AstNode* t); - void fmtImpl(std::stringstream& ss, FormatDetail dt, - const chpl::Location& t); -public: template - std::string fmt(const T& t, FormatDetail dt=FormatDetail::DEFAULT) { - std::stringstream ss; - fmtImpl(ss, dt, t); - return ss.str(); + std::string fmt(const T& t, Format::Detail dt=Format::DEFAULT) { + Format f(this, dt); + f.write(t); + return f.read(); } }; diff --git a/tools/chpldef/Transport.cpp b/tools/chpldef/Transport.cpp index ce8e4cb39d32..e4a4be6e8043 100644 --- a/tools/chpldef/Transport.cpp +++ b/tools/chpldef/Transport.cpp @@ -18,8 +18,8 @@ * limitations under the License. */ -#include "./Transport.h" - +#include "Transport.h" +#include "Server.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/JSON.h" #include @@ -27,18 +27,19 @@ namespace chpldef { -// TODO: Will block until it sees a '\n' delimited line. -static bool readLineTrimCarriageReturn(std::istream& is, std::string& out) { - std::getline(is, out); +static Transport::Status +streamReadLineTrimCarriageReturn(Transport* tp, std::string& out) { + auto ret = tp->read(0, out, Transport::READ_UNTIL_NEWLINE); + if (!out.empty() && out.back() == '\n') out.pop_back(); if (!out.empty() && out.back() == '\r') out.pop_back(); - bool ret = !is.bad() && !is.fail(); return ret; } // SEE: 'clangd::JSONTransport::readStandardMessage' for signal handling. -static bool readLength(std::istream& is, int length, std::string& out) { +static bool +streamReadLength(std::istream& is, int64_t length, std::string& out) { out.resize(length); - for (int bytes = length, read; bytes > 0; bytes -= read) { + for (int64_t bytes = length, read; bytes > 0; bytes -= read) { is.read(&out[0], bytes); read = is.gcount(); } @@ -47,18 +48,18 @@ static bool readLength(std::istream& is, int length, std::string& out) { // TODO: Remove the asserts by logging and returning an error/message. // SEE: 'clangd::JSONTransport::readStandardMessage' for robust impl... -bool Transport::readJsonBlocking(Server* ctx, std::istream& is, - JsonValue& out) { - std::string line; +Transport::Status Transport::readJson(Server* ctx, JsonValue& j) { + Status status = Transport::OK; bool err = false; - int length = 0; + std::string line; + int64_t length = 0; // Note the fatal crashes in this function. I've inserted these because // if we fail to read a line in the protocol, it's difficult or // impossible for the server to recover. At any rate, we can worry about // recovering later (and ideally, not reinvent the wheel). - err |= !readLineTrimCarriageReturn(is, line); - if (err) CHPLDEF_FATAL(ctx, "Reading first line of message data"); + status = streamReadLineTrimCarriageReturn(this, line); + if (status) CHPLDEF_FATAL(ctx, "Reading first line of message data"); static constexpr const char* prefix = "Content-Length:"; err |= !(line.rfind(prefix) == 0); @@ -72,35 +73,75 @@ bool Transport::readJsonBlocking(Server* ctx, std::istream& is, err |= length <= 0; if (err) CHPLDEF_FATAL(ctx, "Message length %s is invalid", str.c_str()); - err |= !readLineTrimCarriageReturn(is, line); + status = streamReadLineTrimCarriageReturn(this, line); + if (status) CHPLDEF_FATAL(ctx, "Reading CRLF before payload"); + err |= !line.empty(); if (err) CHPLDEF_FATAL(ctx, "Expected CRLF before payload"); - err |= !readLength(is, length, line); - if (err) CHPLDEF_FATAL(ctx, "Failed to read payload data"); + status = this->read(length, line); + if (status) CHPLDEF_FATAL(ctx, "Failed to read payload data"); - // Create and write out the JSON now. No need to move the string into - // the parse call because the JSON value will always copy. + // Parse and write out the JSON now. if (auto json = llvm::json::parse(line)) { - out = std::move(*json); - return true; + j = std::move(*json); + return OK; } // Failed to parse. - ctx->verbose("Failed to parse JSON object of length: %d\n", length); + ctx->verbose("Failed to parse JSON object of length: %lld\n", length); ctx->trace("String is: %s\n", line.c_str()); - return false; + return ERROR_JSON_PARSE_FAILED; } -// TODO: Is there a way to write to a 'std::ostream' with LLVM? -bool Transport::sendJsonBlocking(Server* ctx, std::ostream& os, - const JsonValue& json) { +// TODO: Is there a way to write the contents without the copies? +Transport::Status Transport::sendJson(Server* ctx, const JsonValue& j) { + std::stringstream ss; const bool pretty = false; - std::string s = jsonToString(json, pretty); - os << "Content-Length: " << s.size() << "\r\n\r\n" << s; - os.flush(); - bool ret = !os.bad() && !os.fail(); + std::string contents = jsonToString(j, pretty); + + ss << "Content-Length: " << contents.size() << "\r\n\r\n" << contents; + + auto ret = this->send(ss.str()); + + if (ret != OK) { + ctx->trace("Failed to send JSON object of length: %zu\n", + contents.size()); + ctx->trace("Payload is: %s\n", contents.c_str()); + } + return ret; } +Transport::Status +TransportStdio::read(int64_t size, std::string& str, int behavior) { + if (size < 0) return ERROR_INVALID_SIZE; + + bool readEntireContents = (size == 0); + bool readUntilNewline = (behavior & READ_UNTIL_NEWLINE); + int64_t bytesToRead = size; + Status ret = OK; + + if (readUntilNewline) { + if (!readEntireContents) CHPLDEF_TODO(); + std::getline(std::cin, str, '\n'); + bool err = std::cin.fail() || std::cin.bad(); + if (err) ret = ERROR; + return ret; + } + + if (readEntireContents) CHPLDEF_TODO(); + bool ok = streamReadLength(std::cin, bytesToRead, str); + if (!ok) ret = ERROR; + + return ret; +} + +Transport::Status +TransportStdio::send(const std::string& str, int behavior) { + std::cout << str; + bool err = std::cout.fail() || std::cout.bad(); + return err ? ERROR : OK; +} + } // end namespace 'chpldef' diff --git a/tools/chpldef/Transport.h b/tools/chpldef/Transport.h index 6892cf1eae17..0746cc8276de 100644 --- a/tools/chpldef/Transport.h +++ b/tools/chpldef/Transport.h @@ -21,34 +21,56 @@ #ifndef CHPL_TOOLS_CHPLDEF_TRANSPORT_H #define CHPL_TOOLS_CHPLDEF_TRANSPORT_H -#include "./Message.h" -#include "./Server.h" - -#include -#include +#include "misc.h" +#include +#include namespace chpldef { -/** - This class is intended to model or wrap some existing transport layer, - but is currently empty. -*/ +class Server; + +/** This is the base class for various transport layers. */ class Transport { public: - /** Helper which reads a message from a stream in a blocking way. - Returns 'false' if there was an error. If there was not an - error, then the formal 'outJson' will contain the JSON for the - message. */ - static bool - readJsonBlocking(chpldef::Server* ctx, std::istream& is, - JsonValue& out); - - /** Helper which sends JSON to a stream in a blocking way. - Returns 'false' if there was an error. */ - static bool - sendJsonBlocking(chpldef::Server* ctx, std::ostream& os, - const JsonValue& json); + /** Error codes indicate the result of a read or write. */ + enum Status { + OK = 0, + ERROR = 1, + ERROR_JSON_PARSE_FAILED = 2, + ERROR_INVALID_SIZE = 4, + }; + + /** Provides a way to configure each read or write. */ + enum Behavior { + DEFAULT = 0, + READ_UNTIL_NEWLINE = 1 + }; + + virtual ~Transport() = default; + + /** Read up to 'size' bytes into 'str' with the given behaviors 'b'. */ + virtual Status read(int64_t size, std::string& str, int behavior=0) = 0; + + /** Send the bytes of 'str' with the given behaviors 'b'. */ + virtual Status send(const std::string& str, int behavior=0) = 0; + + /** Read JSON into the given JSON value 'j'. */ + Status readJson(Server* ctx, JsonValue& j); + + /** Serialize the contents of the JSON 'j' into text and send it. */ + Status sendJson(Server* ctx, const JsonValue& j); +}; + +/** This transport layer performs blocking reads over STDIN and STDOUT. */ +class TransportStdio final : public Transport { +public: + TransportStdio() {} + virtual ~TransportStdio() = default; + virtual Status read(int64_t size, std::string& str, + int behavior=0) override; + virtual Status send(const std::string& str, + int behavior=0) override; }; } // end namespace 'chpldef' diff --git a/tools/chpldef/chpldef.cpp b/tools/chpldef/chpldef.cpp index 6cf750fcf82a..567e72b2dc8e 100644 --- a/tools/chpldef/chpldef.cpp +++ b/tools/chpldef/chpldef.cpp @@ -46,17 +46,16 @@ static Server::Configuration prepareServerConfig(int argc, char** argv) { ret.enableStandardLibrary = cmd::enableStandardLibrary; ret.compilerDebugTrace = cmd::compilerDebugTrace; - return ret; -} + // TODO: Configure transport with a flag. + ret.transport = chpl::toOwned(new TransportStdio()); -static chpl::owned maybePublishDiagnostics(Server* ctx) { - return nullptr; + return ret; } int main(int argc, char** argv) { auto config = prepareServerConfig(argc, argv); - Server context(std::move(config)); - Server* ctx = &context; + Server server(std::move(config)); + Server* ctx = &server; // Flush every log message immediately to avoid losing info on crash. auto& log = ctx->logger(); @@ -66,83 +65,7 @@ int main(int argc, char** argv) { log.filePath().c_str(), log.levelToString()); - int run = 1; - int ret = 0; - - while (run) { - ctx->message("Server awaiting message...\n"); - - JsonValue json(nullptr); - - // TODO: This operation blocks. We'd like non-blocking IO. There are a - // few ways to accomplish this. Ideally we find some sort of high-level, - // non-blocking "transport" abstraction that we can use. - // - // -- LLVM might offer transport layer APIs - // -- LLVM might offer non-blocking IO - // -- C++ futures might be useful here, but we need to be able to - // control which thread runs the future and its lifetime (to - // make sure that the thread reading e.g., stdin is reliably - // closed...). - // -- We can do it ourselves using C++ threads, CVs, and locks, and - // wrap it up in a neat little function that populates a queue - // of messages for the context. - bool ok = Transport::readJsonBlocking(ctx, std::cin, json); - CHPL_ASSERT(ok); - - if (log.level() == Logger::TRACE) { - ctx->trace("Incoming JSON is %s\n", jsonToString(json).c_str()); - } - - // Create a message from the incoming JSON... - auto msg = Message::request(ctx, std::move(json)); - CHPL_ASSERT(msg.get()); - - // Parsing the JSON could have failed. - if (msg->status() == Message::FAILED || msg->isResponse()) { - CHPLDEF_TODO(); - } - - CHPL_ASSERT(msg->status() == Message::PENDING); - CHPL_ASSERT(!msg->isOutbound()); - - // Prepare to exit if we received the 'Exit' message. - if (msg->tag() == Message::Exit) { - CHPL_ASSERT(ctx->state() == Server::SHUTDOWN); - run = 0; - } - - // Handle the request that was read. - auto rsp = Message::handle(ctx, msg.get()); - - // Maybe send a response. - if (rsp) { - auto pack = rsp->pack(); - if (log.level() == Logger::TRACE) { - auto str = jsonToString(pack); - ctx->trace("Outgoing JSON is %s\n", str.c_str()); - } - - ok = Transport::sendJsonBlocking(ctx, std::cout, std::move(pack)); - CHPL_ASSERT(ok); - - // Response was delayed. - } else if (!msg->isNotification()) { - CHPLDEF_FATAL(ctx, "Handler response should not be delayed!"); - } - - // TODO: Enqueue all messages onto an event queue instead. - if (auto notify = maybePublishDiagnostics(ctx)) { - CHPL_ASSERT(notify->isNotification()); - CHPL_ASSERT(notify->isOutbound()); - auto pack = notify->pack(); - ok = Transport::sendJsonBlocking(ctx, std::cout, std::move(pack)); - CHPL_ASSERT(ok); - } - - // Flush the log in case something goes wrong. - log.flush(); - } + int ret = ctx->run(); ctx->message("Server exiting with code '%d'\n", ret); log.flush(); diff --git a/tools/chpldef/compiler-gadgets.cpp b/tools/chpldef/compiler-gadgets.cpp new file mode 100644 index 000000000000..45f201eb42eb --- /dev/null +++ b/tools/chpldef/compiler-gadgets.cpp @@ -0,0 +1,108 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compiler-gadgets.h" + +namespace chpldef { + +const chpl::uast::BuilderResult& +parseFromUri(chpl::Context* chapel, chpl::UniqueString uri) { + using namespace chpl::parsing; + chpl::UniqueString empty; + auto& ret = parseFileToBuilderResultAndCheck(chapel, uri, empty); + return ret; +} + +const chpl::uast::BuilderResult& +parseFromUri(chpl::Context* chapel, const std::string& uri) { + auto ustr = chpl::UniqueString::get(chapel, uri); + return parseFromUri(chapel, ustr); +} + +static bool isSymbolOfInterest(const chpl::uast::AstNode* ast) { + if (ast->isIdentifier()) return true; + if (ast->isDot()) return true; + return false; +} + +// Preorder traversal to collect identifiers in lexical order. +static void doMapLinesInModule(chpl::Context* chapel, + const chpl::uast::BuilderResult& br, + LineToIdsMap& m, + const chpl::uast::AstNode* node) { + for (auto ast : node->children()) { + if (ast->isComment()) continue; + + if (isSymbolOfInterest(ast)) { + auto loc = br.idToLocation(ast->id(), chpl::UniqueString()); + CHPL_ASSERT(!loc.isEmpty()); + auto& v = m[loc.firstLine()]; + v.push_back(ast->id()); + } + + bool recurse = !ast->isModule() && !ast->isLeaf(); + if (recurse) { + doMapLinesInModule(chapel, br, m, ast); + } + } +} + +static LineToIdsMap +mapLinesToIdsInModuleImpl(chpl::Context* chapel, chpl::UniqueString uri) { + auto& br = parseFromUri(chapel, uri); + LineToIdsMap ret; + auto it = br.topLevelExpressions(); + for (auto ast : it) doMapLinesInModule(chapel, br, ret, ast); + return ret; +} + +const LineToIdsMap& +mapLinesToIdsInModule(chpl::Context* chapel, chpl::UniqueString uri) { + using namespace chpl; + QUERY_BEGIN(mapLinesToIdsInModule, chapel, uri); + auto ret = mapLinesToIdsInModuleImpl(chapel, uri); + return QUERY_END(ret); +} + +chpl::Location +locationFromUriAndPosition(chpl::Context* chapel, const std::string& uri, + const Position& pos) { + auto ustr = chpl::UniqueString::get(chapel, uri); + int l1 = pos.line + 1; + int c1 = pos.character + 1; + int l2 = pos.line + 1; + int c2 = pos.character + 1; + return chpl::Location(ustr, l1, c1, l2, c2); +} + +bool isCalledExpression(chpl::Context* chapel, + const chpl::uast::AstNode* ast) { + if (!ast) return false; + auto p = chpl::parsing::parentAst(chapel, ast); + if (!p) return false; + + if (auto call = p->toCall()) + if (auto ce = call->calledExpression()) + return ce == ast; + + return false; +} + +} // end namespace 'chpldef' diff --git a/tools/chpldef/compiler-gadgets.h b/tools/chpldef/compiler-gadgets.h new file mode 100644 index 000000000000..a41fad745199 --- /dev/null +++ b/tools/chpldef/compiler-gadgets.h @@ -0,0 +1,68 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CHPL_TOOLS_CHPLDEF_COMPILER_GADGETS_H +#define CHPL_TOOLS_CHPLDEF_COMPILER_GADGETS_H + +#include "protocol-types.h" +#include "chpl/framework/Context.h" +#include "chpl/framework/UniqueString.h" +#include "chpl/parsing/parsing-queries.h" +#include "chpl/resolution/resolution-queries.h" +#include "chpl/resolution/resolution-types.h" + +// This header contains 'compiler gadgets', functions manipulating the +// Chapel compiler that may be useful for multiple LSP computations. +// They come in two flavors: +// +// -- Those that take 'Server*' as the first argument. Generally, this +// implies that the gadget will log to the log file, as it will +// implement a more substantial operation. +// -- Those that take the 'chpl::Context' only. These are smaller gadgets +// that do not deserve a line in the logger. +// +namespace chpldef { + +using LineToIdsMap = std::map>; + +/** Parse a top level module given a path. */ +const chpl::uast::BuilderResult& +parseFromUri(chpl::Context* chapel, chpl::UniqueString uri); + +/** Parse a top level module given a path. */ +const chpl::uast::BuilderResult& +parseFromUri(chpl::Context* chapel, const std::string& uri); + +/** A compiler query which maps source lines to AST nodes on that line. */ +const LineToIdsMap& +mapLinesToIdsInModule(chpl::Context* chapel, chpl::UniqueString uri); + +/** Given a cursor position and URI, create a Chapel location. */ +chpl::Location +locationFromUriAndPosition(chpl::Context* chapel, const std::string& uri, + const Position& pos); + +/** Determine if a given AST is the base expression of a call. */ +bool isCalledExpression(chpl::Context* chapel, + const chpl::uast::AstNode* ast); + +} // end namespace 'chpldef' + +#endif diff --git a/tools/chpldef/compute-goto-declaration.cpp b/tools/chpldef/compute-goto-declaration.cpp index bc6ee9cc4d39..88faca9c4e6d 100644 --- a/tools/chpldef/compute-goto-declaration.cpp +++ b/tools/chpldef/compute-goto-declaration.cpp @@ -18,83 +18,12 @@ * limitations under the License. */ +#include "compiler-gadgets.h" #include "./Message.h" #include "./Server.h" -#include "chpl/framework/query-impl.h" -#include "chpl/parsing/parsing-queries.h" -#include "chpl/resolution/resolution-queries.h" -#include "chpl/resolution/resolution-types.h" namespace chpldef { -static const chpl::uast::BuilderResult& -parseFromPath(chpl::Context* chapel, chpl::UniqueString path) { - using namespace chpl::parsing; - chpl::UniqueString empty; - auto& ret = parseFileToBuilderResultAndCheck(chapel, path, empty); - return ret; -} - -namespace { - using LineToIdsMap = std::map>; -} - -static bool isSymbolOfInterest(const chpl::uast::AstNode* ast) { - if (ast->isIdentifier()) return true; - if (ast->isDot()) return true; - return false; -} - -// Preorder traversal to collect identifiers in lexical order. -static void doMapLinesInModule(chpl::Context* chapel, - const chpl::uast::BuilderResult& br, - LineToIdsMap& m, - const chpl::uast::AstNode* node) { - for (auto ast : node->children()) { - if (ast->isComment()) continue; - - if (isSymbolOfInterest(ast)) { - auto loc = br.idToLocation(ast->id(), chpl::UniqueString()); - CHPL_ASSERT(!loc.isEmpty()); - auto& v = m[loc.firstLine()]; - v.push_back(ast->id()); - } - - bool recurse = !ast->isModule() && !ast->isLeaf(); - if (recurse) { - doMapLinesInModule(chapel, br, m, ast); - } - } -} - -static LineToIdsMap -mapLinesToIdsInModule(chpl::Context* chapel, chpl::UniqueString path) { - auto& br = parseFromPath(chapel, path); - LineToIdsMap ret; - auto it = br.topLevelExpressions(); - for (auto ast : it) doMapLinesInModule(chapel, br, ret, ast); - return ret; -} - -static const LineToIdsMap& -mapLinesToIdsInModuleQuery(chpl::Context* chapel, chpl::UniqueString path) { - using namespace chpl; - QUERY_BEGIN(mapLinesToIdsInModuleQuery, chapel, path); - auto ret = mapLinesToIdsInModule(chapel, path); - return QUERY_END(ret); -} - -static chpl::Location -chplLocFromUriAndPos(chpl::Context* chapel, const std::string& uri, - Position pos) { - auto path = chpl::UniqueString::get(chapel, uri); - int l1 = pos.line + 1; - int c1 = pos.character + 1; - int l2 = pos.line + 1; - int c2 = pos.character + 1; - return chpl::Location(path, l1, c1, l2, c2); -} - static const chpl::uast::AstNode* chooseAstClosestToLocation(chpl::Context* chapel, const chpl::uast::AstNode* astOld, @@ -106,18 +35,20 @@ chooseAstClosestToLocation(chpl::Context* chapel, auto locOld = astOld ? locateAst(chapel, astOld) : chpl::Location(); auto locNew = locateAst(chapel, astNew); + if (!locNew.contains(loc)) return astOld; if (!astOld) return astNew; // Prefer the location furthest towards the end of the range. + // TODO: Need to compute distance. auto ret = (locNew > locOld) ? astNew : astOld; + return ret; } static const chpl::uast::AstNode* astAtLocation(Server* ctx, chpl::Location loc) { - const auto& m = ctx->withChapel(mapLinesToIdsInModuleQuery, - loc.path()); + const auto& m = ctx->withChapel(mapLinesToIdsInModule, loc.path()); auto it = m.find(loc.firstLine()); if (it == m.end()) return nullptr; @@ -144,7 +75,9 @@ astAtLocation(Server* ctx, chpl::Location loc) { } }); - if (ret == last) break; + + // TODO: Are we at a fixed point? + if (ret && ret == last) break; } return ret; @@ -182,6 +115,7 @@ sourceAstToIds(Server* ctx, const chpl::uast::AstNode* ast) { auto& rr = resolveModule(ctx, mod->id()); auto& re = rr.byAst(ast); + bool isCallBaseExpression = ctx->withChapel(isCalledExpression, ast); if (re.isBuiltin()) CHPLDEF_TODO(); @@ -192,6 +126,22 @@ sourceAstToIds(Server* ctx, const chpl::uast::AstNode* ast) { if (auto idTarget = re.toId()) { ret.push_back(idTarget); + // It's a call base expression, so we need to pick apart the call. + } else if (isCallBaseExpression) { + auto p = ctx->withChapel(chpl::parsing::parentAst, ast); + auto call = p->toFnCall(); + auto& reCall = rr.byAst(call); + + if (auto idTarget = re.toId()) { + ret.push_back(idTarget); + } else { + if (auto tfs = reCall.mostSpecific().only()) { + ret.push_back(tfs->id()); + } else { + CHPLDEF_TODO(); + } + } + // Else, more work is required... } else { CHPLDEF_TODO(); @@ -211,7 +161,7 @@ static std::variant computeDeclarationPoints(Server* ctx, const TextDocumentPositionParams& p) { auto& uri = p.textDocument.uri; auto& pos = p.position; - auto loc = ctx->withChapel(chplLocFromUriAndPos, uri, pos); + auto loc = ctx->withChapel(locationFromUriAndPosition, uri, pos); ctx->verbose("Looking for AST at '%s'\n", ctx->fmt(loc).c_str()); @@ -243,8 +193,9 @@ computeDeclarationPoints(Server* ctx, const TextDocumentPositionParams& p) { return ret; } -Declaration::ComputedResult -Declaration::compute(Server* ctx, const Params& p) { +template <> +Declaration::ComputeResult +Declaration::compute(Server* ctx, ComputeParams p) { DeclarationResult ret; ret.result = computeDeclarationPoints(ctx, p); return ret; @@ -253,8 +204,9 @@ Declaration::compute(Server* ctx, const Params& p) { /** This functionality is turned off in the server capabilities tab, because Chapel does not distinguish between a definition and a declaration in the way that some languages such as C/C++ do. */ -Definition::ComputedResult -Definition::compute(Server* ctx, const Params& p) { +template <> +Definition::ComputeResult +Definition::compute(Server* ctx, ComputeParams p) { DefinitionResult ret; ret.result = computeDeclarationPoints(ctx, p); return ret; diff --git a/tools/chpldef/compute-lifecycle.cpp b/tools/chpldef/compute-lifecycle.cpp index e2959488e392..c98e98305583 100644 --- a/tools/chpldef/compute-lifecycle.cpp +++ b/tools/chpldef/compute-lifecycle.cpp @@ -77,8 +77,9 @@ static ServerInfo configureServerInfo(Server* ctx) { return ret; } -Initialize::ComputedResult -Initialize::compute(Server* ctx, const Params& p) { +template <> +Initialize::ComputeResult +Initialize::compute(Server* ctx, ComputeParams p) { // Set the log verbosity level if it was requested. if (auto trace = p.trace) { @@ -94,8 +95,8 @@ Initialize::compute(Server* ctx, const Params& p) { } // Set the server to the 'INIT' state. - CHPL_ASSERT(ctx->state() == Server::UNINIT); - ctx->setState(Server::INIT); + CHPL_ASSERT(ctx->state() == Server::UNINITIALIZED); + ctx->setState(Server::SETUP); Result ret; @@ -106,22 +107,25 @@ Initialize::compute(Server* ctx, const Params& p) { return ret; } -Initialized::ComputedResult -Initialized::compute(Server* ctx, const Params& p) { - CHPL_ASSERT(ctx->state() == Server::INIT); +template <> +Initialized::ComputeResult +Initialized::compute(Server* ctx, ComputeParams p) { + CHPL_ASSERT(ctx->state() == Server::SETUP); ctx->setState(Server::READY); return {}; } -Shutdown::ComputedResult -Shutdown::compute(Server* ctx, const Params& p) { +template <> +Shutdown::ComputeResult +Shutdown::compute(Server* ctx, ComputeParams p) { CHPL_ASSERT(ctx->state() == Server::READY); ctx->setState(Server::SHUTDOWN); return {}; } -Exit::ComputedResult -Exit::compute(Server* ctx, const Params& p) { +template <> +Exit::ComputeResult +Exit::compute(Server* ctx, ComputeParams p) { CHPL_ASSERT(ctx->state() == Server::SHUTDOWN); return {}; } diff --git a/tools/chpldef/compute-synchronization.cpp b/tools/chpldef/compute-synchronization.cpp index 4dae02e04c57..e25c2236fce6 100644 --- a/tools/chpldef/compute-synchronization.cpp +++ b/tools/chpldef/compute-synchronization.cpp @@ -18,20 +18,20 @@ * limitations under the License. */ -#include "./Message.h" -#include "./Server.h" +#include "Message.h" +#include "Server.h" #include "chpl/parsing/parsing-queries.h" namespace chpldef { -DidOpen::ComputedResult -DidOpen::compute(Server* ctx, const Params& p) { +template<> +DidOpen::ComputeResult DidOpen::compute(Server* ctx, ComputeParams p) { auto& tdi = p.textDocument; - auto& e = ctx->textRegistry()[tdi.uri]; + auto& e = ctx->mutableTextRegistry()[tdi.uri]; if (e.isOpen) { CHPLDEF_TODO(); - return fail(); + return {}; } CHPL_ASSERT(tdi.version > e.version); @@ -56,20 +56,21 @@ DidOpen::compute(Server* ctx, const Params& p) { return {}; } -DidChange::ComputedResult -DidChange::compute(Server* ctx, const Params& p) { +template<> +DidChange::ComputeResult DidChange::compute(Server* ctx, ComputeParams p) { CHPLDEF_TODO(); return {}; } -DidSave::ComputedResult -DidSave::compute(Server* ctx, const Params& p) { + +template<> +DidSave::ComputeResult DidSave::compute(Server* ctx, ComputeParams p) { CHPLDEF_TODO(); return {}; } -DidClose::ComputedResult -DidClose::compute(Server* ctx, const Params& p) { +template<> +DidClose::ComputeResult DidClose::compute(Server* ctx, ComputeParams p) { CHPLDEF_TODO(); return {}; } diff --git a/tools/chpldef/events.cpp b/tools/chpldef/events.cpp new file mode 100644 index 000000000000..b45506aea4ef --- /dev/null +++ b/tools/chpldef/events.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compiler-gadgets.h" +#include "events.h" +#include "Server.h" +#include "Transport.h" + +namespace chpldef { +namespace events { + +/** Always run this - we need to be reading messages. */ +bool ReadMessage::canRun(Server* ctx) const { return true; } + +void ReadMessage::run(Server* ctx, const Message* msg, When when) { + CHPL_ASSERT(!msg && when == Event::LOOP_START); + + ctx->message("Attempting to read message...\n"); + auto ts = ctx->transport(); + + if (ts == nullptr) { + ctx->message("Giving up because no transport was found!\n"); + return; + } + + JsonValue json(nullptr); + + // TODO: This operation blocks. We'd like non-blocking IO. There are a + // few ways to accomplish this. Ideally we find some sort of high-level, + // non-blocking "transport" abstraction that we can use. + // + // -- LLVM might offer transport layer APIs + // -- LLVM might offer non-blocking IO + // -- C++ futures might be useful here, but we need to be able to + // control which thread runs the future and its lifetime (to + // make sure that the thread reading e.g., stdin is reliably + // closed...). + // -- We can do it ourselves using C++ threads, CVs, and locks, and + // wrap it up in a neat little function that populates a queue + // of messages for the context. + auto status = ts->readJson(ctx, json); + CHPL_ASSERT(status == Transport::OK); + + if (ctx->isLogTrace()) { + ctx->trace("Incoming JSON is %s\n", jsonToString(json).c_str()); + } + + // Create a message from the incoming JSON... + auto work = Message::create(ctx, std::move(json)); + CHPL_ASSERT(work.get()); + + // Add the message to the work queue. + ctx->enqueue(std::move(work)); +} + +static bool resolveModulesForMessageTag(MessageTag::Kind tag) { + return tag == MessageTag::DidOpen; +} + +void ResolveModules::run(Server* ctx, const Message* msg, When when) { + if (when == Event::PRIOR_HANDLE) { + CHPL_ASSERT(msg); + resolveModulesAfterHandle_ = resolveModulesForMessageTag(msg->tag()); + return; + } + + CHPL_ASSERT(!msg && when == AFTER_HANDLE); + + if (!resolveModulesAfterHandle_) return; + if (lastRevisionResolved_ == ctx->revision()) return; + + // Commit to resolving. + lastRevisionResolved_ = ctx->revision(); + + for (const auto& p : ctx->textRegistry()) { + const auto& uri = p.first; + const auto& entry = p.second; + if (!entry.isOpen) continue; + ctx->withChapel([&](auto chapel) { + auto& br = parseFromUri(chapel, uri); + for (auto ast : br.topLevelExpressions()) { + if (!ast->isModule()) continue; + auto& rr = chpl::resolution::resolveModule(chapel, ast->id()); + std::ignore = rr; + } + }); + } +} + +void PreparePublishDiagnostics::run(Server* ctx, const Message* msg, + When when) { + std::ignore = ctx; +} + +} // end namespace 'events' +} // end namespace 'chpldef' diff --git a/tools/chpldef/events.h b/tools/chpldef/events.h new file mode 100644 index 000000000000..96587e793bc1 --- /dev/null +++ b/tools/chpldef/events.h @@ -0,0 +1,65 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CHPL_TOOLS_CHPLDEF_EVENTS_H +#define CHPL_TOOLS_CHPLDEF_EVENTS_H + +#include "Server.h" + +namespace chpldef { +namespace events { + +/** This event reads a message from the transport layer. */ +class ReadMessage : public Server::Event { + static constexpr auto NAME = "ReadMessage"; + static constexpr auto WHEN = LOOP_START; +public: + ReadMessage() : Event(NAME, WHEN) {} + virtual ~ReadMessage() = default; + virtual bool canRun(Server* ctx) const override; + virtual void run(Server* ctx, const Message* msg, When when) override; +}; + +/** This event ensures modules are resolved. */ +class ResolveModules : public Server::Event { + static constexpr auto NAME = "ResolveModules"; + static constexpr auto WHEN = AFTER_HANDLE; + bool resolveModulesAfterHandle_ = false; + int64_t lastRevisionResolved_ = -1; +public: + ResolveModules() : Event(NAME, WHEN) {} + virtual ~ResolveModules() = default; + virtual void run(Server* ctx, const Message* msg, When when) override; +}; + +/** This event may prepare a 'PublishDiagnostics' message to send. */ +class PreparePublishDiagnostics : public Server::Event { + static constexpr auto NAME = "PreparePublishDiagnostics"; + static constexpr auto WHEN = PRIOR_HANDLE | AFTER_HANDLE; +public: + PreparePublishDiagnostics() : Event(NAME, WHEN) {} + virtual ~PreparePublishDiagnostics() = default; + virtual void run(Server* ctx, const Message* msg, When when) override; +}; + +} // end namespace 'events' +} // end namespace 'chpldef' + +#endif diff --git a/tools/chpldef/misc.cpp b/tools/chpldef/misc.cpp index c41b0e5380fe..62e9f4441c6e 100644 --- a/tools/chpldef/misc.cpp +++ b/tools/chpldef/misc.cpp @@ -59,4 +59,14 @@ bool cast(std::string str, int& out) noexcept { return true; } +bool cast(std::string str, int64_t& out) noexcept { + char* end = nullptr; + const char* cstr = str.c_str(); + errno = 0; + int64_t x = std::strtoll(cstr, &end, 10); + if (errno == ERANGE || end == cstr) return false; + out = x; + return true; +} + } // end namespace 'chpldef' diff --git a/tools/chpldef/misc.h b/tools/chpldef/misc.h index 1abca47ba6b4..419482ba24d5 100644 --- a/tools/chpldef/misc.h +++ b/tools/chpldef/misc.h @@ -88,6 +88,9 @@ inline opt option(const T& t) { return opt(t); } /** Cast a string to an integer, return 'false' if there was an error. */ bool cast(std::string str, int& out) noexcept; +/** Cast a string to an integer, return 'false' if there was an error. */ +bool cast(std::string str, int64_t& out) noexcept; + /** Print a JSON value's kind as a string. */ const char* jsonKindToString(const JsonValue& json); diff --git a/tools/chpldef/protocol-types.cpp b/tools/chpldef/protocol-types.cpp index 5c961d48a85b..2a149bc23784 100644 --- a/tools/chpldef/protocol-types.cpp +++ b/tools/chpldef/protocol-types.cpp @@ -236,11 +236,19 @@ JsonValue Position::toJson() const { return ret; } +bool Position::operator==(const Position& rhs) const { + return line == rhs.line && character == rhs.character; +} + bool TextDocumentPositionParams::fromJson(const JsonValue& j, JsonPath p) { JsonMapper m(j, p); return m && MAP_(m, textDocument) && MAP_(m, position); } +Location::Location(const chpl::Location& loc) + : uri(loc.path().c_str()), + range(Range(loc)) {} + bool Location::fromJson(const JsonValue& j, JsonPath p) { JsonMapper m(j, p); return m && MAP_(m, uri) && MAP_(m, range); @@ -253,6 +261,10 @@ JsonValue Location::toJson() const { return ret; } +bool Location::operator==(const Location& rhs) const { + return uri == rhs.uri && range == rhs.range; +} + bool LocationLink::fromJson(const JsonValue& j, JsonPath p) { JsonMapper m(j, p); return m && MAP_(m, originSelectionRange) && @@ -270,6 +282,25 @@ JsonValue LocationLink::toJson() const { return ret; } +static Position constructPosition(int chapelLine, int chapelColumn) { + int line = chapelLine - 1; + int character = chapelColumn - 1; + CHPL_ASSERT(line >= 0 && character >= 0); + return Position(line, character); +} + +static Position constructPositionAtStart(const chpl::Location& loc) { + return constructPosition(loc.firstLine(), loc.firstColumn()); +} + +static Position constructPositionAtEnd(const chpl::Location& loc) { + return constructPosition(loc.lastLine(), loc.lastColumn()); +} + +Range::Range(const chpl::Location& loc) + : start(constructPositionAtStart(loc)), + end(constructPositionAtEnd(loc)) {} + bool Range::fromJson(const JsonValue& j, JsonPath p) { JsonMapper m(j, p); return m && MAP_(m, start) && MAP_(m, end); @@ -282,6 +313,10 @@ JsonValue Range::toJson() const { return ret; } +bool Range::operator==(const Range& rhs) const { + return start == rhs.start && end == rhs.end; +} + JsonValue DeclarationResult::toJson() const { if (!result) return nullptr; if (auto v = std::get_if(&*result)) return *v; diff --git a/tools/chpldef/protocol-types.h b/tools/chpldef/protocol-types.h index 65b11d35fe03..bf58783bc8e3 100644 --- a/tools/chpldef/protocol-types.h +++ b/tools/chpldef/protocol-types.h @@ -21,8 +21,8 @@ #ifndef CHPL_TOOLS_CHPLDEF_PROTOCOL_TYPES_H #define CHPL_TOOLS_CHPLDEF_PROTOCOL_TYPES_H -#include "./Logger.h" -#include "./misc.h" +#include "Logger.h" +#include "misc.h" #include #include #include @@ -38,6 +38,14 @@ */ namespace chpldef { +/** Forward declare request params/result types, even if they may not all + be defined (e.g., a notification does not have a result). */ +#define CHPLDEF_MESSAGE(name__, x1__, x2__, x3__) \ + struct name__##Params; \ + struct name__##Result; +#include "./message-macro-list.h" +#undef CHPLDEF_MESSAGE + using OPT_TODO_TYPE = opt; struct BaseProtocolType { @@ -72,9 +80,10 @@ struct ProtocolType : BaseProtocolType { }; struct ClientInfo : ProtocolTypeRecv { - virtual bool fromJson(const JsonValue& j, JsonPath p) override; std::string name; opt version; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; /** TODO: Used to store 'chpldef' specific initialization options. */ @@ -96,18 +105,19 @@ struct ClientCapabilities : ProtocolTypeRecv { }; struct WorkspaceFolder : ProtocolTypeRecv { - virtual bool fromJson(const JsonValue& j, JsonPath p) override; std::string uri; std::string name; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; struct TraceLevel : ProtocolTypeRecv { - virtual bool fromJson(const JsonValue& j, JsonPath p) override; Logger::Level level; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; struct InitializeParams : ProtocolTypeRecv { - virtual bool fromJson(const JsonValue& j, JsonPath p) override; opt processId; opt clientInfo; opt locale; @@ -117,16 +127,17 @@ struct InitializeParams : ProtocolTypeRecv { ClientCapabilities capabilities; opt trace; opt> workspaceFolders; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; struct SaveOptions : ProtocolTypeSend { - virtual JsonValue toJson() const override; opt includeText; + + virtual JsonValue toJson() const override; }; struct TextDocumentSyncOptions : ProtocolTypeSend { - virtual JsonValue toJson() const override; - /** Valid 'change' values. */ enum Change { None = 0, Full = 1, @@ -137,12 +148,13 @@ struct TextDocumentSyncOptions : ProtocolTypeSend { opt willSave; opt willSaveWaitUntil; opt save; + + virtual JsonValue toJson() const override; }; /** Some of the 'provider' queries have more advanced types we can swap in to configure further -- see 'DeclarationRegistrationOptions'. */ struct ServerCapabilities : ProtocolTypeSend { - virtual JsonValue toJson() const override; opt positionEncoding; opt textDocumentSync; OPT_TODO_TYPE notebookDocumentSync; @@ -178,19 +190,22 @@ struct ServerCapabilities : ProtocolTypeSend { opt workspaceSymbolProvider; OPT_TODO_TYPE workspace; OPT_TODO_TYPE experimental; + + virtual JsonValue toJson() const override; }; struct ServerInfo : ProtocolTypeSend { - virtual JsonValue toJson() const override; std::string name; opt version; -}; -struct InitializeResult : ProtocolTypeSend { virtual JsonValue toJson() const override; +}; +struct InitializeResult : ProtocolTypeSend { ServerCapabilities capabilities; opt serverInfo; + + virtual JsonValue toJson() const override; }; struct InitializedParams : EmptyProtocolType {}; @@ -199,79 +214,117 @@ struct ShutdownResult : EmptyProtocolType {}; struct ExitParams : EmptyProtocolType {}; struct TextDocumentItem : ProtocolTypeRecv { - virtual bool fromJson(const JsonValue& j, JsonPath p) override; std::string uri; std::string languageId; - int64_t version; + int64_t version = -1; std::string text; + + TextDocumentItem() = default; + TextDocumentItem(std::string uri, std::string languageId, int64_t version, + std::string text) + : uri(std::move(uri)), + languageId(std::move(languageId)), + version(version), + text(std::move(text)) {} + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; struct DidOpenParams : ProtocolTypeRecv { - virtual bool fromJson(const JsonValue& j, JsonPath p) override; TextDocumentItem textDocument; + + DidOpenParams() = default; + DidOpenParams(TextDocumentItem textDocument) + : textDocument(std::move(textDocument)) {} + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; struct DidChangeParams : ProtocolTypeRecv { - virtual bool fromJson(const JsonValue& j, JsonPath p) override; TextDocumentItem textDocument; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; struct DidSaveParams : ProtocolTypeRecv { - virtual bool fromJson(const JsonValue& j, JsonPath p) override; TextDocumentItem textDocument; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; struct DidCloseParams : ProtocolTypeRecv { - virtual bool fromJson(const JsonValue& j, JsonPath p) override; TextDocumentItem textDocument; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; struct TextDocumentIdentifier : ProtocolTypeRecv { - virtual bool fromJson(const JsonValue& j, JsonPath p) override; std::string uri; + + TextDocumentIdentifier() = default; + TextDocumentIdentifier(std::string uri) : uri(std::move(uri)) {} + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; struct Position : ProtocolType { uint64_t line = 0; /** Zero-based position. */ uint64_t character = 0; /** Zero-based position. */ - virtual bool fromJson(const JsonValue& j, JsonPath p) override; - virtual JsonValue toJson() const override; Position() = default; Position(uint64_t line, uint64_t character) : line(line), character(character) {} + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + virtual JsonValue toJson() const override; + bool operator==(const Position& rhs) const; + bool operator!=(const Position& rhs) const; + bool operator<(const Position& rhs) const; + bool operator>(const Position& rhs) const; + bool operator<=(const Position& rhs) const; + bool operator>=(const Position& rhs) const; }; struct Range : ProtocolType { Position start; Position end; + Range() = default; + Range(Position start, Position end) + : start(std::move(start)), + end(std::move(end)) {} + Range(const chpl::Location& loc); virtual bool fromJson(const JsonValue& j, JsonPath p) override; virtual JsonValue toJson() const override; - Range() = default; - Range(Position start, Position end) : start(start), end(end) {} + bool operator==(const Range& rhs) const; }; struct TextDocumentPositionParams : ProtocolTypeRecv { - virtual bool fromJson(const JsonValue& j, JsonPath p) override; TextDocumentIdentifier textDocument; Position position; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; }; struct Location : ProtocolType { - virtual bool fromJson(const JsonValue& j, JsonPath p) override; - virtual JsonValue toJson() const override; std::string uri; Range range; -}; -struct LocationLink : ProtocolType { + Location(std::string uri, Range range) + : uri(std::move(uri)), + range(std::move(range)) {} + Location() = default; + Location(const chpl::Location& loc); virtual bool fromJson(const JsonValue& j, JsonPath p) override; virtual JsonValue toJson() const override; + bool operator==(const Location& rhs) const; + bool operator!=(const Location& rhs) const; +}; + +struct LocationLink : ProtocolType { opt originSelectionRange; std::string targetUri; Range targetRange; Range targetSelectionRange; + + virtual bool fromJson(const JsonValue& j, JsonPath p) override; + virtual JsonValue toJson() const override; }; using LocationArray = std::vector; @@ -281,13 +334,15 @@ struct DeclarationParams : TextDocumentPositionParams {}; struct DefinitionParams : TextDocumentPositionParams {}; struct DeclarationResult : ProtocolTypeSend { - virtual JsonValue toJson() const override; opt> result; + + virtual JsonValue toJson() const override; }; struct DefinitionResult : ProtocolTypeSend { - virtual JsonValue toJson() const override; opt> result; + + virtual JsonValue toJson() const override; }; /** Instantiate only if 'T' is derived from 'ProtocolType'. */ diff --git a/tools/chpldef/test/.utility.cpp.swp b/tools/chpldef/test/.utility.cpp.swp new file mode 100644 index 0000000000000000000000000000000000000000..9850c319a2fcb89e8337e9b85984c330b3233055 GIT binary patch literal 24576 zcmeI3dyr&RdBCp-%X1gOidyS~vx5#ZyE8q*f&_Zm(P19!;5;_-U|EnF@AN%0bEW&< z_TJk&J1(0TurL+#hbfD!XpvwUlRsh-LY{vZC0l4&B~pnLLM%x_5=@m*6GAK!t&se_ zbMEcFJ>4_AD@ID_E`Id(z32Iz@0{~}-#N21c=N=Rx~?$j@Oho%JQqIbedwXf&f8OR z9IqS(D}gTEq2=Y~pOL}6x7@gQaO$QT54>~pRF3nDjRFTo!&0rHgCrjC^pSx`SHpOq zvff`>b*sAEU!rCm57=Mt97w{jEWfH{PcIK7I!*>^iC^}U^+Ksy-KxsYyN(1p5)cX0 zlI8wuw>wwuz0yqe%e!`|*I&BOkxfSe9SL+K(2+n#0v!o-B+!vSM*(L&`TzfOvEzImJ_YZE z!|(=pVu#~A1b+x0hF^vmn1plS;q8u-KtEgv=fXDlw~HL-`*0F|8}5M*!`*NIE{AjA zwQv@kypT4*3QWN;ybFe42mJd5ln38~zkmne78rq@upKUh3*g!F9p_QF8v-c99WVv^ z;5xV#-T-IAS@0To@;t}+UAP-&VH&339k2tQKG$*n7M_G}z^B27QMefX1(`ksBHOPc z7*6~>e%sp{E!FP{~O=+L~az_Hh8-v zw4ad!2PYSY=jO&{=X-cpi2V=ho?f+ozq+!wk;<(lVYlMF+OKw3v*-S@>XA39%Hv4K zg%zDF`pef(h9x)g!(cdy-1VWJZeI2Fs$-_kEKj#&kZk=!{G(P*Y=Z2ylA0>tH!@9`HFaE3aA<@n zcGV5MvhFD*M;A>$O+R=;wYhA?Vky#YLOS!c5cnUc=_wt@?uwr9dV2d*oOs1zB|M^g zs=cP5T<(KZUWrw;r;#Y}{N_wy?2D*u-g$jA$?n}ucwD~mmwg>6GWFCVpW^4aSu;}h zc~dOLI`DFp^_0ROPHcPPB=UomZbha(bug_$m1qIYGh6U$+g>RCw@Rf3G{^O_$d*Id1rwANLBW15vVz?6DKmy$5LK1(np{caX( zDJ+*sG)adhb+D4G#uLF@Sc^(1L92wb*0e3WRkAIjZI(zwPvquRT`d%!N+EG0=2tei zWO~>%^G(mkqp&g@Cn8+#Y4s^LPEzqo8L{a|YZOlLZg$DUQD%W_nk0#?$n+qo(e*Cd?XEs`L{o>1j?HAIH_vGH zQmsSN_f*Tow4G_EGSX118FrTCS72F=`iJ5~mK?JP z&*_L(7mKrF!=s1Ofs~0q5wL8yWfp5zA(>ug@@$rSZft(xpiCMPoL-wU&6{T5yKh#*X?)Bg?6Fhn(c=N`3mfLsRZd(KqQ%E^VWEEEtWweyiOw zO}IOf@jLxay`(-Bi;XVH9@FT7-G7H#=1C((OBXBIHaim50ydw;J$v>!H^pIavsA!dzzivmJ%^MM>pQv5?WhrT-DWF(leWZo>3G-949C*?k zrOd)YwvD-#eX>yyx|(fM`8A?-5o)u-NcIhV$S;ZHSWeeOxp`YGj)chRD5e4bSoT+H z*7Q-5ZlBYXq>c1O%!V-%R;qqkM-$i*G96n+Ws?SE>r}g%?YlNr+851LUG4{^a?R7K zt5Cl}yxPUWwR-@tGb%o-BWri8YJn%l(W+LHen|(hE=Y6SPzmWuIduzR7sWH zV5NrrrdF`pq5wllRdrPHW7!cfC<9N5IW#eUU}j-n9U7jU9iE<_7@Jcwvub2!dX!~s zW}08)YIypvde6l4XrIzPRYXc3tzyi^iYom|6+6u{P4av($d+me_GwF_#fZ%(2m_j+ zx`yY}#9WuUVR&w0u1^}Y5R|3pNPnQ_M-;TQuG}hhl-#AVrc-NDtGg1VMz9~KwTL|; zSYfLTmyOkUwY9Z^TXjpTx)4Sy1C%Cmj0YwsM#iS+#`^bi?c8QM z9M)9DU02~!;!?JYR#o}6+$3u^NmG-egeH}J(IK^lbn@wpP|`W5s6zF~0ClCK*wwpF zt*5mGVWMi7nTc5Y%P1xyF6pXNg9pGbtRyC8+7QyLh0z@%(X=Odc@LX@X1XFWl~QKR zz`34h%y74C`W5*rtBG0-xy>HMrrS$eF`*H*jG^?$4@@sckb+IUcX06P{=I`&UZ+Oz zdelTvYDutnkOX_LQU~-}Stp4)h!yNco*I+ots41sbW|VF<*+KIotix84D5Eq|Gx`= zdj~$c`2X4Y{)_nf5(791zX}@m!`omdTma|6x$p>n|2;4beefg7dI6q=r{Ej#1bhkZ zgjKj2E`f_d>i!{Z_){2%x5N4H9De>|@EQ0RydRc9;s7t=*Z&ay3toVKg)hJnxEh|v ze?JNL!(H%xD8iM{1<&KZ|1?gH`ix54B1@xKQN%!7jO zyIK2sQ$O7rq_R0~;^-^&l3n>m9qpq92<=pMPtJe-F$^DiP>sS8%d%`DEXcBq#l~W1 z(O?ZMhhddfK)O0&0tm9ZC>we(iBlVg4IskjEUzoJY}rT>(y+J@Y{1gNx?mF%+r3)g zuW-ylB{q0H@{l_AVvbY`VgY0^gr-#DK}nLPJ~!+&#RlET`Lh*$s7DeqTTby|!kkX9 z;fnVLSBRCRjHO`XS*IIkFhlV2hZMYU+&@~YPUYhPaXkWn9Qu?;C@dDI8wJxV>nH*p zwyvNTj@Xb#n!k~g*g-v~DJ7RZFD}v%m|2PnNm!kf4Q$NXL1ItYz#Wy@wO~m+@Twcd zT7nDI!rICbI1*D?~86WlF|{F zO_>+40Od3d;Ik6nt|jZ%{+Y$MKWm`^n(-Sq}x9Jl@C zOW0CvyVC{7*=%XbxvRU;eG{uhY4GeyN@6>C{n&hgDy{luV*lylmW@o)GM8VcOmbE# zCRzWG`4MS?MONfi3$rqEJBR7CiSSo!lwJa&IR zUp9sRc`kzcQR5AOIEyQIp>#Zm!aLWEepxEg!+a; z$*hZrsJ@o5Ca+v%-L^$jaH#ZQNx-fhC~EFr08Po@WBZ7lkY0nTi9 zPR;&QFH=Q1iu$@7Q+SPymN61Ns1N1Q#yE}#BP7lSv0aMMkN{{s#6{^GJC%ug6-tXN zgpKg78Jb+l4J)xZd%y;k&z*MP%p|+FWJ^ujp;JpRKk&;gM^8rU>2cjNBGAy?t#*}k z|BxR~>$Pmom`3vwO+XCoB-bQ3NwlTxWbyFheAm^TciNfi$IX&VB3b{_-Y@Yt@`NT( zjcjtBRFXMz2^0=@tGBJ?wR z%-`|->vt zXbusywXjuKOC;hSMuf!+ukru_^M6h!Ms(&}Lc(q)x@UqL_2))RVo1-2qZrqi$5xhN za^$@!Yf6hXBcHq_Dz=o(Trg*f^1Ih2+n6@@^mNMLq?5vatV~&p?AfSv#(p+wxV==C z`Zki%y3^7XOs&ix%8J?AV{dW%83{QGGKHbA#)qjO=Xf=&l_lDGM5`sueXymPUuKM! zIQA>31%eBHVunW=)Dk_&xp=K!fW2BHHAxtt-mk42zA@BbCgDxQt%wy@C3?&OlRjEd znO`?8&&?c6oz3aAwe4aQj&#CG^`{i1phoL5XW<(e0IR6C$v@>>J@B$I%`1kSmK)V; zuSLdQHz(?i5_77bm1vTgL6>^^hvc7mpF;#<;x4JKc{u)N#H>3k7DyK4k>)h{vTXuQ zl8Y|eV1Qe_KIhByZQ%e88NIY($yzQg%YT}xel-PlGR=930iN{olN-!xISFBPJm!de zjl)%{KSm#SnV^rjZ{xL~B$1cQNn{Zj4V4@>dphP2++#M%RnjyWWqCgLRqbU0&X?W5 zEw9IZY^G#g&4^?Xkow6)nCjuA&6b}mY++(guk?VD99l5>nuD_WNz`sTwfAJx^>k?K z@MeB9ZuQHjSzOYHd_A<;T)X^g&6ke@^$`qhJ!xqdP|6|(CJ-wA|4#f_x!W%O|9I*Z zehYv9b8tVLfRDqk!^a?meXtF_xWjRN1y`{6p+1HI4#JK=F+01v_mNMIIz0pxE1 zo*@qK36Q@9kb42|g3I7V`u_-g4(@>vR=|bZ;UE|prSj=~btKS{Kt}={33MdTk${!J z@%$;uE9E9xCzh-^O*4Oz*11P!|2l$0Q*pMN;}eZQ@uq*p)=1KcB?tEJ${M%9Oz@;n zuGMY0V1e)4i6s;MHE}=_mO8`BXlD{jK5ajBt2lGBV=O}|)5SF1-o+tS@)NrgC!TTp q&r1$`* + +void TestClient::breakpoint() {} + +Server TestClient::createServerInstance() { + Server::Configuration conf; + conf.transport = nullptr; + return { std::move(conf) }; +} + +chpl::Context TestClient::createChapelCompilerInstance() { + chpl::Context::Configuration conf; + return { std::move(conf) }; +} + +int64_t TestClient::bumpVersionForUri(const std::string& uri) { + auto it = uriToVersion_.find(uri); + if (it != uriToVersion_.end()) return it->second++; + uriToVersion_[uri] = 0; + return 0; +} + +JsonValue TestClient::createUniqueMessageId() { + return messageIdCounter_++; +} + +opt TestClient::sendInitialize() { + Initialize::Params p; + auto msg = Initialize::create(createUniqueMessageId(), std::move(p)); + msg->handle(ctx_); + if (auto r = msg->result()) return *r; + return {}; +} + +void TestClient::sendInitialized() { + Initialized::Params p; + auto msg = Initialized::create(createUniqueMessageId(), std::move(p)); + msg->handle(ctx_); +} + +void TestClient::sendExit() { + CHPLDEF_TODO(); +} + +void TestClient::sendShutdown() { + CHPLDEF_TODO(); +} + +void TestClient::advanceServerToReady() { + if (ctx_->state() == Server::UNINITIALIZED) { + std::ignore = sendInitialize(); + CHPL_ASSERT(ctx_->state() == Server::SETUP); + } + + if (ctx_->state() == Server::SETUP) { + sendInitialized(); + CHPL_ASSERT(ctx_->state() == Server::READY); + } +} + +void TestClient::sendDidOpen(const std::string& uri, + const std::string& text) { + auto ver = bumpVersionForUri(uri); + TextDocumentItem tdi { uri, "chapel", ver, text }; + DidOpen::Params p { std::move(tdi) }; + auto msg = DidOpen::create(nullptr, std::move(p)); + msg->handle(ctx_); +} + +static std::string mentionSymbol(const chpl::uast::AstNode* ast) { + if (auto x = ast->toIdentifier()) return x->name().c_str(); + if (auto x = ast->toDot()) return x->field().c_str(); + CHPLDEF_TODO(); + return {}; +} + +static Location +locationFromAst(chpl::Context* chapel, const chpl::uast::AstNode* ast) { + CHPL_ASSERT(ast); + auto loc = chpl::parsing::locateAst(chapel, ast); + return Location(std::move(loc)); +} + +static void +doCollectMentions(chpl::Context* chapel, + const chpl::uast::AstNode* ast, + std::vector& decls, + std::vector& mentions) { + if (auto decl = ast->toNamedDecl()) { + decls.push_back(decl); + } else if (ast->isIdentifier() || ast->isDot()) { + TestClient::Mention m; + m.tag = ast->tag(); + m.symbol = mentionSymbol(ast); + m.isCallBaseExpression = chpldef::isCalledExpression(chapel, ast); + m.source = locationFromAst(chapel, ast); + mentions.push_back(std::move(m)); + } + + const bool canRecurse = !ast->isFunction(); + if (!canRecurse) return; + + for (auto child : ast->children()) { + doCollectMentions(chapel, child, decls, mentions); + } +} + +static void +doFixupMentionTargets(chpl::Context* chapel, + std::vector& decls, + std::vector& mentions) { + std::map> nameToDecls; + std::map functionMentionIdx; + + for (auto& nd : decls) { + auto name = nd->name().c_str(); + auto& v = nameToDecls[name]; + v.push_back(nd); + if (!nd->isFunction()) CHPL_ASSERT(v.size() == 1); + } + + for (size_t i = 0; i < mentions.size(); i++) { + auto& m = mentions[i]; + auto& name = m.symbol; + + // Get the declaration(s) with the name 'name'. + auto it = nameToDecls.find(name); + CHPL_ASSERT(it != nameToDecls.end()); + auto& v = it->second; + + const chpl::uast::NamedDecl* nd = nullptr; + + // Calls map linearly in order to simulate overloading. + if (m.isCallBaseExpression) { + auto& idx = functionMentionIdx[name]; + CHPL_ASSERT(v.size() >= 1); + CHPL_ASSERT(idx < v.size()); + nd = v[idx++]; + + // Everything else just maps by name. + } else { + CHPL_ASSERT(v.size() == 1); + nd = v[0]; + } + + CHPL_ASSERT(nd); + m.target = locationFromAst(chapel, nd); + } +} + +std::vector +TestClient::collectMentions(const std::string& uri, + const std::string& text) { + auto chapelContext = createChapelCompilerInstance(); + auto chapel = &chapelContext; + std::vector ret; + std::vector decls; + + // Set the file text and parse the file. + auto ustr = chpl::UniqueString::get(chapel, uri); + chpl::parsing::setFileText(chapel, ustr, text); + auto& br = parseFromUri(chapel, uri); + + for (auto ast : br.topLevelExpressions()) { + doCollectMentions(chapel, ast, decls, ret); + } + + // Fixing up the targets allows tests to write decls out of order. + doFixupMentionTargets(chapel, decls, ret); + + return ret; +} + +std::vector +TestClient::collectLineLengthsInSource(const std::string& text) { + std::vector ret; + uint64_t character = 0; + for (auto c : text) { + if (c == '\n') { + ret.push_back(character); + character = 0; + } else { + character += 1; + } + } + return ret; +} + +opt +TestClient::sendDeclaration(const std::string& uri, Position cursor) { + TextDocumentIdentifier tdi(uri); + Declaration::Params p; + p.textDocument = std::move(tdi); + p.position = std::move(cursor); + + // Create and handle the message directly rather than enqueue it for + // processing by the server main loop. + auto msg = Declaration::create(createUniqueMessageId(), std::move(p)); + msg->handle(ctx_); + + // TODO: This is absurd, we need to flatten 'DeclarationResult'. + if (auto r = msg->result()) { + if (auto& res = r->result) { + if (auto p = std::get_if(&(*res))) { + auto& arr = *p; + CHPL_ASSERT(arr.size() <= 1); + if (arr.size() == 1) return arr[0]; + return {}; + } + } + } + return {}; +} + +std::string TestClient::Mention::toString() const { + std::stringstream ss; + ss << chpl::uast::asttags::tagToString(tag) << " " << symbol << " ("; + ss << source.range.start.line << ":" << source.range.start.character; + ss << "-"; + ss << source.range.end.line << ":" << source.range.end.character; + ss << " -> "; + ss << target.range.start.line << ":" << target.range.start.character; + ss << "-"; + ss << target.range.end.line << ":" << target.range.end.character; + ss << ")"; + return ss.str(); +} diff --git a/tools/chpldef/test/TestClient.h b/tools/chpldef/test/TestClient.h new file mode 100644 index 000000000000..f30cdd535ca2 --- /dev/null +++ b/tools/chpldef/test/TestClient.h @@ -0,0 +1,117 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CHPL_TOOLS_CHPLDEF_TESTCLIENT_H +#define CHPL_TOOLS_CHPLDEF_TESTCLIENT_H + +#include "compiler-gadgets.h" +#include "events.h" +#include "Format.h" +#include "Logger.h" +#include "Message.h" +#include "misc.h" +#include "protocol-types.h" +#include "Server.h" +#include "Transport.h" +#include + +using namespace chpldef; + +/** This 'TestClient' simulates a LSP client, but issues direct calls to + server functionality rather than by sending JSON over some sort of + transport layer as a normal client would (also, usually the client + operates as a separate process - here for the sake of unit testing + the client and server operate in the same process). This client should + be used to test most language server queries. +*/ +class TestClient { + Server server_; + Server* ctx_ = &server_; + int64_t messageIdCounter_ = 0; + std::map uriToVersion_; + std::ostream& dbg_ = ctx_->transport() ? std::cerr : std::cout; + + static Server createServerInstance(); + static chpl::Context createChapelCompilerInstance(); + int64_t bumpVersionForUri(const std::string& uri); + +public: + TestClient() : server_(createServerInstance()), ctx_(&server_) {} + ~TestClient() = default; + + /** Get a handle to a stream suitable for printing debug infos. */ + inline std::ostream& dbg() { return dbg_; } + + /** For requests, prepare a new ID number embedded in JSON. */ + JsonValue createUniqueMessageId(); + + /** Send the 'Initialize' message to the server. */ + opt sendInitialize(); + + /** Send the 'Initialized' message to the server. */ + void sendInitialized(); + + /** Send the 'Shutdown' message to the server. */ + void sendShutdown(); + + /** Send the 'Exit' message to the server. */ + void sendExit(); + + /** Complete the initialization handshake. After this call the server will + be in the 'READY' state and can accept any message. */ + void advanceServerToReady(); + + /** Send a 'DidOpen' notification. */ + void sendDidOpen(const std::string& uri, const std::string& text); + + /** A mention represents a text range the client is interested in. */ + struct Mention { + using AstTag = chpl::uast::asttags::AstTag; + AstTag tag; + bool isCallBaseExpression = false; + std::string symbol; + Location source; + Location target; + + std::string toString() const; + }; + + /** Collect locations the client is interested in using a separate + instance of the Chapel compiler. The way mentions are mapped to + target locations depends on the target. Calls are mapped linearly + (where call N of 'foo' maps to overload N of 'foo') in order to + simulate overloading. Every other declaration is mapped by name. + */ + static std::vector + collectMentions(const std::string& uri, const std::string& text); + + /** Collect line lengths within the file using a separate instance + of the Chapel compiler. */ + static std::vector + collectLineLengthsInSource(const std::string& text); + + /** Send a 'Declaration' message. May return a 'Location'. */ + opt sendDeclaration(const std::string& uri, Position cursor); + + /** For testing/debugging purposes. */ + static void breakpoint(); +}; + +#endif diff --git a/tools/chpldef/test/test-declaration.cpp b/tools/chpldef/test/test-declaration.cpp new file mode 100644 index 000000000000..d63bf4be445a --- /dev/null +++ b/tools/chpldef/test/test-declaration.cpp @@ -0,0 +1,112 @@ +/* + * Copyright 2020-2023 Hewlett Packard Enterprise Development LP + * Copyright 2004-2019 Cray Inc. + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "./TestClient.h" + +static void testDeclaration(const std::string& uri, const std::string& text) { + TestClient client; + + client.advanceServerToReady(); + + /** Send 'DidOpen' to communicate text open in the editor. */ + client.sendDidOpen(uri, text); + + /** Collect locations we are interested in. */ + auto lineLengths = TestClient::collectLineLengthsInSource(text); + auto mentions = TestClient::collectMentions(uri, text); + + for (auto& m : mentions) { + auto& range = m.source.range; + auto& start = range.start; + auto& end = range.end; + + // TODO: Fuzz characters to the left and the right of the identifier. + if (m.tag == chpl::uast::asttags::Identifier) { + CHPL_ASSERT(start.line == end.line); + const auto line = start.line; + for (uint64_t c = start.character; c <= end.character; c++) { + Position cursor(line, c); + client.dbg() << m.toString(); + client.dbg() << " w/ cursor (" << cursor.line << ":"; + client.dbg() << cursor.character << ")" << std::endl; + + auto targetLoc = client.sendDeclaration(uri, cursor); + CHPL_ASSERT(targetLoc == m.target); + } + } else { + CHPLDEF_TODO(); + } + } +} + +static void test0(void) { + const auto uri = "test0.chpl"; + const auto text = R"""( + class C { + var f = 0; + } + + var x1 = 0; + var x2 = 0.0; + var x3 = new C(); + + x1; + x2; + // x3.f; TODO: Dot targets. // BUG: Dot hugs right. + + x1 = 8; + x2 = x1 * 2; // BUG: Assigned expression not considered. + + proc foo() {} + proc bar(a: owned C?, b: int=0) { a; b; } + + foo(); + bar(x3, x1); + )"""; + + testDeclaration(uri, text); +} + +static void test1(void) { + const auto uri = "test1.chpl"; + const auto text = R"""( + class C {} + + proc foo() {} + proc foo(a: int) {} // BUG: Formal not considered... + proc foo(b: real, c: owned C?) {} + proc foo(d: owned C?, e: int=0) {} + + // TODO: How to map calls to procedures with default values? + // Short of using the resolver itself... + foo(); + foo(8); + foo(16.0, new C()); + foo(new C()); + )"""; + + testDeclaration(uri, text); +} + +int main(int argc, char** argv) { + test0(); + test1(); + return 0; +} diff --git a/tools/chpldef/test/utility.cpp b/tools/chpldef/test/utility.cpp deleted file mode 100644 index 113b75d63d86..000000000000 --- a/tools/chpldef/test/utility.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2020-2023 Hewlett Packard Enterprise Development LP - * Copyright 2004-2019 Cray Inc. - * Other additional copyright holders may be indicated within. - * - * The entirety of this work is licensed under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - diff --git a/tools/chpldef/test/utility.h b/tools/chpldef/test/utility.h deleted file mode 100644 index b0e26790e517..000000000000 --- a/tools/chpldef/test/utility.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2020-2023 Hewlett Packard Enterprise Development LP - * Copyright 2004-2019 Cray Inc. - * Other additional copyright holders may be indicated within. - * - * The entirety of this work is licensed under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef CHPL_TOOLS_CHPLDEF_TEST_UTILITY_H -#define CHPL_TOOLS_CHPLDEF_TEST_UTILITY_H - -#endif From 3dd7c3d76f2ee3d74d2796262c23033824b5ccfa Mon Sep 17 00:00:00 2001 From: David Longnecker Date: Wed, 2 Aug 2023 12:26:23 -0700 Subject: [PATCH 20/27] Respond to reviewer feedback from #22465 Signed-off-by: David Longnecker --- tools/chpldef/Server.cpp | 18 ++++++++------ tools/chpldef/Server.h | 29 ++++++++++++++-------- tools/chpldef/compute-goto-declaration.cpp | 6 ++--- tools/chpldef/compute-synchronization.cpp | 3 +-- tools/chpldef/misc.h | 10 ++------ tools/chpldef/protocol-types.cpp | 8 ++---- 6 files changed, 35 insertions(+), 39 deletions(-) diff --git a/tools/chpldef/Server.cpp b/tools/chpldef/Server.cpp index 82077b922077..86250bf000f3 100644 --- a/tools/chpldef/Server.cpp +++ b/tools/chpldef/Server.cpp @@ -74,13 +74,14 @@ bool Server::Event::canRun(Server* ctx) const { return ctx->state() == Server::READY; } -Server::Server(Server::Configuration config) : config_(std::move(config)) { - chapel_ = createCompilerContext(); +Server::Server(Server::Configuration config) + : chapel_(createCompilerContext(config)), + config_(std::move(config)) { // Install the server error handler. auto handler = toOwned(new ErrorHandler(this)); errorHandler_ = handler.get(); - chapel_->installErrorHandler(std::move(handler)); + chapel_.installErrorHandler(std::move(handler)); // Open the server log. Logger logger; @@ -104,15 +105,16 @@ Server::Server(Server::Configuration config) : config_(std::move(config)) { doRegisterEssentialEvents(); } -chpl::owned Server::createCompilerContext() const { +chpl::Context +Server::createCompilerContext(const Server::Configuration& config) const { chpl::Context::Configuration chplConfig; - chplConfig.chplHome = config_.chplHome; - auto ret = chpl::toOwned(new chpl::Context(std::move(chplConfig))); - return ret; + chplConfig.chplHome = config.chplHome; + return chpl::Context(std::move(chplConfig)); } bool Server::shouldGarbageCollect() const { auto f = config_.garbageCollectionFrequency; + if (f == 1) return true; if (f == 0) return false; return (revision_ % f) == 0; } @@ -120,7 +122,7 @@ bool Server::shouldGarbageCollect() const { bool Server::shouldPrepareToGarbageCollect() const { auto f = config_.garbageCollectionFrequency; if (f == 0) return false; - return (revision_ % f) == 1; + return (revision_ % f) == (f - 1); } void Server::doRegisterEssentialEvents() { diff --git a/tools/chpldef/Server.h b/tools/chpldef/Server.h index 033f24f9e979..90ce4f0f24c8 100644 --- a/tools/chpldef/Server.h +++ b/tools/chpldef/Server.h @@ -121,10 +121,13 @@ class Server { virtual void run(Server* ctx, const Message* msg, When when) = 0; }; + /** Note that below we list the compiler instance before the server + configuration so that the server initializer can use the config + to help initialize the compiler. */ private: State state_ = UNINITIALIZED; Logger logger_; - chpl::owned chapel_ = nullptr; + chpl::Context chapel_; Configuration config_; TextRegistry textRegistry_; int64_t revision_ = 0; @@ -137,7 +140,7 @@ class Server { inline bool isLogLevel(Logger::Level level) const { return logger_.level() == level; } - chpl::owned createCompilerContext() const; + chpl::Context createCompilerContext(const Configuration& config) const; bool shouldGarbageCollect() const; bool shouldPrepareToGarbageCollect() const; void doRegisterEssentialEvents(); @@ -211,10 +214,14 @@ class Server { inline int64_t revision() const { return revision_; } - inline const chpl::Context* chapel() const { return chapel_.get(); } + inline const chpl::Context* chapel() const { return &chapel_; } inline const TextRegistry& textRegistry() const { return textRegistry_; } + inline int garbageCollectionFrequency() const { + return config_.garbageCollectionFrequency; + } + void setLogger(Logger&& logger); inline Logger& logger() { return logger_; } inline bool isLogMessage() const { return isLogLevel(Logger::MESSAGES); } @@ -234,21 +241,21 @@ class Server { /** Execute code with controlled access to the Chapel context. */ template - auto withChapel(WithChapelConfig c, F&& f, Ns... ns) - -> decltype(f(chapel_.get(), ns...)) { - if (shouldGarbageCollect()) chapel_->collectGarbage(); + auto withChapel(WithChapelConfig c, F&& f, Ns&&... ns) + -> decltype(f(&chapel_, std::forward(ns)...)) { + if (shouldGarbageCollect()) chapel_.collectGarbage(); if (c & CHPL_BUMP_REVISION) { - chapel_->advanceToNextRevision(shouldPrepareToGarbageCollect()); + chapel_.advanceToNextRevision(shouldPrepareToGarbageCollect()); ++revision_; } - return f(chapel_.get(), ns...); + return f(&chapel_, std::forward(ns)...); } /** Execute code with controlled access to the Chapel context. */ template - auto withChapel(F&& f, Ns... ns) - -> decltype(withChapel(CHPL_NO_MASK, f, ns...)) { - return withChapel(CHPL_NO_MASK, f, ns...); + auto withChapel(F&& f, Ns&&... ns) + -> decltype(withChapel(CHPL_NO_MASK, f, std::forward(ns)...)) { + return withChapel(CHPL_NO_MASK, f, std::forward(ns)...); } template diff --git a/tools/chpldef/compute-goto-declaration.cpp b/tools/chpldef/compute-goto-declaration.cpp index 88faca9c4e6d..3934e758d1e3 100644 --- a/tools/chpldef/compute-goto-declaration.cpp +++ b/tools/chpldef/compute-goto-declaration.cpp @@ -33,6 +33,7 @@ chooseAstClosestToLocation(chpl::Context* chapel, CHPL_ASSERT(astNew); CHPL_ASSERT(astOld != astNew); + // TODO: This can run 'locateAst' on the same AST node O(n) times. auto locOld = astOld ? locateAst(chapel, astOld) : chpl::Location(); auto locNew = locateAst(chapel, astNew); @@ -182,10 +183,7 @@ computeDeclarationPoints(Server* ctx, const TextDocumentPositionParams& p) { static_cast(loc.firstColumn()-1) }; Position end = { static_cast(loc.lastLine()-1), static_cast(loc.lastColumn()-1) }; - Location out; - out.range = { start, end }; - out.uri = loc.path().str(); - + Location out(loc.path().str(), { start, end }); ret.push_back(std::move(out)); } } diff --git a/tools/chpldef/compute-synchronization.cpp b/tools/chpldef/compute-synchronization.cpp index e25c2236fce6..1844a3f597d7 100644 --- a/tools/chpldef/compute-synchronization.cpp +++ b/tools/chpldef/compute-synchronization.cpp @@ -42,8 +42,7 @@ DidOpen::ComputeResult DidOpen::compute(Server* ctx, ComputeParams p) { // to have changed and the "truth of the file's contents" are determined // by the client as long as it has the file open. Cannot implicitly // read from disk, so have to bump the revision to ensure correctness. - ctx->withChapel(Server::CHPL_BUMP_REVISION, - [&](auto chapel) { + ctx->withChapel(Server::CHPL_BUMP_REVISION, [&](auto chapel) { chpl::parsing::setFileText(chapel, tdi.uri, tdi.text); auto& fc = chpl::parsing::fileText(chapel, tdi.uri); CHPL_ASSERT(!fc.error()); diff --git a/tools/chpldef/misc.h b/tools/chpldef/misc.h index 419482ba24d5..d67748608215 100644 --- a/tools/chpldef/misc.h +++ b/tools/chpldef/misc.h @@ -70,14 +70,8 @@ using JsonObject = llvm::json::Object; using JsonPath = llvm::json::Path; using JsonMapper = llvm::json::ObjectMapper; -/** Wrapper around LLVM's optional type. */ -#if LLVM_VERSION_MAJOR >= 16 - #include - static_assert(std::is_same, llvm::Optional>::value); - template using opt = std::optional; -#else - template using opt = llvm::Optional; -#endif +/** Wrapper around Chapel's optional type. */ +template using opt = chpl::optional; template inline opt option(T&& t) { return opt(std::move(t)); } diff --git a/tools/chpldef/protocol-types.cpp b/tools/chpldef/protocol-types.cpp index 2a149bc23784..0ad89166f44b 100644 --- a/tools/chpldef/protocol-types.cpp +++ b/tools/chpldef/protocol-types.cpp @@ -185,12 +185,8 @@ JsonValue TextDocumentSyncOptions::toJson() const { bool TextDocumentItem::fromJson(const JsonValue& j, JsonPath p) { JsonMapper m(j, p); - bool ret = m; - ret &= MAP_(m, uri); - ret &= MAP_(m, languageId); - ret &= MAP_(m, version); - ret &= MAP_(m, text); - return ret; + return MAP_(m, uri) && MAP_(m, languageId) && MAP_(m, version) && + MAP_(m, text); } bool DidOpenParams::fromJson(const JsonValue& j, JsonPath p) { From 13b2fb6686c4024c9b992a598186495486aa2ea2 Mon Sep 17 00:00:00 2001 From: David Longnecker Date: Wed, 13 Sep 2023 16:26:52 -0700 Subject: [PATCH 21/27] Respond to most reviewer feedback Signed-off-by: David Longnecker --- tools/chpldef/Message.cpp | 43 +++++++++++----------- tools/chpldef/Message.h | 30 ++++++++------- tools/chpldef/Transport.cpp | 6 +-- tools/chpldef/Transport.h | 13 ++++--- tools/chpldef/compiler-gadgets.cpp | 24 +++++++----- tools/chpldef/compiler-gadgets.h | 7 ++-- tools/chpldef/compute-goto-declaration.cpp | 8 ++-- tools/chpldef/events.cpp | 2 +- tools/chpldef/test/TestClient.cpp | 3 +- 9 files changed, 72 insertions(+), 64 deletions(-) diff --git a/tools/chpldef/Message.cpp b/tools/chpldef/Message.cpp index e5a760b96b83..54e9b45bda74 100644 --- a/tools/chpldef/Message.cpp +++ b/tools/chpldef/Message.cpp @@ -259,32 +259,32 @@ std::string Message::idToString() const { return {}; } -template +template Message::Error -TemplatedMessage::unpack(JsonValue j, Params& p, std::string* note) { +TemplatedMessage::unpack(JsonValue j, Params& p, std::string& note) { llvm::json::Path::Root root; if (p.fromJson(j, root)) return Message::OK; - *note = "Failed to unpack JSON for parameters"; + note = "Failed to unpack JSON for parameters"; return Message::ERR_INVALID_PARAMS; } -template +template Message::Error -TemplatedMessage::unpack(JsonValue j, Result& r, std::string* note) { +TemplatedMessage::unpack(JsonValue j, Result& r, std::string& note) { llvm::json::Path::Root root; if (r.fromJson(j, root)) return Message::OK; - *note = "Failed to unpack JSON for result"; + note = "Failed to unpack JSON for result"; return Message::ERR_INVALID_PARAMS; } -template +template Message::Error -TemplatedMessage::unpack(Response* rsp, Result& r, std::string* note) { +TemplatedMessage::unpack(Response* rsp, Result& r, std::string& note) { CHPLDEF_TODO(); return Message::OK; } -template +template chpl::owned> TemplatedMessage::create(JsonValue id, Params p) { auto msg = new TemplatedMessage(K, std::move(id), Message::OK, {}, @@ -293,12 +293,12 @@ TemplatedMessage::create(JsonValue id, Params p) { return ret; } -template +template chpl::owned> TemplatedMessage::createFromJson(JsonValue id, JsonValue j) { Params p = {}; std::string note; - auto error = unpack(std::move(j), p, ¬e); + auto error = unpack(std::move(j), p, note); auto msg = new TemplatedMessage(K, std::move(id), error, std::move(note), std::move(p)); @@ -434,28 +434,27 @@ class TemplatedMessageHandler { private: Server* ctx_; M* msg_; - const char* dsc_ = description(); - std::string fmt_ = ctx_->fmt(static_cast(msg_)); public: TemplatedMessageHandler(Server* ctx, M* msg) : ctx_(ctx), msg_(msg) {} TemplatedMessageHandler() = default; - inline const char* description() const { + inline std::string fmt() { return ctx_->fmt(static_cast(msg_)); } + inline const char* dsc() const { constexpr bool hasBehavior = BEHAVIOR != Message::NO_BEHAVIOR; auto ret = hasBehavior ? Message::behaviorToString(BEHAVIOR) : "message"; return ret; } void logComputationPrelude() { - ctx_->message("Handling %s '%s'\n", dsc_, fmt_.c_str()); + ctx_->message("Handling %s '%s'\n", dsc(), fmt().c_str()); } void logComputationEpilogue(const ComputeResult& cr) { if (cr.error != Message::OK) { - ctx_->message("The %s %s failed with code '%s'\n", dsc_, fmt_.c_str(), + ctx_->message("The %s %s failed with code '%s'\n", dsc(), fmt().c_str(), Message::errorToString(cr.error)); } else { - ctx_->message("The %s '%s' is complete...\n", dsc_, fmt_.c_str()); + ctx_->message("The %s '%s' is complete...\n", dsc(), fmt().c_str()); } } @@ -509,7 +508,7 @@ class TemplatedMessageHandler { if (rsp->status() == Message::FAILED) CHPLDEF_TODO(); std::string note; Result r; - auto error = M::unpack(rsp, r, ¬e); + auto error = M::unpack(rsp, r, note); if (error != Message::OK) { msg_->markFailed(error, std::move(note)); } else { @@ -524,22 +523,22 @@ class TemplatedMessageHandler { } }; -template +template void TemplatedMessage::handle(Server* ctx) { TemplatedMessageHandler> tmh(ctx, this); tmh.handle(); } -template +template void TemplatedMessage::handle(Server* ctx, Response* rsp) { TemplatedMessageHandler> tmh(ctx, this); tmh.handle(rsp); } -template +template void TemplatedMessage::handle(Server* ctx, Result r) { TemplatedMessageHandler> tmh(ctx, this); - tmh.handle(r); + tmh.handle(std::move(r)); } } // end namespace 'chpldef' diff --git a/tools/chpldef/Message.h b/tools/chpldef/Message.h index d39bf9db3c04..45a2a89323a9 100644 --- a/tools/chpldef/Message.h +++ b/tools/chpldef/Message.h @@ -35,9 +35,9 @@ class Response; class Server; class Message; -/** These tags are used to do dynamic casts to message types at runtime. */ -namespace MessageTag { -enum Kind { +/** These tags are used to do dynamic casts to message types at runtime, + and to parameterize `TemplatedMessage` instances at compile-time. */ +enum class MessageTag { UNSET = 0, INVALID = 1, RESPONSE = 2, @@ -47,15 +47,14 @@ enum Kind { #undef CHPLDEF_MESSAGE NUM_MESSAGES }; -} -template class TemplatedMessage; +template class TemplatedMessage; /** Attempts to model a LSP message. A message may be either incoming or outgoing (most are incoming). */ class Message { public: - using Tag = MessageTag::Kind; + using Tag = MessageTag; /** Error codes are listed in order according to the LSP spec. */ enum Error { @@ -258,7 +257,7 @@ class Message { Server* ctx() const; }; - /** Determine a message's behavior as a compile-time expression. */ + /** Determine a message's behavior. */ static inline constexpr Message::Behavior determineBehavior(bool outbound, bool notify) { return outbound @@ -283,6 +282,8 @@ class Message { virtual void handle(Server* ctx, Response* rsp) = 0; }; +namespace detail { + /** Stores computation results for a message. */ template struct ComputationOutput { @@ -357,8 +358,7 @@ struct Computation { using FunctionResult = ComputationOutput; }; -template -struct ComputationByTag {}; +template struct ComputationByTag {}; /** Specialize computation details for each tag. */ #define CHPLDEF_MESSAGE(name__, outbound__, notify__, x3__) \ @@ -370,13 +370,15 @@ struct ComputationByTag {}; #include "message-macro-list.h" #undef CHPLDEF_MESSAGE +} // end namespace 'detail' + template class TemplatedMessageHandler; -template +template class TemplatedMessage : public Message { public: - using Computation = typename ComputationByTag::type; + using Computation = typename detail::ComputationByTag::type; static constexpr auto BEHAVIOR = Computation::BEHAVIOR; using Params = typename Computation::Params; using Result = typename Computation::Result; @@ -395,9 +397,9 @@ class TemplatedMessage : public Message { : Message(tag, std::move(id), error, std::move(note)), p(std::move(p)) {} - static Message::Error unpack(JsonValue j, Params& p, std::string* note); - static Message::Error unpack(JsonValue j, Result& r, std::string* note); - static Message::Error unpack(Response* rsp, Result& r, std::string* note); + static Message::Error unpack(JsonValue j, Params& p, std::string& note); + static Message::Error unpack(JsonValue j, Result& r, std::string& note); + static Message::Error unpack(Response* rsp, Result& r, std::string& note); /** Use in message handlers to return failure. */ static ComputeResult fail(Error error=Message::ERR_REQUEST_FAILED, diff --git a/tools/chpldef/Transport.cpp b/tools/chpldef/Transport.cpp index e4a4be6e8043..cb5083504ce2 100644 --- a/tools/chpldef/Transport.cpp +++ b/tools/chpldef/Transport.cpp @@ -114,11 +114,11 @@ Transport::Status Transport::sendJson(Server* ctx, const JsonValue& j) { } Transport::Status -TransportStdio::read(int64_t size, std::string& str, int behavior) { +TransportStdio::read(int64_t size, std::string& str, Transport::Behavior b) { if (size < 0) return ERROR_INVALID_SIZE; bool readEntireContents = (size == 0); - bool readUntilNewline = (behavior & READ_UNTIL_NEWLINE); + bool readUntilNewline = (b & READ_UNTIL_NEWLINE); int64_t bytesToRead = size; Status ret = OK; @@ -138,7 +138,7 @@ TransportStdio::read(int64_t size, std::string& str, int behavior) { } Transport::Status -TransportStdio::send(const std::string& str, int behavior) { +TransportStdio::send(const std::string& str, Transport::Behavior b) { std::cout << str; bool err = std::cout.fail() || std::cout.bad(); return err ? ERROR : OK; diff --git a/tools/chpldef/Transport.h b/tools/chpldef/Transport.h index 0746cc8276de..95e82c4dfd88 100644 --- a/tools/chpldef/Transport.h +++ b/tools/chpldef/Transport.h @@ -42,18 +42,21 @@ class Transport { }; /** Provides a way to configure each read or write. */ - enum Behavior { + enum BehaviorTag { DEFAULT = 0, READ_UNTIL_NEWLINE = 1 }; + /** Behavior is a bitfield composed of 'BehaviorTag'. */ + using Behavior = int; + virtual ~Transport() = default; /** Read up to 'size' bytes into 'str' with the given behaviors 'b'. */ - virtual Status read(int64_t size, std::string& str, int behavior=0) = 0; + virtual Status read(int64_t size, std::string& str, Behavior b=0) = 0; /** Send the bytes of 'str' with the given behaviors 'b'. */ - virtual Status send(const std::string& str, int behavior=0) = 0; + virtual Status send(const std::string& str, Behavior b=0) = 0; /** Read JSON into the given JSON value 'j'. */ Status readJson(Server* ctx, JsonValue& j); @@ -68,9 +71,9 @@ class TransportStdio final : public Transport { TransportStdio() {} virtual ~TransportStdio() = default; virtual Status read(int64_t size, std::string& str, - int behavior=0) override; + Behavior b=0) override; virtual Status send(const std::string& str, - int behavior=0) override; + Behavior b=0) override; }; } // end namespace 'chpldef' diff --git a/tools/chpldef/compiler-gadgets.cpp b/tools/chpldef/compiler-gadgets.cpp index 45f201eb42eb..74ac22528506 100644 --- a/tools/chpldef/compiler-gadgets.cpp +++ b/tools/chpldef/compiler-gadgets.cpp @@ -50,11 +50,15 @@ static void doMapLinesInModule(chpl::Context* chapel, for (auto ast : node->children()) { if (ast->isComment()) continue; + // Push back the occupied lines. This is usually just one line but can + // be up to 3 in the case of a dot expression. if (isSymbolOfInterest(ast)) { auto loc = br.idToLocation(ast->id(), chpl::UniqueString()); CHPL_ASSERT(!loc.isEmpty()); - auto& v = m[loc.firstLine()]; - v.push_back(ast->id()); + for (int i = loc.firstLine(); i <= loc.lastLine(); i++) { + auto& v = m[i]; + v.push_back(ast->id()); + } } bool recurse = !ast->isModule() && !ast->isLeaf(); @@ -92,17 +96,17 @@ locationFromUriAndPosition(chpl::Context* chapel, const std::string& uri, return chpl::Location(ustr, l1, c1, l2, c2); } -bool isCalledExpression(chpl::Context* chapel, - const chpl::uast::AstNode* ast) { - if (!ast) return false; +const chpl::uast::FnCall* +parentCallIfBaseExpression(chpl::Context* chapel, + const chpl::uast::AstNode* ast) { + if (!ast) return nullptr; auto p = chpl::parsing::parentAst(chapel, ast); - if (!p) return false; + if (!p) return nullptr; - if (auto call = p->toCall()) + if (auto call = p->toFnCall()) if (auto ce = call->calledExpression()) - return ce == ast; - - return false; + if (ce == ast) return call; + return nullptr; } } // end namespace 'chpldef' diff --git a/tools/chpldef/compiler-gadgets.h b/tools/chpldef/compiler-gadgets.h index a41fad745199..77b68e972a79 100644 --- a/tools/chpldef/compiler-gadgets.h +++ b/tools/chpldef/compiler-gadgets.h @@ -59,9 +59,10 @@ chpl::Location locationFromUriAndPosition(chpl::Context* chapel, const std::string& uri, const Position& pos); -/** Determine if a given AST is the base expression of a call. */ -bool isCalledExpression(chpl::Context* chapel, - const chpl::uast::AstNode* ast); +/** If 'ast' is the base expression of a call, return the parent call. */ +const chpl::uast::FnCall* +parentCallIfBaseExpression(chpl::Context* chapel, + const chpl::uast::AstNode* ast); } // end namespace 'chpldef' diff --git a/tools/chpldef/compute-goto-declaration.cpp b/tools/chpldef/compute-goto-declaration.cpp index 3934e758d1e3..cd4fef1374cc 100644 --- a/tools/chpldef/compute-goto-declaration.cpp +++ b/tools/chpldef/compute-goto-declaration.cpp @@ -116,7 +116,7 @@ sourceAstToIds(Server* ctx, const chpl::uast::AstNode* ast) { auto& rr = resolveModule(ctx, mod->id()); auto& re = rr.byAst(ast); - bool isCallBaseExpression = ctx->withChapel(isCalledExpression, ast); + auto parentCall = ctx->withChapel(parentCallIfBaseExpression, ast); if (re.isBuiltin()) CHPLDEF_TODO(); @@ -128,10 +128,8 @@ sourceAstToIds(Server* ctx, const chpl::uast::AstNode* ast) { ret.push_back(idTarget); // It's a call base expression, so we need to pick apart the call. - } else if (isCallBaseExpression) { - auto p = ctx->withChapel(chpl::parsing::parentAst, ast); - auto call = p->toFnCall(); - auto& reCall = rr.byAst(call); + } else if (parentCall) { + auto& reCall = rr.byAst(parentCall); if (auto idTarget = re.toId()) { ret.push_back(idTarget); diff --git a/tools/chpldef/events.cpp b/tools/chpldef/events.cpp index b45506aea4ef..ca41e33688ae 100644 --- a/tools/chpldef/events.cpp +++ b/tools/chpldef/events.cpp @@ -70,7 +70,7 @@ void ReadMessage::run(Server* ctx, const Message* msg, When when) { ctx->enqueue(std::move(work)); } -static bool resolveModulesForMessageTag(MessageTag::Kind tag) { +static bool resolveModulesForMessageTag(MessageTag tag) { return tag == MessageTag::DidOpen; } diff --git a/tools/chpldef/test/TestClient.cpp b/tools/chpldef/test/TestClient.cpp index 81f142064789..93bd6aede197 100644 --- a/tools/chpldef/test/TestClient.cpp +++ b/tools/chpldef/test/TestClient.cpp @@ -113,7 +113,8 @@ doCollectMentions(chpl::Context* chapel, TestClient::Mention m; m.tag = ast->tag(); m.symbol = mentionSymbol(ast); - m.isCallBaseExpression = chpldef::isCalledExpression(chapel, ast); + bool isBaseExpr = parentCallIfBaseExpression(chapel, ast) != nullptr; + m.isCallBaseExpression = isBaseExpr; m.source = locationFromAst(chapel, ast); mentions.push_back(std::move(m)); } From 5a827705463d3e1e27bc8e5388e285c6406f3f6a Mon Sep 17 00:00:00 2001 From: David Longnecker Date: Thu, 14 Sep 2023 12:19:53 -0700 Subject: [PATCH 22/27] Remove assertion that masked buggy behavior Signed-off-by: David Longnecker --- tools/chpldef/Message.cpp | 6 +++--- tools/chpldef/Server.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/chpldef/Message.cpp b/tools/chpldef/Message.cpp index 54e9b45bda74..eefaf2076210 100644 --- a/tools/chpldef/Message.cpp +++ b/tools/chpldef/Message.cpp @@ -432,8 +432,8 @@ class TemplatedMessageHandler { using Result = typename M::Result; using ComputeResult = typename M::ComputeResult; private: - Server* ctx_; - M* msg_; + Server* ctx_ = nullptr; + M* msg_ = nullptr; public: TemplatedMessageHandler(Server* ctx, M* msg) : ctx_(ctx), msg_(msg) {} TemplatedMessageHandler() = default; @@ -504,7 +504,7 @@ class TemplatedMessageHandler { } void handle(Response* rsp) { - CHPL_ASSERT(rsp && rsp->id() == msg_->id()); + if (!rsp || rsp->id() != msg_->id()) return; if (rsp->status() == Message::FAILED) CHPLDEF_TODO(); std::string note; Result r; diff --git a/tools/chpldef/Server.cpp b/tools/chpldef/Server.cpp index 86250bf000f3..97ce580d5522 100644 --- a/tools/chpldef/Server.cpp +++ b/tools/chpldef/Server.cpp @@ -219,7 +219,7 @@ bool Server::handle(chpl::owned msg) { auto idStr = msg->idToString(); auto it = idToOutboundRequest_.find(idStr); if (it != idToOutboundRequest_.end()) CHPLDEF_TODO(); - auto p = std::make_pair(idStr, std::move(msg)); + auto p = std::make_pair(std::move(idStr), std::move(msg)); idToOutboundRequest_.emplace(std::move(p)); } } break; From 304452331d2652d46e77d8acf802e9a565ef3164 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Thu, 14 Sep 2023 19:40:26 -0700 Subject: [PATCH 23/27] Add a prediff to a test to avoid line numbers changing Signed-off-by: Danila Fedorin --- test/io/serializers/unstableWriting.good | 30 ++++++++++----------- test/io/serializers/unstableWriting.prediff | 16 +++++++++++ 2 files changed, 31 insertions(+), 15 deletions(-) create mode 100755 test/io/serializers/unstableWriting.prediff diff --git a/test/io/serializers/unstableWriting.good b/test/io/serializers/unstableWriting.good index dba51ec4b2f1..4969d2311b34 100644 --- a/test/io/serializers/unstableWriting.good +++ b/test/io/serializers/unstableWriting.good @@ -1,22 +1,22 @@ -$CHPL_HOME/modules/standard/JSON.chpl:66: In method 'serializeValue': -$CHPL_HOME/modules/standard/JSON.chpl:79: warning: Serialization of ranges with non-default Serializer is unstable, and may change in the future - $CHPL_HOME/modules/standard/IO.chpl:5921: called as jsonSerializer.serializeValue(writer: fileWriter(false,jsonSerializer), val: range(int(64),both,one)) from method '_serializeOne' - $CHPL_HOME/modules/standard/IO.chpl:9102: called as (fileWriter(true,jsonSerializer))._serializeOne(x: range(int(64),both,one), loc: locale) from method 'write' - $CHPL_HOME/modules/standard/IO.chpl:9164: called as (fileWriter(true,jsonSerializer)).write(args(0): range(int(64),both,one), args(1): chpl_ioNewline) from method 'writeln' +$CHPL_HOME/modules/standard/JSON.chpl:nnnn: In method 'serializeValue': +$CHPL_HOME/modules/standard/JSON.chpl:nnnn: warning: Serialization of ranges with non-default Serializer is unstable, and may change in the future + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as jsonSerializer.serializeValue(writer: fileWriter(false,jsonSerializer), val: range(int(64),both,one)) from method '_serializeOne' + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as (fileWriter(true,jsonSerializer))._serializeOne(x: range(int(64),both,one), loc: locale) from method 'write' + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as (fileWriter(true,jsonSerializer)).write(args(0): range(int(64),both,one), args(1): chpl_ioNewline) from method 'writeln' unstableWriting.chpl:7: called as (fileWriter(true,jsonSerializer)).writeln(args(0): range(int(64),both,one)) from function 'helper' unstableWriting.chpl:11: called as helper(thing: range(int(64),both,one)) -$CHPL_HOME/modules/standard/JSON.chpl:66: In method 'serializeValue': -$CHPL_HOME/modules/standard/JSON.chpl:79: warning: Serialization of rectangular domains with non-default Serializer is unstable, and may change in the future - $CHPL_HOME/modules/standard/IO.chpl:5921: called as jsonSerializer.serializeValue(writer: fileWriter(false,jsonSerializer), val: domain(1,int(64),one)) from method '_serializeOne' - $CHPL_HOME/modules/standard/IO.chpl:9102: called as (fileWriter(true,jsonSerializer))._serializeOne(x: domain(1,int(64),one), loc: locale) from method 'write' - $CHPL_HOME/modules/standard/IO.chpl:9164: called as (fileWriter(true,jsonSerializer)).write(args(0): domain(1,int(64),one), args(1): chpl_ioNewline) from method 'writeln' +$CHPL_HOME/modules/standard/JSON.chpl:nnnn: In method 'serializeValue': +$CHPL_HOME/modules/standard/JSON.chpl:nnnn: warning: Serialization of rectangular domains with non-default Serializer is unstable, and may change in the future + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as jsonSerializer.serializeValue(writer: fileWriter(false,jsonSerializer), val: domain(1,int(64),one)) from method '_serializeOne' + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as (fileWriter(true,jsonSerializer))._serializeOne(x: domain(1,int(64),one), loc: locale) from method 'write' + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as (fileWriter(true,jsonSerializer)).write(args(0): domain(1,int(64),one), args(1): chpl_ioNewline) from method 'writeln' unstableWriting.chpl:7: called as (fileWriter(true,jsonSerializer)).writeln(args(0): domain(1,int(64),one)) from function 'helper' unstableWriting.chpl:12: called as helper(thing: domain(1,int(64),one)) -$CHPL_HOME/modules/standard/JSON.chpl:66: In method 'serializeValue': -$CHPL_HOME/modules/standard/JSON.chpl:79: warning: Serialization of iterators with non-default Serializer is unstable, and may change in the future - $CHPL_HOME/modules/standard/IO.chpl:5921: called as jsonSerializer.serializeValue(writer: fileWriter(false,jsonSerializer), val: iterator) from method '_serializeOne' - $CHPL_HOME/modules/standard/IO.chpl:9102: called as (fileWriter(true,jsonSerializer))._serializeOne(x: iterator, loc: locale) from method 'write' - $CHPL_HOME/modules/standard/IO.chpl:9164: called as (fileWriter(true,jsonSerializer)).write(args(0): iterator, args(1): chpl_ioNewline) from method 'writeln' +$CHPL_HOME/modules/standard/JSON.chpl:nnnn: In method 'serializeValue': +$CHPL_HOME/modules/standard/JSON.chpl:nnnn: warning: Serialization of iterators with non-default Serializer is unstable, and may change in the future + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as jsonSerializer.serializeValue(writer: fileWriter(false,jsonSerializer), val: iterator) from method '_serializeOne' + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as (fileWriter(true,jsonSerializer))._serializeOne(x: iterator, loc: locale) from method 'write' + $CHPL_HOME/modules/standard/IO.chpl:nnnn: called as (fileWriter(true,jsonSerializer)).write(args(0): iterator, args(1): chpl_ioNewline) from method 'writeln' unstableWriting.chpl:7: called as (fileWriter(true,jsonSerializer)).writeln(args(0): iterator) from function 'helper' unstableWriting.chpl:13: called as helper(thing: iterator) "1..10" diff --git a/test/io/serializers/unstableWriting.prediff b/test/io/serializers/unstableWriting.prediff new file mode 100755 index 000000000000..7f8b7caeb136 --- /dev/null +++ b/test/io/serializers/unstableWriting.prediff @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# +# Designed to squash out line numbers from modules files to prevent +# sensitivities to code locations. Borrowed from +# `test/functions/operatorOverloads/operatorMethods/nestedMethodBad.prediff` +# + +tmpfile=$2 + +tmptmp=`mktemp "tmp.XXXXXX"` + +regex='\|CHPL_HOME/modules|s/:[0-9:]*:/:nnnn:/' + +sed -e "$regex" $tmpfile > $tmptmp + +mv $tmptmp $tmpfile From b755f25d4fc0ae1c5a865bd22ae5733795cefc65 Mon Sep 17 00:00:00 2001 From: David Longnecker Date: Thu, 14 Sep 2023 20:01:44 -0700 Subject: [PATCH 24/27] Remove accidentally committed swap file Signed-off-by: David Longnecker --- tools/chpldef/test/.utility.cpp.swp | Bin 24576 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tools/chpldef/test/.utility.cpp.swp diff --git a/tools/chpldef/test/.utility.cpp.swp b/tools/chpldef/test/.utility.cpp.swp deleted file mode 100644 index 9850c319a2fcb89e8337e9b85984c330b3233055..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 zcmeI3dyr&RdBCp-%X1gOidyS~vx5#ZyE8q*f&_Zm(P19!;5;_-U|EnF@AN%0bEW&< z_TJk&J1(0TurL+#hbfD!XpvwUlRsh-LY{vZC0l4&B~pnLLM%x_5=@m*6GAK!t&se_ zbMEcFJ>4_AD@ID_E`Id(z32Iz@0{~}-#N21c=N=Rx~?$j@Oho%JQqIbedwXf&f8OR z9IqS(D}gTEq2=Y~pOL}6x7@gQaO$QT54>~pRF3nDjRFTo!&0rHgCrjC^pSx`SHpOq zvff`>b*sAEU!rCm57=Mt97w{jEWfH{PcIK7I!*>^iC^}U^+Ksy-KxsYyN(1p5)cX0 zlI8wuw>wwuz0yqe%e!`|*I&BOkxfSe9SL+K(2+n#0v!o-B+!vSM*(L&`TzfOvEzImJ_YZE z!|(=pVu#~A1b+x0hF^vmn1plS;q8u-KtEgv=fXDlw~HL-`*0F|8}5M*!`*NIE{AjA zwQv@kypT4*3QWN;ybFe42mJd5ln38~zkmne78rq@upKUh3*g!F9p_QF8v-c99WVv^ z;5xV#-T-IAS@0To@;t}+UAP-&VH&339k2tQKG$*n7M_G}z^B27QMefX1(`ksBHOPc z7*6~>e%sp{E!FP{~O=+L~az_Hh8-v zw4ad!2PYSY=jO&{=X-cpi2V=ho?f+ozq+!wk;<(lVYlMF+OKw3v*-S@>XA39%Hv4K zg%zDF`pef(h9x)g!(cdy-1VWJZeI2Fs$-_kEKj#&kZk=!{G(P*Y=Z2ylA0>tH!@9`HFaE3aA<@n zcGV5MvhFD*M;A>$O+R=;wYhA?Vky#YLOS!c5cnUc=_wt@?uwr9dV2d*oOs1zB|M^g zs=cP5T<(KZUWrw;r;#Y}{N_wy?2D*u-g$jA$?n}ucwD~mmwg>6GWFCVpW^4aSu;}h zc~dOLI`DFp^_0ROPHcPPB=UomZbha(bug_$m1qIYGh6U$+g>RCw@Rf3G{^O_$d*Id1rwANLBW15vVz?6DKmy$5LK1(np{caX( zDJ+*sG)adhb+D4G#uLF@Sc^(1L92wb*0e3WRkAIjZI(zwPvquRT`d%!N+EG0=2tei zWO~>%^G(mkqp&g@Cn8+#Y4s^LPEzqo8L{a|YZOlLZg$DUQD%W_nk0#?$n+qo(e*Cd?XEs`L{o>1j?HAIH_vGH zQmsSN_f*Tow4G_EGSX118FrTCS72F=`iJ5~mK?JP z&*_L(7mKrF!=s1Ofs~0q5wL8yWfp5zA(>ug@@$rSZft(xpiCMPoL-wU&6{T5yKh#*X?)Bg?6Fhn(c=N`3mfLsRZd(KqQ%E^VWEEEtWweyiOw zO}IOf@jLxay`(-Bi;XVH9@FT7-G7H#=1C((OBXBIHaim50ydw;J$v>!H^pIavsA!dzzivmJ%^MM>pQv5?WhrT-DWF(leWZo>3G-949C*?k zrOd)YwvD-#eX>yyx|(fM`8A?-5o)u-NcIhV$S;ZHSWeeOxp`YGj)chRD5e4bSoT+H z*7Q-5ZlBYXq>c1O%!V-%R;qqkM-$i*G96n+Ws?SE>r}g%?YlNr+851LUG4{^a?R7K zt5Cl}yxPUWwR-@tGb%o-BWri8YJn%l(W+LHen|(hE=Y6SPzmWuIduzR7sWH zV5NrrrdF`pq5wllRdrPHW7!cfC<9N5IW#eUU}j-n9U7jU9iE<_7@Jcwvub2!dX!~s zW}08)YIypvde6l4XrIzPRYXc3tzyi^iYom|6+6u{P4av($d+me_GwF_#fZ%(2m_j+ zx`yY}#9WuUVR&w0u1^}Y5R|3pNPnQ_M-;TQuG}hhl-#AVrc-NDtGg1VMz9~KwTL|; zSYfLTmyOkUwY9Z^TXjpTx)4Sy1C%Cmj0YwsM#iS+#`^bi?c8QM z9M)9DU02~!;!?JYR#o}6+$3u^NmG-egeH}J(IK^lbn@wpP|`W5s6zF~0ClCK*wwpF zt*5mGVWMi7nTc5Y%P1xyF6pXNg9pGbtRyC8+7QyLh0z@%(X=Odc@LX@X1XFWl~QKR zz`34h%y74C`W5*rtBG0-xy>HMrrS$eF`*H*jG^?$4@@sckb+IUcX06P{=I`&UZ+Oz zdelTvYDutnkOX_LQU~-}Stp4)h!yNco*I+ots41sbW|VF<*+KIotix84D5Eq|Gx`= zdj~$c`2X4Y{)_nf5(791zX}@m!`omdTma|6x$p>n|2;4beefg7dI6q=r{Ej#1bhkZ zgjKj2E`f_d>i!{Z_){2%x5N4H9De>|@EQ0RydRc9;s7t=*Z&ay3toVKg)hJnxEh|v ze?JNL!(H%xD8iM{1<&KZ|1?gH`ix54B1@xKQN%!7jO zyIK2sQ$O7rq_R0~;^-^&l3n>m9qpq92<=pMPtJe-F$^DiP>sS8%d%`DEXcBq#l~W1 z(O?ZMhhddfK)O0&0tm9ZC>we(iBlVg4IskjEUzoJY}rT>(y+J@Y{1gNx?mF%+r3)g zuW-ylB{q0H@{l_AVvbY`VgY0^gr-#DK}nLPJ~!+&#RlET`Lh*$s7DeqTTby|!kkX9 z;fnVLSBRCRjHO`XS*IIkFhlV2hZMYU+&@~YPUYhPaXkWn9Qu?;C@dDI8wJxV>nH*p zwyvNTj@Xb#n!k~g*g-v~DJ7RZFD}v%m|2PnNm!kf4Q$NXL1ItYz#Wy@wO~m+@Twcd zT7nDI!rICbI1*D?~86WlF|{F zO_>+40Od3d;Ik6nt|jZ%{+Y$MKWm`^n(-Sq}x9Jl@C zOW0CvyVC{7*=%XbxvRU;eG{uhY4GeyN@6>C{n&hgDy{luV*lylmW@o)GM8VcOmbE# zCRzWG`4MS?MONfi3$rqEJBR7CiSSo!lwJa&IR zUp9sRc`kzcQR5AOIEyQIp>#Zm!aLWEepxEg!+a; z$*hZrsJ@o5Ca+v%-L^$jaH#ZQNx-fhC~EFr08Po@WBZ7lkY0nTi9 zPR;&QFH=Q1iu$@7Q+SPymN61Ns1N1Q#yE}#BP7lSv0aMMkN{{s#6{^GJC%ug6-tXN zgpKg78Jb+l4J)xZd%y;k&z*MP%p|+FWJ^ujp;JpRKk&;gM^8rU>2cjNBGAy?t#*}k z|BxR~>$Pmom`3vwO+XCoB-bQ3NwlTxWbyFheAm^TciNfi$IX&VB3b{_-Y@Yt@`NT( zjcjtBRFXMz2^0=@tGBJ?wR z%-`|->vt zXbusywXjuKOC;hSMuf!+ukru_^M6h!Ms(&}Lc(q)x@UqL_2))RVo1-2qZrqi$5xhN za^$@!Yf6hXBcHq_Dz=o(Trg*f^1Ih2+n6@@^mNMLq?5vatV~&p?AfSv#(p+wxV==C z`Zki%y3^7XOs&ix%8J?AV{dW%83{QGGKHbA#)qjO=Xf=&l_lDGM5`sueXymPUuKM! zIQA>31%eBHVunW=)Dk_&xp=K!fW2BHHAxtt-mk42zA@BbCgDxQt%wy@C3?&OlRjEd znO`?8&&?c6oz3aAwe4aQj&#CG^`{i1phoL5XW<(e0IR6C$v@>>J@B$I%`1kSmK)V; zuSLdQHz(?i5_77bm1vTgL6>^^hvc7mpF;#<;x4JKc{u)N#H>3k7DyK4k>)h{vTXuQ zl8Y|eV1Qe_KIhByZQ%e88NIY($yzQg%YT}xel-PlGR=930iN{olN-!xISFBPJm!de zjl)%{KSm#SnV^rjZ{xL~B$1cQNn{Zj4V4@>dphP2++#M%RnjyWWqCgLRqbU0&X?W5 zEw9IZY^G#g&4^?Xkow6)nCjuA&6b}mY++(guk?VD99l5>nuD_WNz`sTwfAJx^>k?K z@MeB9ZuQHjSzOYHd_A<;T)X^g&6ke@^$`qhJ!xqdP|6|(CJ-wA|4#f_x!W%O|9I*Z zehYv9b8tVLfRDqk!^a?meXtF_xWjRN1y`{6p+1HI4#JK=F+01v_mNMIIz0pxE1 zo*@qK36Q@9kb42|g3I7V`u_-g4(@>vR=|bZ;UE|prSj=~btKS{Kt}={33MdTk${!J z@%$;uE9E9xCzh-^O*4Oz*11P!|2l$0Q*pMN;}eZQ@uq*p)=1KcB?tEJ${M%9Oz@;n zuGMY0V1e)4i6s;MHE}=_mO8`BXlD{jK5ajBt2lGBV=O}|)5SF1-o+tS@)NrgC!TTp q&r1$`* Date: Thu, 14 Sep 2023 20:35:36 -0700 Subject: [PATCH 25/27] Get rid of the two-part initializers I accidentally inherited from Block (not needed here... I'd forgotten they'd been done there, copy-pasted, and when things worked, didn't look closer) --- Signed-off-by: Brad Chamberlain --- modules/dists/BlockCycDist.chpl | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/modules/dists/BlockCycDist.chpl b/modules/dists/BlockCycDist.chpl index 047ca98c3d47..e4dd38461de6 100644 --- a/modules/dists/BlockCycDist.chpl +++ b/modules/dists/BlockCycDist.chpl @@ -190,8 +190,6 @@ record blockCycDist: writeSerializable { forwarding const chpl_distHelp: chpl_PrivatizedDistHelper(unmanaged BlockCyclicImpl(rank, idxType)); - pragma "last resort" - @unstable("passing arguments other than 'boundingBox' and 'targetLocales' to 'blockCycDist' is currently unstable") proc init(startIdx, blocksize, targetLocales: [] locale = Locales, @@ -211,16 +209,6 @@ record blockCycDist: writeSerializable { value); } - proc init(boundingBox: domain, - targetLocales: [] locale = Locales) - { - this.init(boundingBox, targetLocales, - /* by specifying even one unstable argument, this should select - the whole unstable constructor, which has defaults for everything - else. */ - dataParTasksPerLocale=getDataParTasksPerLocale()); - } - proc init(_pid : int, _instance, _unowned : bool) { this.rank = _instance.rank; this.idxType = _instance.idxType; From 6a7274aa3b4f3f7ea445c1e55d1c7994654ba92b Mon Sep 17 00:00:00 2001 From: Brad Chamberlain Date: Thu, 14 Sep 2023 20:37:16 -0700 Subject: [PATCH 26/27] Here's a test that escaped me... --- Signed-off-by: Brad Chamberlain --- .../robust/arithmetic/resizing/resizeFromOtherLocale.chpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/distributions/robust/arithmetic/resizing/resizeFromOtherLocale.chpl b/test/distributions/robust/arithmetic/resizing/resizeFromOtherLocale.chpl index cb4e1c0d091a..032812f9fbfc 100644 --- a/test/distributions/robust/arithmetic/resizing/resizeFromOtherLocale.chpl +++ b/test/distributions/robust/arithmetic/resizing/resizeFromOtherLocale.chpl @@ -38,7 +38,7 @@ proc buildSpace(Dom) { return Dom dmapped stencilDist(boundingBox={1..3}); } else if distType == DistType.blockcyclic { - return Dom dmapped BlockCyclic(startIdx=1, blocksize=2); + return Dom dmapped blockCycDist(startIdx=1, blocksize=2); } else { compilerError("Compiling with unknown DistType."); From 90340493edfb19bc168f4c750af205b367f34b11 Mon Sep 17 00:00:00 2001 From: Engin Kayraklioglu Date: Thu, 14 Sep 2023 21:02:49 -0700 Subject: [PATCH 27/27] Skip dependency checking in Arkouda tests Signed-off-by: Engin Kayraklioglu --- util/cron/common-arkouda.bash | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/util/cron/common-arkouda.bash b/util/cron/common-arkouda.bash index 68c04e5ad903..b4f9622cee60 100644 --- a/util/cron/common-arkouda.bash +++ b/util/cron/common-arkouda.bash @@ -25,6 +25,10 @@ fi export CHPL_NIGHTLY_TEST_DIRS=studies/arkouda/ export CHPL_TEST_ARKOUDA=true +# Removing regex.compile caused smoke test failures. As a stopgap measure we are +# skipping dependency check. +export ARKOUDA_SKIP_CHECK_DEPS=1 + ARKOUDA_DEP_DIR=$COMMON_DIR/arkouda-deps if [ -d "$ARKOUDA_DEP_DIR" ]; then export ARKOUDA_ARROW_PATH=${ARKOUDA_ARROW_PATH:-$ARKOUDA_DEP_DIR/arrow-install}