From 7d15e966e722b93c0d3ec4168f09748881165e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 23 Dec 2024 12:20:25 +0100 Subject: [PATCH 1/9] Add `Process::Status#system_exit_status` (#15296) Provides a consistent, portable API to retrieve the platform-specific exit status --- spec/std/process/status_spec.cr | 10 ++++++++++ src/process/status.cr | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index b56f261c4b5c..6cbabbea5d73 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -42,6 +42,16 @@ describe Process::Status do status_for(:interrupted).exit_code?.should be_nil end + it "#system_exit_status" do + Process::Status.new(exit_status(0)).system_exit_status.should eq 0_u32 + Process::Status.new(exit_status(1)).system_exit_status.should eq({{ flag?(:unix) ? 0x0100_u32 : 1_u32 }}) + Process::Status.new(exit_status(127)).system_exit_status.should eq({{ flag?(:unix) ? 0x7f00_u32 : 127_u32 }}) + Process::Status.new(exit_status(128)).system_exit_status.should eq({{ flag?(:unix) ? 0x8000_u32 : 128_u32 }}) + Process::Status.new(exit_status(255)).system_exit_status.should eq({{ flag?(:unix) ? 0xFF00_u32 : 255_u32 }}) + + status_for(:interrupted).system_exit_status.should eq({% if flag?(:unix) %}Signal::INT.value{% else %}LibC::STATUS_CONTROL_C_EXIT{% end %}) + end + it "#success?" do Process::Status.new(exit_status(0)).success?.should be_true Process::Status.new(exit_status(1)).success?.should be_false diff --git a/src/process/status.cr b/src/process/status.cr index db759bd1c178..933e81d5ad84 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -108,6 +108,13 @@ class Process::Status @exit_status.to_i32! end + # Returns the exit status as indicated by the operating system. + # + # It can encode exit codes and termination signals and is platform-specific. + def system_exit_status : UInt32 + @exit_status.to_u32! + end + {% if flag?(:win32) %} # :nodoc: def initialize(@exit_status : UInt32) From 47b948f5152a216e9dafe67b6a0623a911f152f7 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 23 Dec 2024 12:24:15 +0100 Subject: [PATCH 2/9] Fix: Cleanup nodes in `Thread::LinkedList(T)#delete` (#15295) --- src/crystal/system/thread_linked_list.cr | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/crystal/system/thread_linked_list.cr b/src/crystal/system/thread_linked_list.cr index b6f3ccf65d4e..c18c62afbde8 100644 --- a/src/crystal/system/thread_linked_list.cr +++ b/src/crystal/system/thread_linked_list.cr @@ -47,16 +47,21 @@ class Thread # `#unsafe_each` until the method has returned. def delete(node : T) : Nil @mutex.synchronize do - if previous = node.previous - previous.next = node.next + previous = node.previous + _next = node.next + + if previous + node.previous = nil + previous.next = _next else - @head = node.next + @head = _next end - if _next = node.next - _next.previous = node.previous + if _next + node.next = nil + _next.previous = previous else - @tail = node.previous + @tail = previous end end end From bd4e0d4a399385a2739626e8e60a9fa306567dd5 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 23 Dec 2024 12:24:52 +0100 Subject: [PATCH 3/9] Improve Crystal::Tracing (#15297) - Avoid spaces in fiber names (easier to columnize) - Support more types (Bool, Char, Time::Span) --- src/crystal/event_loop/epoll.cr | 2 +- src/crystal/event_loop/kqueue.cr | 2 +- src/crystal/scheduler.cr | 6 ++-- src/crystal/system/unix/signal.cr | 2 +- src/crystal/system/win32/process.cr | 2 +- src/crystal/tracing.cr | 51 ++++++++++++++++++++--------- 6 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/crystal/event_loop/epoll.cr b/src/crystal/event_loop/epoll.cr index 2d7d08ce7c94..371b9039b6b5 100644 --- a/src/crystal/event_loop/epoll.cr +++ b/src/crystal/event_loop/epoll.cr @@ -55,7 +55,7 @@ class Crystal::EventLoop::Epoll < Crystal::EventLoop::Polling {% end %} private def system_run(blocking : Bool, & : Fiber ->) : Nil - Crystal.trace :evloop, "run", blocking: blocking ? 1 : 0 + Crystal.trace :evloop, "run", blocking: blocking # wait for events (indefinitely when blocking) buffer = uninitialized LibC::EpollEvent[128] diff --git a/src/crystal/event_loop/kqueue.cr b/src/crystal/event_loop/kqueue.cr index 52a7701ef2b1..47f00ddc9e89 100644 --- a/src/crystal/event_loop/kqueue.cr +++ b/src/crystal/event_loop/kqueue.cr @@ -73,7 +73,7 @@ class Crystal::EventLoop::Kqueue < Crystal::EventLoop::Polling private def system_run(blocking : Bool, & : Fiber ->) : Nil buffer = uninitialized LibC::Kevent[128] - Crystal.trace :evloop, "run", blocking: blocking ? 1 : 0 + Crystal.trace :evloop, "run", blocking: blocking timeout = blocking ? nil : Time::Span.zero kevents = @kqueue.wait(buffer.to_slice, timeout) diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr index ad0f2a55672e..efee6b3c06f1 100644 --- a/src/crystal/scheduler.cr +++ b/src/crystal/scheduler.cr @@ -66,7 +66,7 @@ class Crystal::Scheduler end def self.sleep(time : Time::Span) : Nil - Crystal.trace :sched, "sleep", for: time.total_nanoseconds.to_i64! + Crystal.trace :sched, "sleep", for: time Thread.current.scheduler.sleep(time) end @@ -225,7 +225,7 @@ class Crystal::Scheduler pending = Atomic(Int32).new(count - 1) @@workers = Array(Thread).new(count) do |i| if i == 0 - worker_loop = Fiber.new(name: "Worker Loop") { Thread.current.scheduler.run_loop } + worker_loop = Fiber.new(name: "worker-loop") { Thread.current.scheduler.run_loop } worker_loop.set_current_thread Thread.current.scheduler.enqueue worker_loop Thread.current @@ -272,7 +272,7 @@ class Crystal::Scheduler # Background loop to cleanup unused fiber stacks. def spawn_stack_pool_collector - fiber = Fiber.new(name: "Stack pool collector", &->@stack_pool.collect_loop) + fiber = Fiber.new(name: "stack-pool-collector", &->@stack_pool.collect_loop) {% if flag?(:preview_mt) %} fiber.set_current_thread {% end %} enqueue(fiber) end diff --git a/src/crystal/system/unix/signal.cr b/src/crystal/system/unix/signal.cr index 802cb418db15..a68108ad327a 100644 --- a/src/crystal/system/unix/signal.cr +++ b/src/crystal/system/unix/signal.cr @@ -82,7 +82,7 @@ module Crystal::System::Signal end private def self.start_loop - spawn(name: "Signal Loop") do + spawn(name: "signal-loop") do loop do value = reader.read_bytes(Int32) rescue IO::Error diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 5eb02d826c3b..5249491bbd3f 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -203,7 +203,7 @@ struct Crystal::System::Process def self.start_interrupt_loop : Nil return unless @@setup_interrupt_handler.test_and_set - spawn(name: "Interrupt signal loop") do + spawn(name: "interrupt-signal-loop") do while true @@interrupt_count.wait { sleep 50.milliseconds } diff --git a/src/crystal/tracing.cr b/src/crystal/tracing.cr index 684680b10b28..d9508eda85a8 100644 --- a/src/crystal/tracing.cr +++ b/src/crystal/tracing.cr @@ -51,37 +51,58 @@ module Crystal @size = 0 end - def write(bytes : Bytes) : Nil + def write(value : Bytes) : Nil pos = @size remaining = N - pos return if remaining == 0 - n = bytes.size.clamp(..remaining) - bytes.to_unsafe.copy_to(@buf.to_unsafe + pos, n) + n = value.size.clamp(..remaining) + value.to_unsafe.copy_to(@buf.to_unsafe + pos, n) @size = pos + n end - def write(string : String) : Nil - write string.to_slice + def write(value : String) : Nil + write value.to_slice end - def write(fiber : Fiber) : Nil - write fiber.as(Void*) - write ":" - write fiber.name || "?" + def write(value : Char) : Nil + chars = uninitialized UInt8[4] + i = 0 + value.each_byte do |byte| + chars[i] = byte + i += 1 + end + write chars.to_slice[0, i] + end + + def write(value : Fiber) : Nil + write value.as(Void*) + write ':' + write value.name || '?' end - def write(ptr : Pointer) : Nil + def write(value : Pointer) : Nil write "0x" - System.to_int_slice(ptr.address, 16, true, 2) { |bytes| write(bytes) } + System.to_int_slice(value.address, 16, true, 2) { |bytes| write(bytes) } + end + + def write(value : Int::Signed) : Nil + System.to_int_slice(value, 10, true, 2) { |bytes| write(bytes) } + end + + def write(value : Int::Unsigned) : Nil + System.to_int_slice(value, 10, false, 2) { |bytes| write(bytes) } + end + + def write(value : Time::Span) : Nil + write(value.seconds * Time::NANOSECONDS_PER_SECOND + value.nanoseconds) end - def write(int : Int::Signed) : Nil - System.to_int_slice(int, 10, true, 2) { |bytes| write(bytes) } + def write(value : Bool) : Nil + write value ? '1' : '0' end - def write(uint : Int::Unsigned) : Nil - System.to_int_slice(uint, 10, false, 2) { |bytes| write(bytes) } + def write(value : Nil) : Nil end def to_slice : Bytes From 7954027587f3b47dbb1aae64417aa9746a6e0ca7 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Mon, 23 Dec 2024 12:25:02 +0100 Subject: [PATCH 4/9] Add Thread#internal_name= (#15298) Allows to change the Thread#name property without affecting the system name used by the OS, which affect ps, top, gdb, ... We usually want to change the system name, except for the main thread. If we name the thread "DEFAULT" then ps will report our process as "DEFAULT" instead of "myapp" (oops). --- src/crystal/system/thread.cr | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index 0d6f5077633a..fec4a989f10a 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -182,6 +182,11 @@ class Thread self.system_name = name end + # Changes the Thread#name property but doesn't update the system name. Useful + # on the main thread where we'd change the process name (e.g. top, ps, ...). + def internal_name=(@name : String) + end + # Holds the GC thread handler property gc_thread_handler : Void* = Pointer(Void).null From d21008edfa8016597c884bb00fe3254e636f17d2 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 25 Dec 2024 23:19:02 +0100 Subject: [PATCH 5/9] Add `Thread::LinkedList#each` to safely iterate lists (#15300) Sometimes we'd like to be able to safely iterate the lists. Also adds `Thread.each(&)` and `Fiber.each(&)` as convenience accessors. This will be used in RFC 2 to safely iterate execution contexts for example. Also adds `Thread.each(&)` and `Fiber.each(&)`. --- src/crystal/system/thread.cr | 4 ++++ src/crystal/system/thread_linked_list.cr | 7 +++++++ src/fiber.cr | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index fec4a989f10a..92136d1f3989 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -74,6 +74,10 @@ class Thread @@threads.try(&.unsafe_each { |thread| yield thread }) end + def self.each(&) + threads.each { |thread| yield thread } + end + def self.lock : Nil threads.@mutex.lock end diff --git a/src/crystal/system/thread_linked_list.cr b/src/crystal/system/thread_linked_list.cr index c18c62afbde8..f43dd04fedc2 100644 --- a/src/crystal/system/thread_linked_list.cr +++ b/src/crystal/system/thread_linked_list.cr @@ -24,6 +24,13 @@ class Thread end end + # Safely iterates the list. + def each(&) : Nil + @mutex.synchronize do + unsafe_each { |node| yield node } + end + end + # Appends a node to the tail of the list. The operation is thread-safe. # # There are no guarantees that a node being pushed will be iterated by diff --git a/src/fiber.cr b/src/fiber.cr index 2b596a16017c..55745666c66d 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -78,6 +78,11 @@ class Fiber @@fibers.try(&.unsafe_each { |fiber| yield fiber }) end + # :nodoc: + def self.each(&) + fibers.each { |fiber| yield fiber } + end + # Creates a new `Fiber` instance. # # When the fiber is executed, it runs *proc* in its context. From c38f4df4c4206e5fa3f18f1ea4877aa42701050b Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 25 Dec 2024 23:19:26 +0100 Subject: [PATCH 6/9] Explicit exit from main (#15299) In a MT environment such as proposed in https://github.com/crystal-lang/rfcs/pull/2, the main thread's fiber may be resumed by any thread, and it may return which would terminate the program... but it might return from _another thread_ that the process' main thread, which may be unexpected by the OS. This patch instead explicitly exits from `main` and `wmain`. For backward compatibility reasons (win32 `wmain` and wasi `__main_argc_argv` both call `main` andand are documented to do so), the default `main` still returns, but is being replaced for UNIX targets by one that exits. Maybe the OS actual entrypoint could merely call `Crystal.main` instead of `main` and explicitely exit (there wouldn't be a global `main` except for `UNIX`), but this is out of scope for this PR. --- src/crystal/main.cr | 6 +++--- src/crystal/system/unix/main.cr | 11 +++++++++++ src/crystal/system/wasi/main.cr | 3 ++- src/crystal/system/win32/wmain.cr | 22 +++++++++++----------- 4 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 src/crystal/system/unix/main.cr diff --git a/src/crystal/main.cr b/src/crystal/main.cr index 625238229c58..9b4384f16a8c 100644 --- a/src/crystal/main.cr +++ b/src/crystal/main.cr @@ -132,8 +132,8 @@ end {% if flag?(:win32) %} require "./system/win32/wmain" -{% end %} - -{% if flag?(:wasi) %} +{% elsif flag?(:wasi) %} require "./system/wasi/main" +{% else %} + require "./system/unix/main" {% end %} diff --git a/src/crystal/system/unix/main.cr b/src/crystal/system/unix/main.cr new file mode 100644 index 000000000000..1592a6342002 --- /dev/null +++ b/src/crystal/system/unix/main.cr @@ -0,0 +1,11 @@ +require "c/stdlib" + +# Prefer explicit exit over returning the status, so we are free to resume the +# main thread's fiber on any thread, without occuring a weird behavior where +# another thread returns from main when the caller might expect the main thread +# to be the one returning. + +fun main(argc : Int32, argv : UInt8**) : Int32 + status = Crystal.main(argc, argv) + LibC.exit(status) +end diff --git a/src/crystal/system/wasi/main.cr b/src/crystal/system/wasi/main.cr index 57ffd5f3f43c..9a3394809271 100644 --- a/src/crystal/system/wasi/main.cr +++ b/src/crystal/system/wasi/main.cr @@ -27,7 +27,8 @@ fun _start LibWasi.proc_exit(status) if status != 0 end -# `__main_argc_argv` is called by wasi-libc's `__main_void` with the program arguments. +# `__main_argc_argv` is called by wasi-libc's `__main_void` with the program +# arguments. fun __main_argc_argv(argc : Int32, argv : UInt8**) : Int32 main(argc, argv) end diff --git a/src/crystal/system/win32/wmain.cr b/src/crystal/system/win32/wmain.cr index 2120bfc06bfc..b1726f90329b 100644 --- a/src/crystal/system/win32/wmain.cr +++ b/src/crystal/system/win32/wmain.cr @@ -12,17 +12,13 @@ require "c/stdlib" lib LibCrystalMain end -# The actual entry point for Windows executables. This is necessary because -# *argv* (and Win32's `GetCommandLineA`) mistranslate non-ASCII characters to -# Windows-1252, so `PROGRAM_NAME` and `ARGV` would be garbled; to avoid that, we -# use this Windows-exclusive entry point which contains the correctly encoded -# UTF-16 *argv*, convert it to UTF-8, and then forward it to the original -# `main`. +# The actual entry point for Windows executables. # -# The different main functions in `src/crystal/main.cr` need not be aware that -# such an alternate entry point exists, nor that the original command line was -# not UTF-8. Thus all other aspects of program initialization still occur there, -# and uses of those main functions continue to work across platforms. +# This is necessary because *argv* (and Win32's `GetCommandLineA`) mistranslate +# non-ASCII characters to Windows-1252, so `PROGRAM_NAME` and `ARGV` would be +# garbled; to avoid that, we use this Windows-exclusive entry point which +# contains the correctly encoded UTF-16 *argv*, convert it to UTF-8, and then +# forward it to the original `main`. # # NOTE: we cannot use anything from the standard library here, including the GC. fun wmain(argc : Int32, argv : UInt16**) : Int32 @@ -46,5 +42,9 @@ fun wmain(argc : Int32, argv : UInt16**) : Int32 end LibC.free(utf8_argv) - status + # prefer explicit exit over returning the status, so we are free to resume the + # main thread's fiber on any thread, without occuring a weird behavior where + # another thread returns from main when the caller might expect the main + # thread to be the one returning. + LibC.exit(status) end From 4772066c996b78e1c1c8694813adadfeed5b21fc Mon Sep 17 00:00:00 2001 From: Jeremy Woertink Date: Wed, 25 Dec 2024 14:19:38 -0800 Subject: [PATCH 7/9] Deprecate `Process::Status#exit_status` (#8647) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johannes Müller --- src/process/status.cr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/process/status.cr b/src/process/status.cr index 933e81d5ad84..268d2f9e52d6 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -104,6 +104,7 @@ end class Process::Status # Platform-specific exit status code, which usually contains either the exit code or a termination signal. # The other `Process::Status` methods extract the values from `exit_status`. + @[Deprecated("Use `#exit_reason`, `#exit_code`, or `#system_exit_status` instead")] def exit_status : Int32 @exit_status.to_i32! end @@ -268,7 +269,7 @@ class Process::Status # define __WEXITSTATUS(status) (((status) & 0xff00) >> 8) (@exit_status & 0xff00) >> 8 {% else %} - exit_status + @exit_status.to_i32! {% end %} end From 9a218a0ff0e13d0292aff1d21fbf2996b67a2783 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Wed, 25 Dec 2024 17:19:54 -0500 Subject: [PATCH 8/9] Add `MacroIf#is_unless?` AST node method (#15304) --- spec/compiler/macro/macro_methods_spec.cr | 5 +++++ spec/compiler/parser/parser_spec.cr | 2 +- src/compiler/crystal/macros.cr | 12 +++++++++++- src/compiler/crystal/macros/methods.cr | 2 ++ src/compiler/crystal/syntax/ast.cr | 7 ++++--- src/compiler/crystal/syntax/parser.cr | 2 +- 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 10ba78d5bdc6..979a507d624d 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -2726,6 +2726,11 @@ module Crystal it "executes else" do assert_macro %({{x.else}}), "\"foo\"", {x: MacroIf.new(BoolLiteral.new(true), StringLiteral.new("test"), StringLiteral.new("foo"))} end + + it "executes is_unless?" do + assert_macro %({{x.is_unless?}}), "true", {x: MacroIf.new(BoolLiteral.new(true), StringLiteral.new("test"), StringLiteral.new("foo"), is_unless: true)} + assert_macro %({{x.is_unless?}}), "false", {x: MacroIf.new(BoolLiteral.new(false), StringLiteral.new("test"), StringLiteral.new("foo"), is_unless: false)} + end end describe "macro for methods" do diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 897e5bf7060c..ab8b1e9edfca 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1156,7 +1156,7 @@ module Crystal it_parses "macro foo;bar{% if x %}body{% else %}body2{%end%}baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroIf.new("x".var, "body".macro_literal, "body2".macro_literal), "baz;".macro_literal] of ASTNode)) it_parses "macro foo;bar{% if x %}body{% elsif y %}body2{%end%}baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroIf.new("x".var, "body".macro_literal, MacroIf.new("y".var, "body2".macro_literal)), "baz;".macro_literal] of ASTNode)) it_parses "macro foo;bar{% if x %}body{% elsif y %}body2{% else %}body3{%end%}baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroIf.new("x".var, "body".macro_literal, MacroIf.new("y".var, "body2".macro_literal, "body3".macro_literal)), "baz;".macro_literal] of ASTNode)) - it_parses "macro foo;bar{% unless x %}body{% end %}baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroIf.new("x".var, Nop.new, "body".macro_literal), "baz;".macro_literal] of ASTNode)) + it_parses "macro foo;bar{% unless x %}body{% end %}baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroIf.new("x".var, Nop.new, "body".macro_literal, is_unless: true), "baz;".macro_literal] of ASTNode)) it_parses "macro foo;bar{% for x in y %}\\ \n body{% end %}baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroFor.new(["x".var], "y".var, "body".macro_literal), "baz;".macro_literal] of ASTNode)) it_parses "macro foo;bar{% for x in y %}\\ \n body{% end %}\\ baz;end", Macro.new("foo", [] of Arg, Expressions.from(["bar".macro_literal, MacroFor.new(["x".var], "y".var, "body".macro_literal), "baz;".macro_literal] of ASTNode)) diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index a2ea0aeb85fe..ae6634e83a6f 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -2361,7 +2361,7 @@ module Crystal::Macros end end - # An `if` inside a macro, e.g. + # An `if`/`unless` inside a macro, e.g. # # ``` # {% if cond %} @@ -2369,6 +2369,12 @@ module Crystal::Macros # {% else %} # puts "Else" # {% end %} + # + # {% unless cond %} + # puts "Then" + # {% else %} + # puts "Else" + # {% end %} # ``` class MacroIf < ASTNode # The condition of the `if` clause. @@ -2382,6 +2388,10 @@ module Crystal::Macros # The `else` branch of the `if`. def else : ASTNode end + + # Returns `true` if this node represents an `unless` conditional, otherwise returns `false`. + def is_unless? : BoolLiteral + end end # A `for` loop inside a macro, e.g. diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index ab7b353fec45..f2691ba707c9 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1541,6 +1541,8 @@ module Crystal interpret_check_args { @then } when "else" interpret_check_args { @else } + when "is_unless?" + interpret_check_args { BoolLiteral.new @is_unless } else super end diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 9ccd8dda1f69..fb443e2e6777 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -2238,8 +2238,9 @@ module Crystal property cond : ASTNode property then : ASTNode property else : ASTNode + property? is_unless : Bool - def initialize(@cond, a_then = nil, a_else = nil) + def initialize(@cond, a_then = nil, a_else = nil, @is_unless : Bool = false) @then = Expressions.from a_then @else = Expressions.from a_else end @@ -2251,10 +2252,10 @@ module Crystal end def clone_without_location - MacroIf.new(@cond.clone, @then.clone, @else.clone) + MacroIf.new(@cond.clone, @then.clone, @else.clone, @is_unless) end - def_equals_and_hash @cond, @then, @else + def_equals_and_hash @cond, @then, @else, @is_unless end # for inside a macro: diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 569bbd4d9409..60a3ec6414a7 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -3505,7 +3505,7 @@ module Crystal end a_then, a_else = a_else, a_then if is_unless - MacroIf.new(cond, a_then, a_else).at_end(token_end_location) + MacroIf.new(cond, a_then, a_else, is_unless: is_unless).at_end(token_end_location) end def parse_expression_inside_macro From eb56b5534005e24284411cf3e8d589d39bbb4bd8 Mon Sep 17 00:00:00 2001 From: Mike Robbins Date: Thu, 26 Dec 2024 13:27:11 -0500 Subject: [PATCH 9/9] Fix error handling for `LibC.clock_gettime(CLOCK_MONOTONIC)` calls (#15309) POSIX `clock_gettime` returns 0 on success and -1 on error. See https://man7.org/linux/man-pages/man3/clock_gettime.3.html#RETURN_VALUE --- src/crystal/system/unix/time.cr | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/crystal/system/unix/time.cr b/src/crystal/system/unix/time.cr index 2ead3bdb0fa2..5ffcc6f373a2 100644 --- a/src/crystal/system/unix/time.cr +++ b/src/crystal/system/unix/time.cr @@ -39,9 +39,8 @@ module Crystal::System::Time nanoseconds = total_nanoseconds.remainder(NANOSECONDS_PER_SECOND) {seconds.to_i64, nanoseconds.to_i32} {% else %} - if LibC.clock_gettime(LibC::CLOCK_MONOTONIC, out tp) == 1 - raise RuntimeError.from_errno("clock_gettime(CLOCK_MONOTONIC)") - end + ret = LibC.clock_gettime(LibC::CLOCK_MONOTONIC, out tp) + raise RuntimeError.from_errno("clock_gettime(CLOCK_MONOTONIC)") unless ret == 0 {tp.tv_sec.to_i64, tp.tv_nsec.to_i32} {% end %} end